美文网首页
浅谈僵尸进程与孤儿进程

浅谈僵尸进程与孤儿进程

作者: 稀饭不加糖C | 来源:发表于2020-01-29 16:49 被阅读0次

    在《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) 的区别。

    示例代码:

    <?php
    // 获取当前进程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 "PPID:{$ppid} , PID:{$pid} child progress is running! {$i} \n";
            $rand = rand(1,3);
            sleep($rand);
        }
        exit("({$pid})child progress end!\n");
    } else {
        // 父进程执行程序
        $childList[$pid] = 1;
    }
    
    // 延迟5秒,父进程退出
    sleep(5);
    echo "({$parentPid})main progress end!\n";
    

    执行过程输出:

    孤儿进程演示.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() 这些孤儿进程,释放它们占用的系统进程表中的资源。这样,这些已经僵死的孤儿进程就能瞑目而去了。

    相关文章

      网友评论

          本文标题:浅谈僵尸进程与孤儿进程

          本文链接:https://www.haomeiwen.com/subject/aetbthtx.html