Unix/Linux编程:孤儿进程和僵尸进程

父进程和子进程生命周期一般都不相同,父子进程程间互有长短。这就引出了下面两个问题

问题:谁是孤儿子进程的父进程?

  • 进程ID为1的众进程之组------init会接管孤儿进程
  • 换言之,某一子进程的父进程终止后,对getppid()的调用将返回1.这时判定某一子进程的"生父"是否"在世"的方法之一(前提是假设该子进程由 init 之外的进程创建)

注:使用参数 PR_SET_PDEATHSIG 调用 Linux 特有的系统调用 prctl(),将有可能导致某一进程在成为孤儿时收到特定信号。

问题:在父进程执行wait()之前,其子进程就已经终止,这将会发生什么?

  • 此处的要点在于,即使子进程已经结束,系统仍然运行其父进程在以后的某一时刻去执行wait(),以确定该子进程是如何终止的。
  • 内核通过将子进程转为僵尸进程(zombie)来处理这种情况。
    • 这也意味着将释放子进程所把持的大部分资源,以便供其他进程重新使用
    • 该进程所唯一保留的是内核检查表中的一条记录,其中包含了子进程ID、终止状态、资源使用数据等信息

至于僵尸进程名称的由来,则源于Unix系统对电影情节的效仿------无法通过信号来杀死僵尸进程,即使是SIGKILL。这就确保了父进程总是可以执行wait()方法

当父进程执行wait()之后,由于不再需要子进程搜索剩余的最后信息,故而内核将删除僵尸进程。另一方面,如果父进程未执行wait()随即退出,那么init进程将接管子进程并自动调用wait(),从而从系统中移除僵尸进程。

如果父进程创建了某一子进程,但并未执行 wait(),那么在内核的进程表中将为该子进程永久保留一条记录。如果存在大量此类僵尸进程,它们势必将填满内核进程表,从而阻碍新进程的创建。既然无法用信号杀死僵尸进程,那么从系统中将其移除的唯一方法就是杀掉它们的父进程(或者等待父进程终止),此时init进程将接管和等待这些僵尸进程,从而从系统中将它们清理调

在设计长生命周期的父进程(例如:会创建众多子进程的网络服务器和 Shell)时,这些语义具有重要意义。换句话说,在此类应用中,父进程应执行 wait()方法,以确保系统总是能够清理那些死去的子进程,避免使其成为长寿僵尸。父进程在处理 SIGCHLD 信号时,对 wait()的调用既可同步,也可异步

下面 展示了一个僵尸进程的创建,以及发送 SIGKILL 信号无法杀死僵尸进程的例子:

#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <zconf.h>
#include <sys/wait.h>
#include <errno.h>

#define CMD_SIZE 200
int
main(int argc, char *argv[])
{
    char cmd[CMD_SIZE];
    pid_t childPid;

    setbuf(stdout, NULL);       /* Disable buffering of stdout */

    printf("Parent PID=%ld\n", (long) getpid());

    switch (childPid = fork()) {
        case -1:
            perror("fork");
            exit(EXIT_FAILURE);

        case 0:     /* Child: immediately exits to become zombie */
            printf("Child (PID=%ld) exiting\n", (long) getpid());
            _exit(EXIT_SUCCESS);

        default:    /* Parent */
            sleep(3);               /* Give child a chance to start and exit */
            snprintf(cmd, CMD_SIZE, "ps | grep %s", basename(argv[0]));
            system(cmd);            /* View zombie child */

            /* Now send the "sure kill" signal to the zombie */

            if (kill(childPid, SIGKILL) == -1){
                perror("kill");
                exit(EXIT_FAILURE);
            }

            sleep(3);               /* Give child a chance to react to signal */
            printf("After sending SIGKILL to zombie (PID=%ld):\n", (long) childPid);
            system(cmd);            /* View zombie child again */

            exit(EXIT_SUCCESS);
    }
}

Unix/Linux编程:孤儿进程和僵尸进程

上一篇:MOOC《Linux操作系统编程》学习笔记-实验五


下一篇:操作系统(Linux)