Linux下的SIGCHLD訊號
- 2020 年 3 月 9 日
- 筆記
對於父子進程來說,父進程可以通過wait或這waitpid函數來釋放子進程的資源,也可以通過waitpid獲得子進程的退出狀態。那麼這個實現過程是通過阻塞等待,或者不斷的輪詢。那麼這兩個操作對於父進程來說都不是理想的,因此可以通過SIGCHLD訊號來實現非同步的操作。也就是當子進程結束的時候通過SIGCHLD訊號告訴父進程,然後父進程再去釋放其資源,如果沒有收到該訊號也不影響父進程的運行。
那麼對於SIGCHLD訊號來說,只有在以下三個條件中才會向父進程發送SIGCHLD訊號:
1. 子進程終止時 2. 子進程接收到SIGSTOP訊號停止時 3. 子進程處在停止態,接受到SIGCONT後喚醒時
下面我們通過示例來進一步詳細說明,我們實現一個父進程來創建10個子進程,然後通過捕捉訊號來實現上述所說的功能。首先我們需要考慮,當我們創建子進程的時候,如果父進程還沒有定義捕捉函數子進程就結束了,那麼這個子進程就變為了殭屍進程,所以在定義捕捉函數之前需要先將SIGCHLD訊號進行阻塞,在定義捕捉函數後再去UNBLOCK,就可以捕捉到子進程的訊號了。還有需要注意的是因為SIGCHLD不能疊加,所以如果多個子進程結束了也只有一個SIGCHLD,那麼對於這種情況我們在捕捉函數中使用循環來處理多個子進程結束的情況,下面就直接看程式碼吧:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <signal.h> #include <unistd.h> void sys_err(char *str){ perror(str); exit(1); } void sig_handler(int num){ int status; pid_t pid; /* 由於該訊號不能疊加,所以可能同時有多個子進程已經結束 所以循環wait */ while((pid = waitpid(0, &status, WNOHANG)) > 0){ if (WIFEXITED(status)) // 判斷子進程的退出狀態 是否是正常退出 printf("-----child %d exit with %dn", pid, WEXITSTATUS(status)); else if (WIFSIGNALED(status)) // 判斷子進程是否是 通過訊號退出 printf("child %d killed by the %dth signaln", pid, WTERMSIG(status)); } } int main(void) { pid_t pid; int i; /* 首先我們先將SIGCHLD訊號阻塞 保證在子進程結束前設置父進程的捕捉函數 */ sigset_t nmask, omask; sigemptyset(&nmask); sigaddset(&nmask, SIGCHLD); sigprocmask(SIG_BLOCK, &nmask, &omask); for(i=0;i<10;i++){ pid = fork(); if(pid == 0) break; if(pid == -1) sys_err("fork"); } /* 子進程中循環10s */ if(pid == 0){ for(int i=0;i<10;i++){ printf("This is son pid %dn", getpid()); sleep(1); } return i + 1; } else if(pid > 0){ /* 父進程首先要設置好捕捉函數 */ struct sigaction sig; sig.sa_handler = sig_handler; sigemptyset(&sig.sa_mask); sig.sa_flags = 0; sigaction(SIGCHLD, &sig, NULL); /* 然後再unblock */ sigdelset(&omask, SIGCHLD); sigprocmask(SIG_SETMASK, &omask, NULL); while(1){ printf("Parent pid %dn", getpid()); sleep(1); } } return 0; }
運行結果如下:
