父进程捕获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
函数一共会出现以下三种情况:
- 如果其所有子进程都还在运行,则阻塞
- 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
- 如果它没有任何子进程,则立即出错返回。
注意,是一个子进程终止,wait就会立即返回。由于一次fork了100个进程,并发量很大,可能出现多个子进程退出,发送SIGCHLD信号量,导致同时进入sig_handler
函数(此处可能有理解不到位的地方,我的理解是如此。不同版本的内核实现不一样,早期版本是一个信号来临后,屏蔽一段时间窗口内同一个信号量;后续的内核版本是连续触发。),而wait
一次仅能处理一个,后面的信号被丢弃,也就产生了僵死进程。
此处需要补充一下信号量的知识,信号量出现时会产生中断,打断原来运行的父进程的程序,从而进入信号量处理函数。如果从信号处理程序返回(没有goto和jump的情况下),继续执行原父进程程序指令。
可以采用以下3种方式规避僵死进程:
- 忽略SIGCHLD信号量
- double fork方式。父进程fork子进程A,子进程A fork 子子进程B,然后A退出,B由init托管,执行对应的程序,这样退出时init能够拿到退出状态。
- 调用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