父进程与子进程的生命周期一般都不相同,父子进程间互有长短。这就会引起孤儿进程和僵尸进程的问题
孤儿进程
简单的说,孤儿进程就是在子进程还未退出之前,父进程先退出了,子进程就成了孤儿进程。那么,谁会成为孤儿子进程的父进程呢?进程ID为1的众进程之祖------init会接管孤儿进程。换言之,某一子进程的父进程终止后,对getppid()的调用将返回1。这是判定某一子进程之“生父”是否“在世”的方法之一(前提是假设该子进程由init之外的进程创建)
僵尸进程
简单的说,僵尸进程就是子进程退出后,父进程还没有调用wait,那么子进程就成了僵尸进程。在父进程执行wait()之前,其子进程就已经终止,这将会发生什么?此处的要点在于,即使子进程已经结束,系统能够仍然允许其父进程在之后的某一时刻去执行wait(),以确定盖子进程是如何终止的。内核通过将子进程转为僵尸进程来处理这种情况。这也意味着将释放子进程所把持的大部分资源,以便供其他进程重新使用。该进程所唯一保留的是内核进程表中的一条记录,其中包含了子进程ID、终止状态、资源使用数据等信息。
关于僵尸进程的一些细节
僵尸进程名称的由来,则源于UNIX系统对电影情节的效仿-----无法通过信号来杀死僵尸进程,即便是(银弹)SIGKILL。这就确保了父进程总是可以执行wait()方法。
当父进程执行wait()后,由于不再需要子进程所剩余的最后信息,故而内核将删除僵尸进程。另一方面,如果父进程为执行wait()随即退出,那么init进程将接管子进程并自动调用wait(),从而从系统中移除僵尸进程。
如果父进程创建了某一子进程,但并未执行wait(),那么在内核的进程表中将为该子进程永久保留一条记录。如果存在大量此类僵尸进程,它们势必将填满内核进程表,从而阻碍新进城的创建。既然无法用信号杀死僵尸进程,那么从系统中将其移除的唯一方法就是杀掉他们的父进程(或等待其父进程终止),此时init进程将接管和等待这些僵尸进程,从而从系统中将它们清理掉。
在设计长生命周期得父进程时,这些予以具有重要意义。换句话说,在此类应用中,父进程应执行wait(),以确保系统总是能够清理那些死去的子进程,避免使其成为长寿僵尸。
孤儿进程示例
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t childpid = fork();
switch (childpid)
{
case -1:
{
perror("fork()");
exit(EXIT_FAILURE);
}
case 0:
{
sleep(100);
_exit(EXIT_SUCCESS);
}
default:
{
exit(EXIT_SUCCESS);
}
}
}
使用ps -ef |grep fork查看结果
ubuntu 417 1 0 13:15 pts/5 00:00:00 ./fork
可以看到子进程的pid为417,其父进程为1
僵尸进程示例
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t childpid = fork();
switch (childpid)
{
case -1:
{
perror("fork()");
exit(EXIT_FAILURE);
}
case 0:
{
_exit(EXIT_SUCCESS);
}
default:
{
sleep(100);
exit(EXIT_SUCCESS);
}
}
}
使用ps -ef |grep fork查看结果
209:ubuntu 1891 23556 0 13:22 pts/5 00:00:00 ./fork
210:ubuntu 1894 1891 0 13:22 pts/5 00:00:00 [fork] <defunct>
可以看到进程id为1894的为子进程,在该进程的后面有<defunct>表示该进程为僵尸进程。