C语言fork后的子进程变为僵尸进程

C/C++ 2017-11-22

起步

进程之所以会有一个僵尸这种状态是因为要能更好的维护子进程的信息,以便父进程在以后某个时间可以获取时间,子进程ID,资源利用情况(CPU时间,内存使用量)等。

我在子进程中将子进程 exit 后,发现子进程并没有消失,而是变成了僵尸进程。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int main()
{
    pid_t pid;
    while (1){
        pid = fork();
        if (pid < 0){
            puts("fock error");
            exit(1);
        }
        else if(pid == 0){
            printf("I am a Child Process, pid is %d.\n", getpid());
            sleep(1);
            exit(1);
        }
        else{
            sleep(3);
        }
    }
    return 0;
}

运行几秒后用 ps 命令查看:

20171122112945.png

僵尸进程产生的原因

要在当前进程中生成一个子进程,一般需要调用 fork 这个系统调用,如果子进程先于父进程退出, 同时父进程又没有调用 wait/waitpid ,则该子进程将成为僵尸进程。<defunct> 和进程状态 Z+ 都能表明这个进程僵尸了。

避免产生僵尸进程

通常情况下,我们是不愿意留存僵尸进程的,它们占用内核中的空间,最终可能导致我们耗尽进程资源。既然清楚了子进程僵尸的原因,下面就看看如何避免它。

1. 使用 wait 回收子进程

int main()
{
    int status;
    while (1){
        ...
        else {
            wait(&status); // 回收子进程
            sleep(3);
        }
    }
    return 0;
}

但是,wait 会将主进程阻塞,如果主进程运行到此处,而此时子进程并没有运行完,那么主进程会等待。

2. 使用 signal 信号处理

当子进程退出的时候,内核都会给父进程一个 SIGCHLD 信号,所以我们可以建立一个捕获SIGCHLD信号的信号处理函数,在函数体中调用 wait(或 waitpid ),就可以清理退出的子进程以达到防止僵尸进程的目的。

void sig_chld( int signo ) {
    int stat;
    wait(&stat);    
}
int main() {
    signal(SIGCHLD,  &sig_chld);
    //...
}

运维上遇到僵尸程序怎么办

上面的方法当然是开发上避免的方法,如果是运维上遇到怎么办呢?僵尸进程是 kill -9 都不能杀掉的。有一个办法是,找到僵尸进程的父进程,将父进程杀掉,子进程就自动消失了。因为没有父进程的僵尸进程就成为”孤儿进程”,过继给 1 号进程 initinit始终会负责清理僵尸进程。找僵尸进程的父进程一个命令就够了:ps -ef | grep defunct_process_pid


本文由 hongweipeng 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

赏个馒头吧