进程控制3——进程终止和等待

来源:互联网 发布:聚划算淘宝商城特价区 编辑:程序博客网 时间:2024/05/19 23:55

一个进程不可能永远的存在,必然存在着结束,我们需要进行一些操作来终止这些进程。而父进程创建子进程后,如何知道子进程什么时候终止?如何知道子进程怎么终止?所以又带来了进程等待的问题。

下面先将一些关于进程终止 的函数,
exit,_exit都是用于终止进程的函数
区别:
_exit: 直接使进程停止,清除其使用的内存,并清除缓冲区中内容
exit:在停止进程之前,要检查文件的打开情况,并把文件缓冲区中的内容写回文件才停止进程。

exit()

表头文件: #include  <stdlib.h>定义函数: void exit(int status);

函数说明:exit()用来正常终结目前进程的执行,并把参数status返回给父进程,而进程所有的缓冲区数据会自动写回并关闭未关闭的文件。
_exit()

表头文件: #include<unistd.h>定义函数: void _exit(int status);

函数说明
此函数调用后不会返回,并且会传递SIGCHLD信号给父进程,父进程可以由wait函数取得子进程结束状态。

这两种退出方式没有太多好说的,自己简单试一试就可以发现一些区别了。

子进程比父进程先退出:僵尸进程
僵尸进程指的是那些虽然已经终止的进程,但仍然保留一些信息,等待其父进程为其收尸。

僵尸进程产生的过程:
1.父进程调用fork创建子进程后,子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中(进程描述符占有极少的内存空间)。

2.子进程的状态变成EXIT_ZOMBIE,并且向父进程发送SIGCHLD 信号,父进程此时应该调用 wait() 系统调用来获取子进程的退出状态以及其它的信息。在 wait 调用之后,僵尸进程就完全从内存中移除。

3.因此一个僵尸存在于其终止到父进程调用 wait 等函数这个时间的间隙,一般很快就消失,但如果编程不合理,父进程从不调用 wait 等系统调用来收集僵尸进程,那么这些进程会一直存在内存中。

