通過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來回收子進程。

由於改動較少,就不給出代碼和運行結果了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章