35-标准信号及其不可靠性

来源:互联网 发布:sis第一网络会所地址 编辑:程序博客网 时间:2024/05/20 13:08

信号你会发了,也会捕了,但是还有好些个坑没填上。之前一直强调发送信号 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 continued\n\n", 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
  • 运行
$ ./stdsig

1.1 结果分析

在我机器上运行的结果如下:

I'm 7699.child 7706 exited! return code = 4child 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. 总结

  • 知道信号有可靠的和不可靠的
  • 理解标准信号的不可靠性指的是什么
  • 掌握异步回收子进程
0 0
原创粉丝点击