linux系統程式設計之訊號(二):訊號處理流程(產生、註冊、登出、執行)

mickole發表於2013-07-14

    對於一個完整的訊號生命週期(從訊號傳送到相應的處理函式執行完畢)來說,可以分為三個階段:

  • 訊號誕生
  • 訊號在程式中註冊
  • 訊號在程式中的登出
  • 訊號處理函式執行

1    訊號誕生

    訊號事件的發生有兩個來源:硬體來源(比如我們按下了鍵盤或者其它硬體故障);軟體來源,最常用傳送訊號的系統函式是kill, raise, alarm和setitimer以及sigqueue函式,軟體來源還包括一些非法運算等操作。

這裡按發出訊號的原因簡單分類,以瞭解各種訊號:

(1) 與程式終止相關的訊號。當程式退出,或者子程式終止時,發出這類訊號。

(2) 與程式例外事件相關的訊號。如程式越界,或企圖寫一個只讀的記憶體區域(如程式正文區),或執行一個特權指令及其他各種硬體錯誤。

(3) 與在系統呼叫期間遇到不可恢復條件相關的訊號。如執行系統呼叫exec時,原有資源已經釋放,而目前系統資源又已經耗盡。

(4) 與執行系統呼叫時遇到非預測錯誤條件相關的訊號。如執行一個並不存在的系統呼叫。

(5) 在使用者態下的程式發出的訊號。如程式呼叫系統呼叫kill向其他程式傳送訊號。

(6) 與終端互動相關的訊號。如使用者關閉一個終端,或按下break鍵等情況。

(7) 跟蹤程式執行的訊號。

Linux支援的訊號列表如下。很多訊號是與機器的體系結構相關的

訊號值 預設處理動作 發出訊號的原因

SIGHUP 1 A 終端掛起或者控制程式終止

SIGINT 2 A 鍵盤中斷(如break鍵被按下)

SIGQUIT 3 C 鍵盤的退出鍵被按下

SIGILL 4 C 非法指令

SIGABRT 6 C 由abort(3)發出的退出指令

SIGFPE 8 C 浮點異常

SIGKILL 9 AEF Kill訊號

SIGSEGV 11 C 無效的記憶體引用

SIGPIPE 13 A 管道破裂: 寫一個沒有讀埠的管道

SIGALRM 14 A 由alarm(2)發出的訊號

SIGTERM 15 A 終止訊號

SIGUSR1 30,10,16 A 使用者自定義訊號1

SIGUSR2 31,12,17 A 使用者自定義訊號2

SIGCHLD 20,17,18 B 子程式結束訊號

SIGCONT 19,18,25 程式繼續(曾被停止的程式)

SIGSTOP 17,19,23 DEF 終止程式

SIGTSTP 18,20,24 D 控制終端(tty)上按下停止鍵

SIGTTIN 21,21,26 D 後臺程式企圖從控制終端讀

SIGTTOU 22,22,27 D 後臺程式企圖從控制終端寫

處理動作一項中的字母含義如下

A 預設的動作是終止程式

B 預設的動作是忽略此訊號,將該訊號丟棄,不做處理

C 預設的動作是終止程式並進行核心映像轉儲(dump core),核心映像轉儲是指將程式資料在記憶體的映像和程式在核心結構中的部分內容以一定格式轉儲到檔案系統,並且程式退出執行,這樣做的好處是為程式設計師提供了方便,使得他們可以得到程式當時執行時的資料值,允許他們確定轉儲的原因,並且可以除錯他們的程式。

D 預設的動作是停止程式,進入停止狀況以後還能重新進行下去,一般是在除錯的過程中(例如ptrace系統呼叫)

E 訊號不能被捕獲

F 訊號不能被忽略

2 訊號在目標程式中註冊

在程式表的表項中有一個軟中斷訊號域,該域中每一位對應一個訊號。核心給一個程式傳送軟中斷訊號的方法,是在程式所在的程式表項的訊號域設定對應於該訊號的位。如果訊號傳送給一個正在睡眠的程式,如果程式睡眠在可被中斷的優先順序上,則喚醒程式;否則僅設定程式表中訊號域相應的位,而不喚醒程式。如果傳送給一個處於可執行狀態的程式,則只置相應的域即可。

程式的task_struct結構中有關於本程式中未決訊號的資料成員: struct sigpending pending:

struct sigpending{

        struct sigqueue *head, *tail;

        sigset_t signal;

};

