進程在正常結束以前,不可能無休止地運行下去.進程在運行的時候,系統中總會發生這樣或那樣的事件.內核必須響應這些事件,這樣,進程的執行就會被打斷.
一.從那些干擾程序執行的事件說起
有些事件產生自硬件,比如時鐘或I/O設備.這樣的事件通常與當前正在執行的進程沒有什麼關係,不會對進程的執行流造成任何影響.於是,進程根本意識不到自己曾被這種事件打斷過.我們通常將這樣的事件叫做中斷.
有些事件來自進程自身,這通常意味着進程做了些不該做的事,比如除0.當這樣的事情發生時,內核必須打斷正在執行的進程,並進行相應的處理.這樣的事件幾乎一定會改變進程的執行流.我們通常把這樣的事件叫做異常.
還有些事件既不是從硬件產生,也不是從進程自身產生.這樣的事件產生自其他進程.Linux允許一個進程向另一個進程發送一個消息,以便進程之間可以相互”通信”.於是,爲了響應消息,收到消息的進程通常又會被打斷.我們把這樣的事件叫做信號.
現實更加複雜.事實上,異常事件發生以後,內核不會強勢介入進程的執行流,而是用了一種更加”文雅”的方式:向進程發送一個信號,雖然在大多數情況下這個信號會殺死進程.
二.信號
這樣,我們知道,信號是被髮送給進程的一條消息,收到信號的進程通常會對信號作出響應,從而改變自己的執行流.
並且,我們還可以總結出信號的兩個作用:
- 在用戶態, 信號被用作進程間通信;
- 在內核態,信號被內核用來通知進程已發生的事件.
我們需要先定義一些與信號處理相關的術語,並明確它們的語義.
三.信號的三個狀態:產生,傳遞,未決
首先,我們來定義信號的三個狀態.值得注意的是,信號的三個狀態存在於理論之中,內核不會記錄信號的狀態.
- 產生
當一個導致信號生成的事件發生時,內核會根據需要更新一個或多個進程描述符.從事件發生到進程描述符更新完畢,這個過程叫做信號產生.下面這些事件都可能引起信號產生:
- 硬件異常(如除0)
- 軟件條件(如定時器超時)
- 終端操作
- kill()函數調用
- 傳遞
內核強迫目標進程對信號作出反應.這個過程叫做信號傳遞.內核強迫目標進程相應信號的方式有:
- 改變目標進程的執行狀態
- 執行特定的信號處理程序
- 既改變目標進程的執行狀態,又執行特定的信號處理程序
- 未決
產生的信號不會立即被傳遞.當信號已產生但是沒有被傳遞時,信號處於未決狀態.
四.信號的傳遞方式:忽略,默認,捕捉
其次, 我們看內核傳遞信號的三種方式.與信號狀態不同的是,內核對信號的處理動作明確記錄在進程描述符的sighand->action[sig].sa_handler字段中.
- 顯式忽略: SIG_IGN
- 默認操作: SIG_DFL
默認操作與信號相關,有以下三種:
- Terminate: 終止進程
- Dump: 終止進程並轉儲其執行上下文
- Ignore: 忽略信號
- Stop: 停止進程
- Continue: 若進程被停止,就將它設爲運行態
- 捕獲: handler_address
至此,事情好像已經完美了: 信號產生以後,經歷未決狀態,最後被內核根據註冊在進程描述符中的動作進行傳遞.然而,信號並不總是能夠被順利傳遞,因爲內核可能阻塞信號.
五.信號的阻塞
- 阻塞
信號註冊在以下兩個地方時,信號被阻塞.被阻塞的信號處在信號未決狀態,當它被解除阻塞以後,它纔有機會被傳遞.
- blocked
- sighand->action[sig]->sa_mask