Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

Vinllen Chen


To be a better coder

父进程捕获SIGCHLD信号依旧产生僵死进程

  最近自己写的一段代码出现了很多僵死进程,简约版本代码如下:

#include <stdio.h>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <sys/wait.h>
#include <pthread.h>

using namespace std;

int cnt = 0;

void sig_handler(int signo) {  
    pid_t pid;
    int stat;
    pid = wait(&stat);
    cout << "cnt:" << ++cnt << ", pid:" << pid << " signal:" << signo << endl;
}

int main() {  
    signal(SIGCHLD, sig_handler);
    for (int i = 0; i < 100; ++i) {
        if (fork() == 0) {
            sleep(1);
            exit(0);
        }
    }
    printf("wait\n");
    while (1);
}

运行结果是有时候会出现僵死进程,有时候运行又是正常的。

[vinllen@my-host]$ ./a.out
wait  
cnt:1, pid:4383 signal:17  
cnt:2, pid:4384 signal:17  
cnt:3, pid:4385 signal:17  
cnt:4, pid:4386 signal:17  
cnt:5, pid:4387 signal:17  
…
cnt:94, pid:4476 signal:17  
cnt:95, pid:4477 signal:17  
cnt:96, pid:4478 signal:17  
cnt:97, pid:4479 signal:17  
cnt:98, pid:4480 signal:17  
[vinllen@my-host ~]$ ps aux | grep a.out
Vinllen       4382 96.2  0.0  13896  1084 pts/8    R+   15:14   0:03 ./a.out  
Vinllen       4481  0.0  0.0      0     0 pts/8    Z+   15:14   0:00 [a.out] <defunct>  
Vinllen       4482  0.0  0.0      0     0 pts/8    Z+   15:14   0:00 [a.out] <defunct>  
Vinllen       4493  0.0  0.0 105300   864 pts/9    S+   15:14   0:00 grep a.out  

  僵死进程出现的原因无非是退出时,父进程未捕获子进程退出的状态,而内核会一直保留这部分状态,导致出现僵死进程。
  而代码中,父进程明明捕获了SIGCHLD信号量并调用wait,为什么会出现僵死进程。而且调整sleep的时间发现缩短时间,僵死进程出现的个数变大。
  问题原因在于wait函数,调用wait函数一共会出现以下三种情况:

  1. 如果其所有子进程都还在运行,则阻塞
  2. 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
  3. 如果它没有任何子进程,则立即出错返回。

注意,是一个子进程终止,wait就会立即返回。由于一次fork了100个进程,并发量很大,可能出现多个子进程退出,发送SIGCHLD信号量,导致同时进入sig_handler函数(此处可能有理解不到位的地方,我的理解是如此。不同版本的内核实现不一样,早期版本是一个信号来临后,屏蔽一段时间窗口内同一个信号量;后续的内核版本是连续触发。),而wait一次仅能处理一个,后面的信号被丢弃,也就产生了僵死进程。
  此处需要补充一下信号量的知识,信号量出现时会产生中断,打断原来运行的父进程的程序,从而进入信号量处理函数。如果从信号处理程序返回(没有goto和jump的情况下),继续执行原父进程程序指令。
  可以采用以下3种方式规避僵死进程:

  1. 忽略SIGCHLD信号量
  2. double fork方式。父进程fork子进程A,子进程A fork 子子进程B,然后A退出,B由init托管,执行对应的程序,这样退出时init能够拿到退出状态。
  3. 调用while()循环方式拿到退出状态,直到错误返回即当前一刻所有退出子进程状态已处理完毕。而不必担心while会陷入死循环,因为当前一刻所有退出的子进程处理完毕后会立刻break退出执行原程序的状态指令。
void sig_chld(int signo)  
{
    pid_t   pid;
    int     stat;

    //pid = wait(&stat);
    while (1) {
        pid = wait(&stat);
        printf("child %d terminated\n", pid);
        if (pid == -1) {
            break; // means all child prcess exit
        }
    }
    return;
}

  其实我的子进程又做了一些create线程的操作,而线程的确也会产生僵死进程,线程退出如果是join状态需要通过pthread_join获取返回状态,如同wait一样。不过detach状态就不用处理。
  另外,还需要注意的一点,在信号处理函数里面,需要尽量简洁,像我这样调用cout方式很可能不安全(非异步安全)。

说明

转载请注明链接:vinllen.com/fu-jin-cheng-bu-huo-sigchldxin-hao-yi-jiu-chan-sheng-jiang-si-jin-cheng

参考:

APUE
https://stackoverflow.com/questions/45454058/fork-100-processes-at-same-time-and-sometimes-some-processes-become-zombie/45454369#45454369
http://huoyj.iteye.com/blog/1907529
http://man7.org/linux/man-pages/man2/waitpid.2.html


About the author

vinllen chen

Beijing, China

格物致知


Discussions

comments powered by Disqus