Linux訊號處理機制

2021-08-07 17:13:27 字數 2546 閱讀 9465

**:

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

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

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

目前linux支援64種訊號。訊號分為非實時訊號(不可靠訊號)和實時訊號(可靠訊號)兩種型別,對應於 linux 的訊號值為 1-31 和 34-64。訊號是非同步的,乙個程序不必通過任何操作來等待訊號的到達,事實上,程序也不知道訊號到底什麼時候到達。

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

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

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

a) 流產abort:終止程序並產生core檔案。

b) 終止stop:終止程序但不生成core檔案。

c) 忽略:忽略訊號。

d) 掛起suspend:掛起程序。

e) 繼續continue:若程序是掛起的,則resume程序,否則忽略此訊號。

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

訊號是非同步的,當乙個程序p2向另乙個程序p1傳送訊號後,核心接受到訊號,並將其放在p1的訊號佇列當中。當p1再次陷入核心態時,會檢查訊號佇列,並根據相應的訊號調取相應的訊號處理函式。

剛才我們說,當p1再次陷入核心時,會檢查訊號佇列。那麼,p1什麼時候會再次陷入核心呢?陷入核心後在什麼時機會檢測訊號佇列呢?

當前程序由於系統呼叫、中斷或異常而進入系統空間以後,從系統空間返回到使用者空間的前夕。

當前程序在核心中進入睡眠以後剛被喚醒的時候(必定是在系統呼叫中),或者由於不可忽略訊號的存在而提前返回到使用者空間。

發現訊號後,根據訊號向量,知道了處理函式,那麼該如何進入訊號處理程式,又該如何返回呢?

我們知道,使用者程序提供的訊號處理函式是在使用者態裡的,而我們發現訊號,找到訊號處理函式的時刻處於核心態中,所以我們需要從核心態跑到使用者態去執行訊號處理程式,執行完畢後還要返回核心態。這個過程如下圖所示:

如圖中所見,處理訊號的整個過程是這樣的:程序由於 系統呼叫或者中斷 進入核心,完成相應任務返回使用者空間的前夕,檢查訊號佇列,如果有訊號,則根據訊號向量表找到訊號處理函式,設定好「frame」後,跳到使用者態執行訊號處理函式。訊號處理函式執行完畢後,返回核心態,設定「frame」,再返回到使用者態繼續執行程式。

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

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

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

用push指令壓入呼叫引數

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

我們知道,當程序陷入核心態的時候,會在堆疊中儲存中斷現場。因為使用者態和核心態是兩個執行級別,所以要使用兩個不同的棧。當使用者程序通過系統呼叫剛進入核心的時候,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訊號處理機制(二) 阻塞訊號

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

LINUX訊號處理機制的原理

訊號處理機制的原理 核心給乙個程序傳送軟中斷訊號的方法,是在程序所在的程序表項的訊號域設定對應於該訊號的位。這裡要補充的是,如果訊號傳送給乙個正在睡眠的程序,那麼要 看該程序進入睡眠的優先順序,如果程序睡眠在可被中斷的優先順序上,則喚醒程序 否則僅設定程序表中訊號域相應的位,而不喚醒程序。這一點比較...

Linux核心訊號處理機制介紹

本文簡單介紹下linux訊號處理機制,為介紹二進位制翻譯下訊號處理機制做乙個鋪墊。本文主要參考書目 linux核心源 情景分析 程式錯誤 除零,非法記憶體訪問 外部訊號 終端ctrl c產生sgint訊號,定時器到期產生sigalrm 顯式請求 kill函式允許程序傳送任何訊號給其他程序或程序組。在...