您的位置:首页 > 编程语言

菜鸟学习历程【17】进程控制编程

2017-12-06 20:54 267 查看

进程控制编程

进程:进程是一个具有一定独立功能的程序的一次运行活动,同时也是资源分配的最小单元;

进程程序
程序执行的实例放到磁盘的可执行文件
进程不可在计算机之间迁移程序通常对应着文件、静态和可以复制
动态静态
暂时:进程是一个状态变化的过程长久:程序可长久保存
进程与程序组成不同:进程的组成包括程序、数据和进程控制块(即进程状态信息)

进程与程序的对应关系:通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。

进程的生命周期

创建: 每个进程都是由其父进程创建,进程可以创建子进程,子进程又可以创建子进程的子进程

运行: 多个进程可以同时存在,进程间可以通信

撤销: 进程可以被撤销,从而结束一个进程的运行

进程的状态(运行):

执行状态:进程正在占用CPU

就绪状态:进程已具备一切条件,正在等待分配CPU的处理时间片

等待状态:进程不能使用CPU,若等待事件发生则可将其唤醒

Linux进程

Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。

也就是说,每个进程都是一个独立的运行单位,拥有各自的权利和责任。其中,各个进程都运行在独立的虚拟地址空间,因此,即使一个进程发生异常,它也不会影响到系统中的其他进程。

进程ID:(PID)标准进程的唯一数字

父进程:(PPID)

启动进程的用户ID(UID)

进程互斥:进程互斥是指当有若干进程都要使用某一共享资源时,任何时刻最多允许一个进程使用,其他要使用该资源的进程必须等待,直到占用该资源者释放了该资源为止。

临街资源:(共享资源)一次只允许一个进程访问的资源称为临界资源

临界区:进程中访问临界资源的那段程序代码称为临界区

进程同步: 一组并发进程按一定的顺序执行的过程称为进程间的同步,具有同步关系一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件。

进程调度:按一定算法,从一组待运行的进程中选出一个来占有CPU运行。

调度方式:抢占(优先级) 、非抢占式

算法:

先来先服务调度算法

短进程优先调度算法

高优先级优先调度算法

时间片轮转法

目前,后两者最为常用。

死锁:多个进程因竞争资源而形成一种僵局,若无外力作用,这些进程都将永远不能再向前推进

获取ID

pid_t getpid(void) 获取进程ID
pid_t getppid(void) 获取父进程ID

例如:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
printf("PID = %d\n", getpid());
printf("PPID = %d\n", getppid());

while(1);

return 0;
}


进程创建

1.fork()

pid_t fork(void)  创建子进程


子进程的ID号为父进程的ID号 + 1

特别:返回值返回两次,父进程返回子进程的ID号,子进程返回0

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
pid = fork();
if(-1 == pid)
{
perror("fork");
exit(1);
}
else if(0 == pid) //子进程返回0,子进程执行部分
{
printf("Child Process Id = %d\nParent Process Id = %d\n", getpid(), getppid());
}
else  //父进程执行部分
{
printf("Parent Process Id = %d\n", getpid());
}

return 0;
}


思考:下面这段代码的最终输出结果是???

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
int count = 0;
count++;
printf("%d\n", count);
return 0;
}


注意:使用fork()创建进程后,会有两个进程存在,对于fork而言,这两个进程在不同的地址空间(之前我们讲过对于一个进程而言会有一个4G的虚拟内存),那么对于fork后的两个进程,会存在两个地址一模一样的空间,子进程会将父进程的所有代码(除去各自独有的代码)复制到自己的代码段。

写时复制:当某个进程访问某个变量,并要修改时,则会分配另一个空间。

对于上面这个情况,当子进程尝试count++时,会改变count的值,此时系统会开辟另一个空间给子进程,此时count为0,那么经过自加后,输出count为1;对于父进程而言,也是一样的。

所以,上面的代码最终结果是:

1
1


2.vfork()

pid_t vfork(void);


vfork的子进程必须加exit()退出,否则会出错;

子进程先运行,子进程运行后,再执行父进程

vfork的子进程与父进程共享相同的资源

