僵尸进程和孤儿进程

🖋️ 1、孤儿进程

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

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

int main(){
    pid_t pid;
    pid = fork(); //创建一个进程
    if (pid < 0){ //创建失败
        perror("fork error:");
        exit(1);
    }
    //子进程
    if (pid == 0){
        printf("I am the child process.\n");
        //输出进程ID和父进程ID
        printf("pid: %d\tppid:%d\n",getpid(),getppid());
        printf("I will sleep five seconds.\n");
        sleep(5); //睡眠5s,保证父进程先退出
        printf("pid: %d\tppid:%d\n",getpid(),getppid());
        printf("child process is exited.\n");
    }else{  //父进程
        printf("I am father process.\n");
        sleep(1); //父进程睡眠1s,保证子进程输出进程id
        printf("father process is  exited.\n");
    }
    return 0;
}

🖋️ 2、僵尸进程

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

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

🐹 2.1、僵尸进程的危害

僵尸进程会在系统中保留其某些信息如进程描述符、进程id等等。以进程id为例,系统中可用的pid是有限的,如果由于系统中大量的僵尸进程占用pid,就会导致因为没有可用的pid系统不能产生新的进程。

🐹 2.2、僵尸进程解决办法

🍈 2.2.1、通过信号机制

子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用waitpid进行处理僵尸进程。

需要注意的是,捕获SIGCHLD信号并且调用wait来清理退出的进程,不能彻底避免产生僵尸进程.

来看一种特殊的情况:

假设有一个client/server的程序,对于每一个连接过来的clientserver都启动一个新的进程去处理来自这个client的请求。然后有一个client进程,在这个进程内,发起了多个到server的请求(假设5个),则serverfork 5个子进程来读取client输入并处理(同时,当客户端关闭套接字的时候,每个子进程都退出);当我们终止这个client进程的时候 ,内核将自动关闭所有由这个client进程打开的套接字,那么由这个client进程发起的5个连接基本在同一时刻终止。这就引发了5个FIN,每个连接一个。server端接受到这5个FIN的时候,5个子进程基本在同一时刻终止。这就又导致差不多在同一时刻递交5个SIGCHLD信号给父进程。

首先运行服务器程序,然后运行客户端程序,运用ps命令看以看到服务器fork了5个子进程,如图:

然后Ctrl+C终止客户端进程,在我机器上边测试,可以看到信号处理函数运行了3次,还剩下2个僵尸进程,如图:

通过上边这个实验我们可以看出,建立信号处理函数并在其中调用wait并不足以防止出现僵尸进程,其原因在于:所有5个信号都在信号处理函数执行之前产生,而信号处理函数只执行一次,因为Unix信号一般是不排队的。 更为严重的是,本问题是不确定的,依赖于客户FIN到达服务器主机的时机,信号处理函数执行的次数并不确定。

正确的解决办法是调用waitpid而不是wait,这个办法的方法为:信号处理函数中,在一个循环内调用waitpid,以获取所有已终止子进程的状态。我们必须指定WNOHANG选项,他告知waitpid在有尚未终止的子进程在运行时不要阻塞。(我们不能在循环内调用wait,因为没有办法防止 wait在尚有未终止的子进程在运行时阻塞,wait将会阻塞到现有的子进程中第一个终止为止),下边的程序分别给出了这两种处理办法 (func_waitfunc_waitpid)。

🍈 2.2.2、 fork两次

原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程。

Last updated

Was this helpful?