信号你會發了,也會捕了,但是還有好些個坑沒填上。之前一直強調發送信号 1 - 31 号,實際上,還有 32-64 号信号。為什麼不發 32-64 号信号,是因為32-64号和前面的 1- 31 号不屬于一個範疇。
1-31号,被規定為 standard signals,也就是标準信号。32-64号信号,被規定為 real-time signals,也就是實時信号。目前我們隻關心标準信号,而不關心實時信号。
需要特别強調的是,标準信号是不可靠的,不可靠的意思是如果同時來了很多相同的信号,而且還沒來得及處理,這些相同的信号就會被合并成一個信号。實時信号就沒有這個問題,隻要來一次,就會處理一次。
下面以執行個體來講解标準信号到底是有多麼的不可靠。另外,這個執行個體會使用異步的方式來 wait 狀态發生改變的子程序(比如退出,停止,發生段錯誤等等),我們再也不用在主函數裡去 wait 子程序回收僵屍程序了。
1. 不可靠是什麼樣子
為了能夠很快說明問題,請複制後面的代碼編譯運作。
這段代碼的功能:main 函數生成 10 個子程序,每個子程序一生出就直接退出,隻有一個子程序通路非法記憶體不正常退出。最後 main 函數每隔 10 秒在螢幕打點。
除此之外,這段程式注冊了 SIGCHLD 信号處理函數。當有子程序狀态發生改變時,會執行信号處理函數。信号處理函數主要就是 wait 子程序,并列印子程序的退出碼或者列印子程序被何種信号終止或停止。最後信号處理函數會 sleep 1 秒鐘。
- 代碼
// stdsig.c
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/types.h>
void waitchild(int sig) {
int status;
pid_t pid;
if ((pid = waitpid(-, &status, WUNTRACED | WCONTINUED)) > ) {
if (WIFEXITED(status)) {
printf("child %d exited! return code = %d\n\n", pid, WEXITSTATUS(status));
}
else if (WIFSIGNALED(status)) {
printf("child %d terminated by signal %d\n\n", pid, WTERMSIG(status));
}
else if (WIFSTOPPED(status)) {
printf("child %d stopped by signal %d\n\n", pid, WSTOPSIG(status));
}
else if (WIFCONTINUED(status)) {
printf("child %d continued\n\n", pid);
}
}
sleep();
}
void child(int n) {
if (n == ) *((int*)) = ;
exit(n + );
}
int main() {
printf("I'm %d\n", getpid());
if (SIG_ERR == signal(SIGCHLD, waitchild)) {
perror("signal SIGSTOP");
}
int n = ;
pid_t pid;
while(n--) {
pid = fork();
if (pid == ) {
child(n);
}
else if (pid == -) {
perror("fork");
}
}
while() {
write(STDOUT_FILENO, ".", );
sleep();
}
return ;
}
- 編譯
$ gcc stdsig.c -o stdsig
- 運作
$ ./stdsig
1.1 結果分析
在我機器上運作的結果如下:
I'm 7699
.child exited! return code =
child exited! return code =
..............
你會很驚訝的發現,你隻處理了 2 個子程序發來的信号,還有 8 個信号去哪了?另外,再打開一個終端,執行
ps a
,你會發現有一堆僵屍在那等着吃掉你的腦子。
實際上,在信号處理函數 waitchild 中還沒來得及結束(因為 sleep,對于CPU來說,1 秒簡直比人類的 1 個世紀還要長),新的 SIGCHLD 信号又來了,而作業系統對此的操作是将其和前一個 SIGCHLD 信号合并。
1.2 改進方案
弄清楚原因後,我們就知道在 waitpid 函數正在執行時,可能已經有多個子程序結束了。是以隻需修改一處——将waitchild 的 if 判斷改為 while 循環。即下面這樣:
……
- if ((pid = waitpid(-, &status, WUNTRACED | WCONTINUED)) > ) { // 将這一行改成下面那一行
+ while ((pid = waitpid(-1, &status, WUNTRACED | WCONTINUED)) > 0) {
……
重新編譯運作,發現所有子程序正常回收。
2. 總結
- 知道信号有可靠的和不可靠的
- 了解标準信号的不可靠性指的是什麼
- 掌握異步回收子程序