下面这段代码的输出结果又是如何的呢?

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
pid_t pid;
int count = 0;
pid = vfork();
if(-1 == pid)
{
perror("vfork");
exit(1);
}
else if(0 == pid)
{
count++;
printf("count = %d\n", count);
exit(1);
}
else
{
count++;
printf("count = %d\n", count);
}
return 0;
}


我们知道,vfork的子进程和父进程共享相同的资源,那么在执行子进程中的count++时,count由原来的0变成1,输出1;

执行父进程的count++时,count又由1变成2,输出2;

所以最终结果是:

1
2


如果我将程序改动如下,结果又是什么呢?

......
int main()
{
pid_t pid;
pid = vfork();
int count = 0;
......
}


将pid = vfork();与int count = 0;的位置交换,最终结果会是如何?

结果如下:

1
1


为什么呢?

此时子进程和父进程依旧共享相同的资源,但它也共享了”int count = 0”这句话,所以在子进程执行完毕后,count = 1;当父进程开始执行时,先执行的是int count = 0,将原来的1又置为0,再经过自加操作,那么输出结果还是1;

3.exec函数族

exec启动一个新程序,替换原有的进程,因此进程的PID不会改变

1.int execl(const char *path, const char *arg, …);

path:被执行程序名(含完整路径)。
arg: 被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。


例如:

使用execl在进程中调用ls,显示当前目录下的文件信息。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
pid_t pid;
pid = vfork();

if(-1 == pid)
{
perror("vfork");
exit(1);
}
else if(0 == pid)
{
printf("Child process:%d\n", getpid());
execl("/bin/ls",NULL);
}
else
{
printf("Parent process\n");
}
return 0;
}


2.int execv(const char *path, char *const argv[]);

参数:

path:被执行程序名(含完整路径)。

argv[]: 被执行程序所需的命令行参数数组。

例如:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
char *argv[] = {"ls",NULL};
execv("/bin/ls", argv);

return 0;
}


进程等待

先介绍两个概念,一个是孤儿进程,一个是僵尸进程。

孤儿进程:其父进程在它之前结束,不能再对它进行回收

僵尸进程:子进程结束后,没有被回收的时间段内,被称为僵尸进程。

1.pid_t wait(int *status);

功能:阻塞该进程,直到其某个子进程退出。

例如:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
pid_t pid;
pid = fork();
int status;
if(-1 == pid)
{
perror("fork");
exit(1);
}
else if(0 == pid)
{
sleep(2);
printf("Child Process\n");
exit(6);
}
else
{
printf("Parent Process\n");
wait(&status);  //如果不需要知道它的退出类型,wait的形参写NULL也可以
if(WIFEXITED(status))
{
printf("Exit Normally %d\n", WEXITSTATUS(status));
}
}
return 0;
}


如果不写wait(),在子进程sleep的过程中,其父进程就已经结束了,那么子进程就会变成孤儿进程,但加上wait后,父进程直到子进程结束才会退出。

...
else if(0 == pid)
{
printf("Child Process\n");
exit(6);
}
else
{
sleep(2);
printf("Parent Process\n");
wait(&status);
if(WIFEXITED(status))
{
printf("Exit Normally %d\n", WEXITSTATUS(status));
}
}
...


如果我们将子进程中的sleep函数放在父进程中,那么在子进程执行结束后,父进程需要沉睡两秒后才执行,这两秒内,父进程没有去回收子进程,所以称子进程为僵尸进程,但两秒钟后,子进程又被回收。

2.pid_t waitpid(pid_t pid, int *status, int options);

参数pid:(欲等待的子进程识别码)

pid < -1: 等待进程组识别码为pid绝对值的任何子进程。

pid = -1: 等待任何子进程,相当于wait()。

pid = 0: 等待进程组识别码与目前进程相同的任何子进程。

pid > 0: 等待任何子进程识别码为pid的子进程。

参数option:(通常设置为0)

WNOHANG: 如果没有任何已经结束的子进程则马上返回,不予以等待。

WUNTRACED :如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。

进程退出

exit()与_exit()

exit():在停止进程之前,要检查文件的打开情况,并把文件缓冲区中的内容写回文件才停止进程。

_exit():直接使进程停止,清除其使用的内存,并清除缓冲区中内容

所以,我们通常选择使用exit(),而不是用_exit();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  进程