我们知道一个子进程在退出的时候会给其父进程发送一个SIGCHILD信号以告诉父进程"我已经退出了",在父进程中为了避免僵尸进程一般都会在SIGCHILD信号处理函数中调用waitpid or wait来回收子进程的退出状态。示例如下:
- #include<stdio.h>
- #include<stdlib.h>
- #include<signal.h>
- #include<string.h>
- #include<errno.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <sys/wait.h>
- static void sig_handler(int );
- int main(int argc, char *argv[])
- {
- pid_t pid;
- if(signal(SIGCHLD, sig_handler) == SIG_ERR)
- {
- fprintf(stderr, "signal error : %s\n", strerror(errno));
- return 1;
- }
- if((pid = fork()) < 0)
- {
- fprintf(stderr, "fork error : %s\n", strerror(errno));
- return 1;
- }else if(pid == 0)
- {
- sleep(1);
- printf("I am child, ppid is : %d\n", getppid());
- exit(0);
- }else
- {
- printf("I am parent, child pid is : %d\n", pid);
- sleep(5);
- }
- return 0;
- }
- static void sig_handler(int signum)
- {
- int saveerr = errno;
- if(waitpid(-1, NULL, 0) < 0)
- {
- fprintf(stderr, "waitpid error : %s\n", strerror(errno));
- errno = saveerr;
- return;
- }
- printf("child is exit.\n");
- errno = saveerr;
- }
- SIGCHILD只是在子进程退出的时候发送给父进程的一个信号值,这是一种异步通知父进程的方式.父进程可以捕获,忽略这个信号,忽略这个信号也是避免僵尸进程的一种方式.
- waitpid or wait回收子进程的结束状态,避免子进程进入僵尸状态.
- 主进程可以直接调用waitpid or wait来回收子进程的结束状态,不一定非得通过SIGCHILD信号处理函数,也就是说waitpid or wait不是依靠SIGCHLD信号是否到达来判断子进程是否结束.但是如果主进程除了回收子进程状态以外还有其他的业务需要处理那么最好是通过SIGCHILD信号处理函数来调用waitpid or wait,因为这是异步的操作.
- 如果注册了SIGCHLD信号处理函数,那么就需要等待SIGCHLD信号的到达并且完成信号处理函数,waitpid or wait才能接受到子进程的的退出状态.
在system实现中会调用waitpid来回收子进程的状态,首先想到的一点是:阻塞SIGCHLD是为了避免主进程已经注册的SIGCHLD处理函数回收所有的子进程状态,那么在system中的waitpid调用会导致ECHILD(No child processes)的错误.为了证实自己的想法是否正确在网上查了一下,最后发现还跟第4点有关系,因为如果不阻塞SIGCHLD信号并且主进程注册了SIGCHLD信号处理函数,那么就需要等主进程的信号处理函数返回waitpid才能接受到子进程的退出状态,也就是如果信号处理函数需要1min才能处理完那么system也需要1min才能返回.所以在调用system函数的时候阻塞SIGCHLD,这样在执行期间信号被阻塞就不会调用信号处理函数了,system中的waitpid就能"及时"的获取到子进程的状态,然后"及时"退出.
- #include<stdlib.h>
- #include<unistd.h>
- #include<signal.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include<string.h>
- #include<errno.h>
- void sig_handler(int signo)
- {
- printf("receve sig : %d\n", signo);
- sleep(3);
- }
- int main(void)
- {
- pid_t pid;
- /*sigset_t mask, savemask;*/
- /*sigemptyset(&mask);*/
- /*sigaddset(&mask, SIGCHLD);*/
- /*if(sigprocmask(SIG_BLOCK, &mask, &savemask) < 0)*/
- /*{*/
- /*fprintf(stderr, "sigprocmask error : %s\n", strerror(errno));*/
- /*}*/
- signal(SIGCHLD, sig_handler);
- if((pid = fork()) < 0)
- {
- fprintf(stderr, "fork error : %s\n", strerror(errno));
- return 1;
- }else if(pid == 0)
- {
- /*if(sigprocmask(SIG_SETMASK, &savemask, NULL) < 0)*/
- /*{*/
- /*fprintf(stderr, "sigprocmask error : %s\n", strerror(errno));*/
- /*}*/
- sleep(1);
- printf("I am child.\n");
- exit(1);
- }else
- {
- fprintf(stderr, "wait child exit.\n");
- if(waitpid(pid, NULL, 0) < 0)
- {
- fprintf(stderr, "waitpid error : %s\n", strerror(errno));
- }
- fprintf(stderr, "child exit.\n");
- /*if(sigprocmask(SIG_SETMASK, &savemask, NULL) < 0)*/
- /*{*/
- /*fprintf(stderr, "sigprocmask error : %s\n", strerror(errno));*/
- /*}*/
- }
- return 0;
- }
上面的示例中"child exit"的输出要等sig_handler处理完才会输出,如果把注释的语句去掉并注释signal(SIGCHLD, sig_handler);那么"child exit"在子进程退出以后马上就会输出.
下面再考虑一下这个场景:有一个主进程会不断的生成子进程,为了避免僵尸进程,在主进程中注册了SIGCHLD信号,并且在信号处理函数中根据如下代码来回收子进程的退出状态,这段代码是否正确?是否能完全避免僵尸进程?
- void sig_handler(int signo)
- {
- while(waitpid(-1, NULL, 0) > 0);
- }
我们知道linux下的SIGCHLD是不排队的信号,也就是说如果有两个SIGCHLD信号同时到达那么sig_handler函数只会被调用一次,那么是不是就只有一个子进程会回收另外一个不会回收成为僵尸进程?答案是:否
- 前面已经提到了,子进程的状态回收和SIGCHLD信号没有关系,无论是否收到子进程的SIGCHLD信号waitpid都可以回收已经结束的子进程状态.
- 即使有信号的丢失,只要sig_handler函数调用一次就能回收所有已经结束的子进程状态.所以丢失多少个SIGCHLD信号都没有关系,只要最后调用了sig_handler一次就可以回收所有的状态。
- 文章最后提到的避免僵尸进程的方法是有问题的:
-
waitpid成功为一个子进程收了尸后,如果后续没有僵尸进程了,那waitpid岂不是一直阻塞在那里?
正确的方法:
while ((pid = waitpid((pid_t)-1, &exit_status, WNOHANG)) > 0) {
/* do nothing */
}-
Re:
无法更新 2015-03-19 13:48发表
-
- 回复astrotycoon:嗯,你这里说的是对的,但是我前面提到了这个示例的场景:不断有子进程生成。
-
-
2楼
astrotycoon 2014-11-06 11:09发表
-
-
阻塞SIGCHLD是为了避免主进程已经注册的SIGCHLD处理函数回收所有的子进程状态,那么在system中的waitpid调用会导致ECHILD(No child processes)的错误
关于这点,我的结果是和你相反的,即SIGCHLD信号处理函数中的wait系列函数会返回-1,errno设置成ECHLD,而system中的waitpid正确获取了子进程的退出状态。所以,只能说:在没有阻塞SIGCHLD的情况下,system函数是有可能获取不到子进程退出状态的。-
Re:
无法更新 2015-03-19 14:00发表
-
- 回复astrotycoon:嗯,我文章中提到的那个只是一个猜测,看了你的文章以后明白了。
-
-
1楼
astrotycoon 2014-11-06 11:03发表
-
- 阐述的很清晰,谢谢