第三個成員是程式中所有未決訊號集,第一、第二個成員分別指向一個sigqueue型別的結構鏈(稱之為"未決訊號資訊鏈")的首尾,資訊鏈中的每個sigqueue結構刻畫一個特定訊號所攜帶的資訊,並指向下一個sigqueue結構:

struct sigqueue{

        struct sigqueue *next;

        siginfo_t info;

}

    訊號在程式中註冊指的就是訊號值加入到程式的未決訊號集sigset_t signal(每個訊號佔用一位)中,並且訊號所攜帶的資訊被保留到未決訊號資訊鏈的某個sigqueue結構中。只要訊號在程式的未決訊號集中,表明程式已經知道這些訊號的存在,但還沒來得及處理,或者該訊號被程式阻塞。

    當一個實時訊號傳送給一個程式時,不管該訊號是否已經在程式中註冊,都會被再註冊一次,因此,訊號不會丟失,因此,實時訊號又叫做"可靠訊號"。這意味著同一個實時訊號可以在同一個程式的未決訊號資訊鏈中佔有多個sigqueue結構(程式每收到一個實時訊號,都會為它分配一個結構來登記該訊號資訊,並把該結構新增在未決訊號鏈尾,即所有誕生的實時訊號都會在目標程式中註冊)。

    當一個非實時訊號傳送給一個程式時,如果該訊號已經在程式中註冊(通過sigset_t signal指示),則該訊號將被丟棄,造成訊號丟失。因此,非實時訊號又叫做"不可靠訊號"。這意味著同一個非實時訊號在程式的未決訊號資訊鏈中,至多佔有一個sigqueue結構。

    總之訊號註冊與否,與傳送訊號的函式(如kill()或sigqueue()等)以及訊號安裝函式(signal()及sigaction())無關,只與訊號值有關(訊號值小於SIGRTMIN的訊號最多隻註冊一次,訊號值在SIGRTMIN及SIGRTMAX之間的訊號,只要被程式接收到就被註冊)

3 訊號的執行和登出

    核心處理一個程式收到的軟中斷訊號是在該程式的上下文中,因此,程式必須處於執行狀態。當其由於被訊號喚醒或者正常排程重新獲得CPU時,在其從核心空間返回到使用者空間時會檢測是否有訊號等待處理。如果存在未決訊號等待處理且該訊號沒有被程式阻塞,則在執行相應的訊號處理函式前,程式會把訊號在未決訊號鏈中佔有的結構卸掉。

    對於非實時訊號來說,由於在未決訊號資訊鏈中最多隻佔用一個sigqueue結構,因此該結構被釋放後,應該把訊號在程式未決訊號集中刪除(訊號登出完畢);而對於實時訊號來說,可能在未決訊號資訊鏈中佔用多個sigqueue結構,因此應該針對佔用sigqueue結構的數目區別對待:如果只佔用一個sigqueue結構(程式只收到該訊號一次),則執行完相應的處理函式後應該把訊號在程式的未決訊號集中刪除(訊號登出完畢)。否則待該訊號的所有sigqueue處理完畢後再在程式的未決訊號集中刪除該訊號。

    當所有未被遮蔽的訊號都處理完畢後,即可返回使用者空間。對於被遮蔽的訊號,當取消遮蔽後,在返回到使用者空間時會再次執行上述檢查處理的一套流程。

    核心處理一個程式收到的訊號的時機是在一個程式從核心態返回使用者態時。所以,當一個程式在核心態下執行時,軟中斷訊號並不立即起作用,要等到將返回使用者態時才處理。程式只有處理完訊號才會返回使用者態,程式在使用者態下不會有未處理完的訊號。

    處理訊號有三種型別:程式接收到訊號後退出;程式忽略該訊號;程式收到訊號後執行使用者設定用系統呼叫signal的函式。當程式接收到一個它忽略的訊號時,程式丟棄該訊號,就象沒有收到該訊號似的繼續執行。如果程式收到一個要捕捉的訊號,那麼程式從核心態返回使用者態時執行使用者定義的函式。而且執行使用者定義的函式的方法很巧妙,核心是在使用者棧上建立一個新的層,該層中將返回地址的值設定成使用者定義的處理函式的地址,這樣程式從核心返回彈出棧頂時就返回到使用者定義的函式處,從函式返回再彈出棧頂時,才返回原先進入核心的地方。這樣做的原因是使用者定義的處理函式不能且不允許在核心態下執行(如果使用者定義的函式在核心態下執行的話,使用者就可以獲得任何許可權)。

注:本文轉自http://www.cnblogs.com/hoys/archive/2012/08/19/2646377.html

相關文章