当子进程状态发生变化时,会向父进程发送SIGCHLD信号,所以当子进程终止时也会向父进程发送SIGCHLD信号。我们可以利用这一点对他进行回收,回收方法为向内核注册捕捉函数,在捕捉函数内部实现对子进程的回收,但是有些细节问题需要注意,我们看下例代码。
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<signal.h>
5 #include<wait.h>
6 void catch_wait(int signo) //捕捉函数负责回收子进程
7 {
8 pid_t pid;
9 int status;
10 if((pid = waitpid(0,&status,WNOHANG)) > 0)
11 {
12 if(WIFEXITED(status))
13 {
14 printf("-------------child %d exit %d\n",pid,WEXITSTATUS(status));
15 }
16 else if(WIFSIGNALED(status))
17 printf("-------------child %d eixt %d\n",pid,WTERMSIG(status));
18 }
19 }
20 int main(void)
21 {
22 pid_t pid;
23 int i;
24 for(i = 0;i < 10;i++)
25 {
26 pid = fork();
27 if(pid < 0)
28 {
29 perror("fork error");
30 exit(1);
31 }
32 else if(pid == 0)
33 {
34 break;
35 }
36 }
37 if(pid == 0)
38 {
39 printf("i'm child pid = %d\n",getpid());
40 sleep(1);
41 return i+1;
42 }
43 else if(pid > 0)
44 {
45 sigset_t set;
46 sigemptyset(&set);
47 sigaddset(&set,SIGCHLD);
48 sigprocmask(SIG_BLOCK,&set,NULL);//阻塞SIGCHLD信号,防止在父进程注册捕捉函数期间
49 //子进程死亡发送SIGCHLD信号,内核执行默认处理方式,无法回收子进程。
50 //注册SIGCHLD捕捉函数
51 signal(SIGCHLD,catch_wait);
52
53 sigprocmask(SIG_UNBLOCK,&set,NULL);//解除阻塞
54
55 while(1)
56 {
57 printf("i'm parend,pid = %d\n",getpid());
58 sleep(1);
59 }
60 }
61
62 return 0;
63 }
运行结果为:
按道理我们创建10个子进程,应当回收10个子进程,但是却只回收了部分进程,哪里出错了呢?
本来我们在父进程中通常使用while()循环回收子进程,在捕捉函数里面我们使用了if,因为只要子进程死亡就会向父进程发送一个SIGCHLD信号,内核就会调用捕捉函数回收该进程,这样每死一个子进程,都会回收一次,所以应当会将10个子进程全部回收,但是结果却显示只回收了部分信号。
这里的原因还是因为处理信号的机制,当一个信号抵达时,在没有被处理之前将未决信号集中该信号的值置为1(未决信号集采用位图的数据结构,即类型为unsigned long的数,共有64位,第n位表示编号为n的信号,其对应值表示信号是否未决),当该信号被内核处理后置为0。内核会扫描进程的未决信号集,为1时对该信号进行处理,正是因为采用这种简单的数据结构,才会造成上述代码的错误。
我们设想如下场景,当一个子进程死亡时,向父进程发送SIGCHLD信号,如果内核在处理SIGCHLD信号时,又有一个子进程死亡了,此时第二个死亡的子进程向父进程发送SIGCHLD信号,此时未决信号集中该信号的值已经是1了,内核还在处理第一个进程发送的信号,由于未决信号集采用的是位图,只能存0和1,这时就会丢失信号,就会发生只回收部分子进程的情况,所以我们还是需要用while循环来回收子进程,而不能用if来回收子进程。
由于改动较少,就不给出代码和运行结果了。