#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到底是一个什么关系呢?
在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函数只会被调用一次,那么是不是就只有一个子进程会回收另外一个不会回收成为僵尸进程?答案是:否