(1)SIGCHLD信號產生的條件
1.子進程終止時會向父進程發送SIGCHLD信號,告知父進程回收自己,但該信號的默認處理動作為忽略,因此父進程仍然不會去回收子進程,需要捕捉處理實現子進程的回收;
2.子進程接收到SIGSTOP(19)信號停止時;
3.子進程處在停止態,接受到SIGCONT后喚醒時。
綜上:子進程結束、接收到SIGSTOP停止(掛起)和接收到SIGCONT喚醒時都會向父進程發送SIGCHLD信號。父進程可以捕捉該信號,來實現對子進程的回收,或者了解子進程所處的狀態。
(2)借助SIGCHLD信號回收子進程
子進程結束運行,其父進程會收到SIGCHLD信號。該信號的默認處理動作是忽略。可以捕捉該信號,在捕捉函數中完成子進程狀態的回收。
//分析例子:結合 17)SIGCHLD 信號默認動作,掌握父使用捕捉函數回收子進程的方法
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>void sys_err(char *str)
{perror(str);exit(1);
}void do_sig_child(int signo)
{int status;pid_t pid;// if ((pid = waitpid(0, &status, WNOHANG)) > 0) {while ((pid = waitpid(0, &status, WNOHANG)) > 0) {if (WIFEXITED(status))printf("------------child %d exit with %d\n", pid, WEXITSTATUS(status));else if (WIFSIGNALED(status))printf("child %d killed by the %dth signal\n", pid, WTERMSIG(status));}
}int main(void)
{pid_t pid;int i;//阻塞SIGCHLDfor (i = 0; i < 10; i++) {if ((pid = fork()) == 0)break;else if (pid < 0)sys_err("fork");}if (pid == 0) { //10個子進程int n = 1;while (n--) {printf("child ID %d\n", getpid());sleep(1);}return i+1; //子進程結束狀態依次為1、2、??????、10} else if (pid > 0) { struct sigaction act;act.sa_handler = do_sig_child;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGCHLD, &act, NULL);//解除對SIGCHLD的阻塞while (1) {printf("Parent ID %d\n", getpid());sleep(1);}}return 0;
}
[root@localhost 01_signal_test]# ./sigchild
child ID 11129
child ID 11130
child ID 11131
child ID 11132
child ID 11133
child ID 11134
child ID 11135
child ID 11136
child ID 11137
Parent ID 11128? //表明在此時父進程肯定完成了對信號的注冊工作
child ID 11138
------------child 11129 exit with 1
------------child 11130 exit with 2
------------child 11132 exit with 4
------------child 11133 exit with 5
------------child 11131 exit with 3
Parent ID 11128
------------child 11134 exit with 6
------------child 11137 exit with 9
Parent ID 11128
------------child 11136 exit with 8
Parent ID 11128
------------child 11135 exit with 7
Parent ID 11128
------------child 11138 exit with 10 //可見父進程的確完成了對全部子進程的回收
Parent ID 11128
Parent ID 11128
分析如下:
1.在上述程序中,每個子進程在結束之前都會睡眠1s,這是為了留出足夠的時間保證在所有子進程結束之前,父進程能夠完成對SIGCHLD信號的注冊。否則所有子進程都結束了,父進程還沒有完成注冊,則此時信號都被忽略,從而子進程不能被父進程回收。但是,只要有一個子進程在信號注冊后結束,所有子進程都可以被回收,因為使用了while循環回收子進程。除了使用sleep函數來實現這一點外,其實更加高效而又精確的辦法(其實在負載較大的 情況下,sleep函數也不能確保能夠做到)就是在父進程注冊函數之前就將SIGCHLD信號設置為阻塞(通過信號集操作函數加入到屏蔽字中),在注冊完成時立即解除對該信號的阻塞即可。
2.不可以將捕捉函數內部的while替換為if。因為在執行捕捉函數期間,發送了多次SIGCHLD信號,未決信號集只是記錄了一次,因此下一次再調用捕捉函數時,if只能完成對一個子進程的回收(即使有多個子進程都發了信號,但是只是調用一次捕捉函數)。而while循環則可以對所有結束了的子進程都完成回收。因此對于多個子進程的回收,最好采用循環的方式,不采用if。
3.之前在管道進程間通信,父子進程實現ls | wc -l時(父進程exec函數族執行ls,子進程exec執行wc -l),父進程無法完成對子進程的回收,因為父進程執行exec函數族就離開了,不能回來。但是現在可以利用信號捕捉實現,在父進程調用exec函數族之前就注冊SIGCHLD的捕捉函數,但是需要注意以下幾點:1.仍然要考慮對SIGCHLD信號的屏蔽和解除屏蔽操作;2.要考慮子進程向父進程發送信號時,父進程已經結束了,此時父進程仍然無法對子進程回收,可以讓父進程睡眠等待,讓其后結束(但這在實際中是沒有意義的,父進程結束了,子進程變為孤兒進程,會被init進程回收,因此實際編程不需要考慮這一點);3.在ls | wc -l這個例子中,由于使用了標準輸出的重定向(dup2),因此用戶處理函數輸出的回收狀態信息不能顯示在屏幕上,因此在用戶處理函數中還需要再次進行重定向,恢復輸出重定向為屏幕。
總結:
1.子進程繼承了父進程的信號屏蔽字和信號處理動作,但子進程沒有繼承未決信號集spending。
2.注意注冊信號捕捉函數的位置。
3.應該在fork之前,阻塞SIGCHLD信號。注冊完捕捉函數后解除阻塞。