pid_t pid = fork(); switch (pid){    case -1:        perror ("fork");        break;    case 0: // 子进程        printf ("我是子进程,我的Id 是%d\n", getpid());        exit(0);    default: // 父进程        printf ("我是父进程,Id = %d\n", getpid());        while (1);        break;}

这里写图片描述
我们继续用那条指令抓一下进程,会发现在子进程先结束后留下了,它的ID号还在,后面加上了一个<defunct>,这个单词的意思是死者,很明显,这个进程就是我们所说的僵尸进程了。如果不进行一定处理的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

子进程先于父进程结束,如果不做适当处理会产生僵尸进程,那父进程先于子进程结束呢?
若父进程比子进程先终止,则该父进程的所有子进程的父进程都改变为init进程。我们称这些进程由init进程领养。其执行顺序大致如下:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止的进程的子进程,如果是,则该进程的父进程ID就更改为1(init进程的ID); 有init领养的进程不会称为僵死进程,因为只要init的子进程终止,init就会调用一个wait函数取得其终止状态。这样也就防止了在系统中有很多僵死进程。
下面是一个父进程提前结束的案例:

pid_t pid = fork();    switch (pid)    {        case -1:            perror ("fork");            break;        case 0: // 子进程            printf ("我是子进程,我的Id 是%d\n", getpid());            while (1)            break;        default: // 父进程            printf ("我是父进程,Id = %d\n", getpid());            printf ("我走啦\n");        //  while (1);            break;    }

这里写图片描述
从图中不难看出,父进程已经结束了,而子进程依旧存在,但此时接手它的已经成为了1,1是谁?没错1就是init进程,我们通常所说的祖父进程,从此这个子进程不再受出了1之外的任何人控制了。
很明显父进程先于子进程结束,产生的危害就小多了,甚至还给我们一个思路,一个让程序运行在后台的思路,就让我们产生了一个关于守护进程的想法。
守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。
下面是一个创建守护进程的思路:

int daemonize(int nochdir, int noclose){    // 1、创建子进程,关闭父进程    pid_t pid = fork();    if (pid > 0)    {        exit(0);    }    else if (pid < 0)    {        return -1;    }    // 2、设置文件的掩码, mode & ~umask    umask(0);    // 3、设置新的会话: 脱离当前会话和终端的控制    if (setsid() < 0)    {        return -1;    }    if (nochdir == 0 )    {        // 4、改变当前的工作目录        if (chdir("/") < 0)        {            return -1;    }    }    // 标准输入、关闭标准输出、标准错误    close(STDIN_FILENO);    close(STDOUT_FILENO);    close(STDERR_FILENO);    if (noclose == 0)    {        // 重定向标准输入、关闭标准输出、标准错误        open("/dev/null", O_RDONLY);   // 0         open("/dev/null", O_RDWR);   // 1        open("/dev/null", O_RDWR);   // 2    }    return 0;}

创建一个守护进程需要经历这么几步,创建子进程并退出父进程,设置文件掩码,设置新会话,改变工作目录,关闭并重定向标准输入,标准输出和标准错误。虽然有这样一个函数daemon()可以帮助我们完成这一些,但一些原理我们最好还是需要知道的。

上文我们提到了僵尸进程,知道了它的危害性,既然危害这么大,我们是不是该采取一些措施呢?让他的父进程做一些处理呢?下面我们就来讲如何处理僵尸进程。
我们先讲两个函数wait()和waitpid()

#include <sys/wait.h>pid_t wait(int *status);pid_t waitpid(pid_t pid, int *status, int options); 

调用wait或waitpid的进程可能发生的情况有:
如果所有子进程都还在运行,则阻塞(Block)。
如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
如果它没有任何子进程,则立即出错返回。
在一个子进程终止前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞。
waitpid并不等待在其调用之后的第一个终止的子进程。它有若干个选项,可以控制它所等待的进程。
如果一个子进程已经终止,并且是一个僵尸进程,wait立即返回并取得该子进程的状态,否则wait使其调用者阻塞直到一个子进程终止。如果调用者阻塞并且它有多个子进程,则在其一个子进程终止时,wait就立即返回。因为wait返回终止子进程的ID,所以总能了解到是哪一个子进程终止了。
现在我们就使用wait()来处理之前的僵尸进程,

pid_t pid = fork();switch(pid){    case -1:        perror("fork");        break;    case 0:        printf("子进程,ID是%d\n",getpid());        sleep(5);        exit(0);        break;    default:        printf("等待子进程结束\n");        sleep(20);        pid_t childId = wait(NULL);        printf("成功处理掉一个子进程,该子进程是%d\n",childId);        sleep(5);        break;}

这里写图片描述
在程序执行5s后,子进程离开了,这时候产生了一个僵尸进程8623
这里写图片描述
20s之后我们在终端发现提示8623已经被处理了,
这里写图片描述
再抓取一下进程发现子进程已经不见了
除了解决僵尸进程的问题,我们还可以通过几个宏来了解一下终止的状态:
WIFEXITED(status)
若子进程正常终止,该宏返回true。
此时,可以通过WEXITSTATUS(status)获取子进程的退出状态(exit status)。
WIFSIGNALED(status)
若子进程由信号杀死,该宏返回true。
此时,可以通过WTERMSIG(status)获取使子进程终止的信号值。
WIFSTOPPED(status)
若子进程被信号暂停(stopped),该宏返回true。
此时,可以通过WSTOPSIG(status)获取使子进程暂停的信号值。
WIFCONTINUED(status)
若子进程通过SIGCONT恢复,该宏返回true。
有兴趣可以测试一下。

waitpid()
pid_t waitpid (pid_t pid, int * status, int options)
功能:会暂时停止目前进程的执行,直到有信号来到或子进程结束
参数:如果不在意结束状态值,则参数status可以设成NULL。
参数pid为欲等待的子进程识别码:
pid<-1 等待进程组识别码为pid绝对值的任何子进程。
pid=-1 等待任何子进程,相当于wait()。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为pid的子进程。
参数option可以为0 或下面的OR 组合
WNOHANG: 如果没有任何已经结束的子进程则马上返回,不予以等待。
WUNTRACED :如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
返回值:如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。
waitpid()拥有wait的一部分功能,同时能根据ID号处理僵尸进程,有兴趣的可以研究一下,在这就不多说了。

阅读全文
0 0
原创粉丝点击