天天看点

35-标准信号及其不可靠性1. 不可靠是什么样子1.2 改进方案2. 总结

信号你会发了,也会捕了,但是还有好些个坑没填上。之前一直强调发送信号 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. 总结

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

继续阅读