1. 信號的基本概念

為了理解信號,先從我們最熟悉的場景說起:

  1. 用戶輸入命令,在Shell下啟動一個前台進程。

  2. 用戶按下Ctrl-C,這個鍵盤輸入產生一個硬件中斷。

  3. 如果CPU當前正在執行這個進程的代碼,則該進程的用戶空間代碼暫停執行,CPU從用戶態切換到內核態處理硬件中斷。

  4. 終端驅動程式將Ctrl-C解釋成一個SIGINT信號,記在該進程的PCB中(也可以說發送了一個SIGINT信號給該進程)。

  5. 當某個時刻要從內核返回到該進程的用戶空間代碼繼續執行之前,首先處理PCB中記錄的信號,發現有一個SIGINT信號待處理,而這個信號的預設處理動作是終止進程,所以直接終止進程而不再返回它的用戶空間代碼執行。

注意,Ctrl-C產生的信號只能發給前台進程。在第 3.3 節 “wait和waitpid函數”中我們看到一個命令後面加個&可以放到後台運行,這樣Shell不必等待進程結束就可以接受新的命令,啟動新的進程。Shell可以同時運行一個前台進程和任意多個後台進程,只有前台進程才能接到像Ctrl-C這種修飾鍵產生的信號。前台進程在運行過程中用戶隨時可能按下Ctrl-C而產生一個信號,也就是說該進程的用戶空間代碼執行到任何地方都有可能收到SIGINT信號而終止,所以信號相對於進程的控制流程來說是非同步(Asynchronous)的。

kill -l命令可以察看系統定義的信號列表:

$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE
 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGSTKFLT
17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU
25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH
29) SIGIO       30) SIGPWR      31) SIGSYS      34) SIGRTMIN
35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4
...

每個信號都有一個編號和一個宏定義名稱,這些宏定義可以在signal.h中找到,例如其中有定義#define SIGINT 2。編號34以上的是實時信號,本章只討論編號34以下的信號,不討論實時信號。這些信號各自在什麼條件下產生,預設的處理動作是什麼,在signal(7)中都有詳細說明:

Signal     Value     Action   Comment
-------------------------------------------------------------------------
SIGHUP        1       Term    Hangup detected on controlling terminal
                              or death of controlling process
SIGINT        2       Term    Interrupt from keyboard
SIGQUIT       3       Core    Quit from keyboard
SIGILL        4       Core    Illegal Instruction
...

上表中第一列是各信號的宏定義名稱,第二列是各信號的編號,第三列是預設處理動作,Term表示終止當前進程,Core表示終止當前進程並且Core Dump(下一節詳細介紹什麼是Core Dump),Ign表示忽略該信號,Stop表示停止當前進程,Cont表示繼續執行先前停止的進程,表中最後一列是簡要介紹,說明什麼條件下產生該信號。

產生信號的條件主要有:

如果不想按預設動作處理信號,用戶程序可以調用sigaction(2)函數告訴內核如何處理某種信號(sigaction函數稍後詳細介紹),可選的處理動作有以下三種:

  1. 忽略此信號。

  2. 執行該信號的預設處理動作。

  3. 提供一個信號處理函數,要求內核在處理該信號時切換到用戶態執行這個處理函數,這種方式稱為捕捉(Catch)一個信號。