Linux核心訊號處理機制介紹

2021-06-28 10:40:37 字數 3243 閱讀 5016

本文簡單介紹下linux訊號處理機制,為介紹二進位制翻譯下訊號處理機制做乙個鋪墊。

本文主要參考書目《linux核心源**情景分析》

程式錯誤:除零,非法記憶體訪問…

外部訊號:終端ctrl-c產生sgint訊號,定時器到期產生sigalrm…

顯式請求:kill函式允許程序傳送任何訊號給其他程序或程序組。

在linux下,可以通過以下命令檢視系統所有的訊號:

kill-l
可以通過類似下面的命令顯式的給乙個程序傳送乙個訊號:

kill-2 pid
上面的命令將2號訊號傳送給程序id為pid的程序。不存在編號為0的訊號。

目前linux支援64種訊號。訊號分為非實時訊號(不可靠訊號)和實時訊號(可靠訊號)兩種型別,對應於 linux 的訊號值為 1-31 和 34-64。訊號是非同步的,乙個程序不必通過任何操作來等待訊號的到達,事實上,程序也不知道訊號到底什麼時候到達。本文著重於linux的訊號處理機制,對訊號更多的介紹可以參考這裡。

一般情況下乙個程序接受到訊號後,會有如下的行為:

程序對訊號的響應

忽略訊號:大部分訊號可被忽略,除sigstop和sigkill訊號外(這是超級使用者殺掉或停掉任意程序的手段)。

捕獲訊號:註冊訊號處理函式,它對產生的特定訊號做處理。

讓訊號預設動作起作用:unix核心定義的預設動作,有5種情況:

註冊訊號處理函式

如果想要程序捕獲某個訊號,然後作出相應的處理,就需要註冊訊號處理函式。同中斷類似,核心也為每個程序準備了乙個訊號向量表,訊號向量表中記錄著每個訊號所對應的處理機制,預設情況下是呼叫預設處理機制。當程序為某個訊號註冊了訊號處理程式後,發生該訊號時,核心就會呼叫註冊的函式。

註冊訊號處理函式是通過系統呼叫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?為什麼在執行完訊號處理函式後還要返回核心態呢?

什麼叫frame?

在呼叫乙個子程式時,堆疊要往下(邏輯意義上是往上)伸展,這是因為需要在堆疊中儲存子程式的返回位址,還因為子程式往往有區域性變數,也要占用堆疊中的空間。此外,呼叫子程式時的引數也是在堆疊中。子程式呼叫巢狀越深,則堆疊伸展的層次也越多。在堆疊中的每乙個這樣的層次,就稱為乙個」框架」,即frame。

一般來說,當子程式和呼叫它的程式在同一空間中時,堆疊的伸展,也就是堆疊中框架的建立,過程主要如下:

call指令將返回位址壓入堆疊(自動)

用push指令壓入呼叫引數

調整堆疊指標來分配區域性變數

為什麼以及怎麼設定frame?

我們知道,當程序陷入核心態的時候,會在堆疊中儲存中斷現場。因為使用者態和核心態是兩個執行級別,所以要使用兩個不同的棧。當使用者程序通過系統呼叫剛進入核心的時候,cpu會自動在該程序的核心棧上壓入下圖所示的內容:(圖來自《linux核心完全注釋》)

在處理完系統呼叫以後,就要呼叫do_signal()函式進行設定frame等工作。這時核心堆疊的狀態應該跟下圖左半部分類似(系統呼叫將一些資訊壓入棧了):

在找到了訊號處理函式之後,do_signal函式首先把核心堆疊中存放返回執行點的eip儲存為old_eip,然後將eip替換為訊號處理函式的位址,然後將核心中儲存的「原esp」(即使用者態棧位址)減去一定的值,目的是擴大使用者態的棧,然後將核心棧上的內容儲存到使用者棧上,這個過程就是設定frame.值得注意的是下面兩點:

之所以把eip的值設定成訊號處理函式的位址,是因為一旦程序返回使用者態,就要去執行訊號處理程式,所以eip要指向訊號處理程式而不是原來應該執行的位址。

之所以要把frame從核心棧拷貝到使用者棧,是因為程序從核心態返回使用者態會清理這次呼叫所用到的核心棧(類似函式呼叫),核心棧又太小,不能單純的在棧上儲存另乙個frame(想象一下巢狀訊號處理),而我們需要eax(系統呼叫返回值)、eip這些資訊以便執行完訊號處理函式後能繼續執行程式,所以把它們拷貝到使用者態棧以儲存起來。

以上這些搞清楚之後,下面的事情就順利多了。這時程序返回使用者空間,就會根據核心棧中的eip值執行訊號處理函式。那麼,訊號處理程式執行完後,怎麼返回程式繼續執行呢?

訊號處理函式執行完後怎麼辦?

訊號處理程式執行完畢之後,程序會主動呼叫sigreturn()系統呼叫再次回到核心,檢視有沒有其他訊號需要處理,如果沒有,這時核心就會做一些善後工作,將之前儲存的frame恢復到核心棧,恢復eip的值為old_eip,然後返回使用者空間,程式就能夠繼續執行。至此,核心遍完成了一次(或幾次)訊號處理工作。

Linux核心訊號處理機制介紹

on october 9,2010,in linux,linux核心,by sponge 本文簡單介紹下linux訊號處理機制,為介紹二進位制翻譯下訊號處理機制做乙個鋪墊。本文主要參考書目 linux核心源 情景分析 程式錯誤 除零,非法記憶體訪問 外部訊號 終端ctrl c產生sgint訊號,定時...

Linux訊號處理機制

程式錯誤 除零,非法記憶體訪問 外部訊號 終端ctrl c產生sgint訊號,定時器到期產生sigalrm 顯式請求 kill函式允許程序傳送任何訊號給其他程序或程序組。目前linux支援64種訊號。訊號分為非實時訊號 不可靠訊號 和實時訊號 可靠訊號 兩種型別,對應於 linux 的訊號值為 1 ...

Linux訊號處理機制(二) 阻塞訊號

訊號在核心中一般有三種狀態 1 訊號遞達 delivery 實際執行訊號的處理動作稱為訊號遞達 2 訊號未決 pending 訊號從產生到遞達之間的狀態 3 訊號阻塞 block 被阻塞的訊號產生時將保持在未決狀態,直到程序解除對此訊號的阻塞,才執行遞達的動作 注意 阻塞與忽略是不同的,只要訊號被阻...