天天看點

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. 總結

  • 知道信号有可靠的和不可靠的
  • 了解标準信号的不可靠性指的是什麼
  • 掌握異步回收子程序

繼續閱讀