浅谈僵尸进程与孤儿进程

在《unix环境高级编程》中有提到僵尸进程孤儿进程。不少同学对这两个概念会混淆,这篇文章总结一下。

在 unix/linux 系统中,大多情况下,子进程是通过父进程 fork 创建的,注:系统调用 fork,是一个比较有意思系统调用,它调用一次,返回两个值,失败返回 -1,成功时在子进程返回 0,父进程返回所创建子进程的 pid。

子进程创建后,子进程的结束和父进程的运行是一个异步过程,也就是说父进程没办法预测子进程什么时候结束。 当一个子进程完成它的工作终止之后,其父进程需要调用 wait() 或 waitpid() 去获取子进程的终止状态。

孤儿进程

所谓孤儿进程,顾名思义,和现实生活中的孤儿有点类似,当一个进程的父进程结束时,但是它自己还没有结束,那么这个进程将会成为孤儿进程。最后孤儿进程将会被 init 进程(进程号为1)的进程收养,当然在子进程结束时也会由 init 进程完成对它的状态收集工作,因此一般来说,孤儿进程并不会有什么危害。

下面看一个关于孤儿进程的例子:先打印父进程 id,然后创建子进程,让父进程睡眠 5s,让子进程先运行循环打印出其进程 id(pid);随后子进程睡眠 1-3s(此运行过程父进程会结束),目的是让父进程先于子进程结束,让子进程有个孤儿的状态;最后子进程再打印出其进程 id(pid) 以及父进程 id(ppid) ;观察两次打印 其父进程 id(ppid) 的区别。

示例代码:

执行过程输出:

孤儿进程演示.png

可以看到,当父进程退出后,子进程被 init 进程收养。子进程执行完毕,init 进程完成对它的状态收集工作。

僵尸进程

一个进程使用 fork 创建子进程,如果子进程退出,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息,那么子进程的某些信息如进程描述符仍然保存在系统中。这种进程称之为僵死进程。

下面是1个关于僵尸进程的例子,创建子进程,然后让父进程睡眠 90s,让子进程先终止(注意和孤儿进程例子的区别);这里子进程结束后父进程没有调用 wait/waitpid 函数获取其状态,用ps查看进程状态可以看出子进程为僵尸状态。

示例代码:

// 获取当前进程ID
$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";

$childList = array();

// 创建子进程
$pid = pcntl_fork();
if ( $pid == -1) {
    // 创建失败
    exit("fork progress error!\n");
} else if ($pid == 0) {
    $repeatNum = 10;
    for ( $i = 1; $i <= $repeatNum; $i++) {
        // 子进程执行程序
        $ppid = posix_getppid();
        $pid = posix_getpid();

        echo date('Y-m-d H:i:s') , ' ';
        echo "PPID:{$ppid} , PID:{$pid} child progress is running! {$i} \n";
        $rand = rand(1,3);
        sleep($rand);
    }

    echo date('Y-m-d H:i:s') , ' ';
    exit("({$pid})child progress end!\n");
} else {
    // 父进程执行程序
    $childList[$pid] = 1;
}

// 延迟90秒,父进程退出
sleep(90);

echo date('Y-m-d H:i:s') , ' ';
echo "({$parentPid})main progress end!\n";

执行过程输出:

僵尸进程运行过程.png

僵尸进程状态:

僵尸进程进程状态.png

任何一个子进程 (init 除外) 在 exit() 之后,并非马上就消失掉,而是留下一个称为僵尸进程 (Zombie) 的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在 exit() 之后,父进程没有来得及处理,这时用 ps 命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用 ps 命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由 init 接管。init 将会以父进程的身份对僵尸状态的子进程进行处理。

僵尸进程的危害:

僵尸进程会在系统中保留其某些信息如进程描述符、进程 id 等等。以进程 id 为例,系统中可用的进程 id 是有限的,如果由于系统中大量的僵尸进程占用进程 id,就会导致因为没有可用的进程 id 系统不能产生新的进程,这种问题可就大了,这就是僵尸进程带来的危害。因此大部分情况下,我们都应当避免僵尸进程的产生。

如何消除僵尸进程:

严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大量僵死进程的那个元凶枪毙掉(也就是通过 kill 发送 SIGTERM 或者 SIGKILL 信号)。

枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被 init 进程接管,init 进程会 wait() 这些孤儿进程,释放它们占用的系统进程表中的资源。这样,这些已经僵死的孤儿进程就能瞑目而去了。

你可能感兴趣的:(浅谈僵尸进程与孤儿进程)