本文簡單介紹下Linux信號處理機制,為介紹二進制翻譯下信號處理機制做一個鋪墊。
本文主要參考書目《Linux內核源代碼情景分析》《獨辟蹊徑品內核:Linux內核源代碼導讀》
首先,先說一下什麼是信號。信號本質上是在軟件層次上對中斷機制的一種模擬,其主要有以下幾種來源:
在Linux下,可以通過以下命令查看系統所有的信號:
kill -l
可以通過類似下面的命令顯式的給一個進程發送一個信號:
kill -2 pid
上面的命令將2號信號發送給進程id為pid的進程。不存在編號為0的信號。
目前Linux支持64種信號。信號分為非實時信號(不可靠信號)和實時信號(可靠信號)兩種類型,對應於 Linux 的信號值為 1-31 和 34-64。信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什麼時候到達。本文著重於Linux的信號處理機制。
一般情況下一個進程接受到信號後,會有如下的行為:
進程對信號的響應
注冊信號處理函數
如果想要進程捕獲某個信號,然後作出相應的處理,就需要注冊信號處理函數。同中斷類似,內核也為每個進程准備了一個信號向量表,信號向量表中記錄著每個信號所對應的處理機制,默認情況下是調用默認處理機制。當進程為某個信號注冊了信號處理程序後,發生該信號時,內核就會調用注冊的函數。
注冊信號處理函數是通過系統調用signal()、sigaction()。其中signal()在可靠信號系統調用的基礎上實現, 是庫函數。它只有兩個參數,不支持信號傳遞信息,主要是用於前32種非實時信號的安裝;而sigaction()是較新的函數(由兩個系統調用實 現:sys_signal以及sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與 sigqueue() 系統調用配合使用,當然,sigaction()同樣支持非實時信號的安裝。sigaction()優於signal()主要體現在支持信號帶有參數。關於這方面的內容,如果想獲取更多,也可參考這裡。
進程如何發現和接受信號?
我們知道,信號是異步的,一個進程不可能等待信號的到來,也不知道信號會到來,那麼,進程是如何發現和接受信號呢?實際上,信號的接收不是由用戶進程來完成的,而是由內核代理。當一個進程P2向另一個進程P1發送信號後,內核接受到信號,並將其放在P1的信號隊列當中。當P1再次陷入內核態時,會檢查信號隊列,並根據相應的信號調取相應的信號處理函數。如下圖所示:
其中,動作c:發現和捕捉信號
信號檢測和響應時機
剛才我們說,當P1再次陷入內核時,會檢查信號隊列。那麼,P1什麼時候會再次陷入內核呢?陷入內核後在什麼時機會檢測信號隊列呢?
進入信號處理函數
發現信號後,根據信號向量,知道了處理函數,那麼該如何進入信號處理程序,又該如何返回呢?
我們知道,用戶進程提供的信號處理函數是在用戶態裡的,而我們發現信號,找到信號處理函數的時刻處於內核態中,所以我們需要從內核態跑到用戶態去執行信號處理程序,執行完畢後還要返回內核態。這個過程如下圖所示:
如圖中所見,處理信號的整個過程是這樣的:進程由於 系統調用或者中斷 進入內核,完成相應任務返回用戶空間的前夕,檢查信號隊列,如果有信號,則根據信號向量表找到信號處理函數,設置好“frame”後,跳到用戶態執行信號處理函數。信號處理函數執行完畢後,返回內核態,設置“frame”,再返回到用戶態繼續執行程序。
在上面這段話中,我提到“frame”,frame是什麼?那麼為什麼要設置frame?為什麼在執行完信號處理函數後還要返回內核態呢?