35-标准信号及其不可靠性
信号你会发了,也会捕了,但是还有好些个坑没填上。之前一直强调发送信号 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(-1, &status, WUNTRACED | WCONTINUED)) > 0) {
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, pid);
}
}
sleep(1);
}
void child(int n) {
if (n == 9) *((int*)0) = 0;
exit(n + 1);
}
int main() {
printf("I'm %d\n", getpid());
if (SIG_ERR == signal(SIGCHLD, waitchild)) {
perror("signal SIGSTOP");
}
int n = 10;
pid_t pid;
while(n--) {
pid = fork();
if (pid == 0) {
child(n);
}
else if (pid == -1) {
perror("fork");
}
}
while(1) {
write(STDOUT_FILENO, ".", 1);
sleep(10);
}
return 0;
}
$ gcc stdsig.c -o stdsig运行
$ ./stdsig1.1 结果分析
在我机器上运行的结果如下:
I'm 7699
.child 7706 exited! return code = 4
child 7700 exited! return code = 10
你会很惊讶的发现,你只处理了 2 个子进程发来的信号,还有 8 个信号去哪了?另外,再打开一个终端,执行 ps a
,你会发现有一堆僵尸在那等着吃掉你的脑子。
实际上,在信号处理函数 waitchild 中还没来得及结束(因为 sleep,对于CPU来说,1 秒简直比人类的 1 个世纪还要长),新的 SIGCHLD 信号又来了,而操作系统对此的操作是将其和前一个 SIGCHLD 信号合并。
1.2 改进方案弄清楚原因后,我们就知道在 waitpid 函数正在执行时,可能已经有多个子进程结束了。因此只需修改一处——将waitchild 的 if 判断改为 while 循环。即下面这样:
……
-if ((pid = waitpid(-1, &status, WUNTRACED | WCONTINUED)) > 0) { //+ while ((pid = waitpid(-1, &status, WUNTRACED | WCONTINUED)) > 0) {
……
重新编译运行,发现所有子进程正常回收。
2. 总结知道信号有可靠的和不可靠的理解标准信号的不可靠性指的是什么掌握异步回收子进程
版权声明
本文仅代表作者观点,不代表博信信息网立场。