system阻塞SIGCHLD信號原因

代碼1:APUE10.18節的system函數源代碼?
int system(const char *cmdstring) /* with appropriate signal handling */
{
? ? pid_t? ?? ?? ?? ?? ?pid;
? ? int? ?? ?? ?? ?? ???status;
? ? struct sigaction? ? ignore, saveintr, savequit;
? ? sigset_t? ?? ?? ?? ?chldmask, savemask;
? ? ……
? ? sigemptyset(&chldmask);? ?? ?? ?/* now block SIGCHLD */
? ? sigaddset(&chldmask, SIGCHLD);
? ? if (sigprocmask(SIG_BLOCK, &chldmask, &savemask)?
? ?? ???return(-1);
? ? if ((pid = fork())?
? ?? ???status = -1;? ? /* probably out of processes */
? ? } else if (pid == 0) {? ?? ?? ? /* child */
? ?? ???……
? ?? ???sigprocmask(SIG_SETMASK, &savemask, NULL);
? ?? ???execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
? ?? ???_exit(127);? ???/* exec error */
? ? } else {? ?? ?? ?? ?? ?? ?? ?? ?/* parent */
? ?? ? while (waitpid(pid, &status, 0)?
? ?? ?? ???if (errno != EINTR) {
? ?? ?? ?? ?? ?status = -1; /* error other than EINTR from waitpid() */
? ?? ?? ?? ?? ?break;
? ?? ?? ???}
? ? }
? ? ……
? ? if (sigprocmask(SIG_SETMASK, &savemask, NULL)?
? ?? ???return(-1);
? ? return(status);}
如果該函數的調用者在其SIGCHLD信號處理函數中調用了waitpid函數來獲得任意子進程的終止狀態,且在fork之前沒有阻塞SIGCHLD信號,則子進程結束,父進程執行SIGCHLD信號處理函數且得到了子進程的終止狀態。而該函數中的waitpid函數由于沒有得到其創建的子進程的終止狀態而出錯。
定義了一個函數,函數中fork了一個子進程,并且定義的函數要得到關于子進程的一些信息,例如子進程ID、子進程終止狀態等,而該函數的調用者所注冊的SIGCHLD信號處理函數會影響定義的這個函數獲取這些信息,因此為了避免該函數在獲取這些信息之前,由于子進程終止觸發SIGCHLD信號處理函數先執行,在fork之前都將SIGCHLD信號阻塞了,在函數獲取了相關信息后,才對SIGCHLD信號接觸阻塞。?
接下來我們以代碼1為例完整的解釋一下阻塞SIGCHLD信號的原因:
在一個進程終止或停止時,SIGCHLD信號被送給其父進程。按系統默認,將忽略此信號。如果父進程希望了解其子進程的這種狀態改變,則應捕捉此信號。信號捕捉函數中通常要調用wait函數以取得子進程ID和其終止狀態。
一般的,父進程在生成子進程之后會有兩種情況:一是父進程繼續去做別的事情;另一是父進程什么都不做,一直在wait子進程退出。
SIGCHLD信號就是為第一種情況準備的,它讓父進程去做別的事情,而只要父進程注冊了處理該信號的函數,在子進程退出時就會調用該函數,在函數中wait子進程得到終止狀態之后再繼續做父進程的事情。
我們先來寫一個function_model函數模型,如下所示:
int function_model(…) {? ? pid_t? ?? ?? ?? ?? ?pid;? ? int? ?? ?? ?? ?? ???status;? ? ……? ? if ((pid = fork())? ?? ?? ?status = -1;? ?? ???} else if (pid == 0) {? ?? ?? ? /* child */? ?? ? ……? ? } else {? ?? ?? ?? ?? ?? ?? ?? ?/* parent */? ?? ? while (waitpid(pid, &status, 0)? ?? ?? ?? ?if (errno != EINTR) {? ?? ?? ?? ?? ?status = -1;? ?? ?? ?? ?? ? break;? ?? ?? ???}? ? }? ?? ???……? ? return(status);}
當我們定義了一個具有function_model函數模型結構的函數時,應該在fork之前將SIGCHLD信號阻塞,fork之后,父子進程的SIGCHLD信號都是被阻塞的。子進程在進行任何的操作之前,應該將SIGCHLD恢復至原來的設置。父進程則應該在waitpid函數之后,將SIGCHLD恢復至原來的設置。
即這種類型的函數的正確的格式為:
int function_model(…)?
{
? ? pid_t? ?? ?? ?? ?? ?pid;
? ? int? ?? ?? ?? ?? ???status;
? ? sigset_t? ?? ?? ?? ?chldmask, savemask;
? ? ……
? ? sigemptyset(&chldmask);? ?? ?? ?/* now block SIGCHLD */
? ? sigaddset(&chldmask, SIGCHLD);
? ? if(sigprocmask(SIG_BLOCK, &chldmask, &savemask)?
? ?? ???return(-1);
? ? if ((pid = fork())?
? ?? ???status = -1;? ??
} else if (pid == 0) {? ?? ?? ? /* child */
sigprocmask(SIG_SETMASK, &savemask, NULL);
? ?? ? ……
? ? } else {? ?? ?? ?? ?? ?? ?? ?? ?/* parent */
? ?? ? while (waitpid(pid, &status, 0)?
? ?? ?? ???if (errno != EINTR) {
? ?? ?? ?? ?? ?status = -1;?
? ?? ?? ?? ?? ?break;
? ?? ?? ???}
? ? }
? ? ……
if(sigprocmask(SIG_SETMASK, &savemask, NULL)?
? ? return(-1);
? ? return(status);
}
父進程阻塞SIGCHLD信號的原因在于:如果function_model函數的調用者caller在調用function_model之前,注冊了SIGCHLD信號的處理函數sigchld_handler,且sigchld_handler函數中調用了waitpid函數等待任意子進程結束,如下所示:
void sigchld_handler(int signo)
{
? ? pid_t pid;
? ? int status;
? ? while((pid=waitpid(-1,&status,WNOHANG))>0)
? ? {
? ?? ???……
}
return ;
}
? ? 若我們沒有阻塞SIGCHLD信號,則當function_model函數中fork的子進程結束時,會向父進程發送SIGCHLD信號,則其信號處理函數sigchld_handler被調用,并在sigchld_handler函數中調用了waitpid得到了這個子進程的退出狀態,然后釋放掉其占用的進程表等其它資源。返回到function_model函數中繼續執行,由于其創建的子進程的退出狀態已經被獲取,內核不再保存其退出狀態,所以其中調用的waitpid函數由于無法得到其退出狀態而出錯,并將status設置為-1。
? ? 若我們阻塞了SIGCHLD信號,則當function_model函數中fork的子進程結束時,內核會向父進程發送SIGCHLD信號,但SIGCHLD信號被阻塞而不遞送給父進程,即處于未決狀態,因此function_model函數中的waitpid就能正確得到這個子進程的退出狀態。當解除對SIGCHLD信號的阻塞時,SIGCHLD信號會被遞交給父進程,引發父進程調用sigchld_handler函數,由于在function_model函數中以調用waitpid獲取了function_model函數創建的子進程的退出狀態,內核不再保存其終止信息,所以sigchld_handler中的waitpid也不會的到其退出狀態。
? ? 下面是Advanced Programming in the UNIX® Environment: Second Edition(APUE)中的解釋:
POSIX.1 states that if wait or waitpid returns the status of a child process while SIGCHLD is pending, then SIGCHLD should not be delivered to the process unless the status of another child process is also available. None of the four implementations discussed in this book implements this semantic. Instead, SIGCHLD remains pending after the system function calls waitpid; when the signal is unblocked, it is delivered to the caller. If we called wait in the sig_chld function in?
Figure 10.26
, it would return 1 with errno set to ECHILD, since the system function already retrieved the termination status of the child.