通过SIGCHLD信号回收子进程要用while循环而不能用if的原因

当子进程状态发生变化时,会向父进程发送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来回收子进程。

由于改动较少,就不给出代码和运行结果了。

发布了31 篇原创文章 · 获赞 4 · 访问量 954

猜你喜欢

转载自blog.csdn.net/qq_39781096/article/details/104362419