為了理解信號,先從我們最熟悉的場景說起:
用戶輸入命令,在Shell下啟動一個前台進程。
用戶按下Ctrl-C,這個鍵盤輸入產生一個硬件中斷。
如果CPU當前正在執行這個進程的代碼,則該進程的用戶空間代碼暫停執行,CPU從用戶態切換到內核態處理硬件中斷。
終端驅動程式將Ctrl-C解釋成一個SIGINT
信號,記在該進程的PCB中(也可以說發送了一個SIGINT
信號給該進程)。
當某個時刻要從內核返回到該進程的用戶空間代碼繼續執行之前,首先處理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
表示繼續執行先前停止的進程,表中最後一列是簡要介紹,說明什麼條件下產生該信號。
產生信號的條件主要有:
用戶在終端按下某些鍵時,終端驅動程式會發送信號給前台進程,例如Ctrl-C產生SIGINT
信號,Ctrl-\產生SIGQUIT
信號,Ctrl-Z產生SIGTSTP
信號(可使前台進程停止,這個信號將在第 34 章 終端、作業控制與守護進程詳細解釋)。
硬件異常產生信號,這些條件由硬件檢測到並通知內核,然後內核向當前進程發送適當的信號。例如當前進程執行了除以0的指令,CPU的運算單元會產生異常,內核將這個異常解釋為SIGFPE
信號發送給進程。再比如當前進程訪問了非法內存地址,,MMU會產生異常,內核將這個異常解釋為SIGSEGV
信號發送給進程。
一個進程調用kill(2)
函數可以發送信號給另一個進程。
可以用kill(1)
命令發送信號給某個進程,kill(1)
命令也是調用kill(2)
函數實現的,如果不明確指定信號則發送SIGTERM
信號,該信號的預設處理動作是終止進程。
當內核檢測到某種軟件條件發生時也可以通過信號通知進程,例如閙鐘超時產生SIGALRM
信號,向讀端已關閉的管道寫數據時產生SIGPIPE
信號。
如果不想按預設動作處理信號,用戶程序可以調用sigaction(2)
函數告訴內核如何處理某種信號(sigaction
函數稍後詳細介紹),可選的處理動作有以下三種: