linux信號處理機制2





本文簡單介紹下Linux信號處理機制,爲介紹二進制翻譯下信號處理機制做一個鋪墊。
本文主要參考書目《Linux內核源代碼情景分析》《獨闢蹊徑品內核:Linux內核源代碼導讀》

 

信號概述

   ●  信號是在軟件層次上對中斷機制的一種模擬。在原理上,一個進程收到一個信號與處理器收到一箇中斷請求可以說是一樣的。

   ●  信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上進程也不知道信號到底什麼時候到達。

   ●  信號可以直接進行用戶空間進程和內核進程之間的交互,內核進程也可以利用它來通知用戶空間進程發生了哪些系統事件。它可以在任何時候發給某一個進程,而無需知道該進程的狀態。如果該信號當前並未處於執行態(Running),則該信號由內核保存起來,直到該進程恢復執行再傳遞給它爲止。如果一個信號被進程設置爲阻塞,則該信號的傳遞被延遲,直到其阻塞被取消時才被傳遞給進程。

   ●  信號是進程間通信機制中唯一的異步通信機制,可以看作是異步通知,通知接收信號的進程有哪些事件發生了。信號機制除了基本通知外,還可以傳遞附加信息。

信號來源

     信號事件發生的來源有兩種:

     ① 硬件來源。如我們按下了鍵盤上的按鈕 或者出現其他硬件故障;

     ② 軟件來源。最常用發送信號的系統函數有kill()、raise()、alarm()、setitimer()和sigqueue()等,軟件來源還包括一些非法運算等操作。

進程響應信號的方式

    ① 忽略信號。忽略信號即對信號不做處理,其中,有兩個信號不能忽略:SIGKILL和SIGSTOP。

    ② 捕捉信號。定義信號處理函數,當信號發生時,執行響應的處理函數。

    ③ 執行默認操作。

首先,先說一下什麼是信號。信號本質上是在軟件層次上對中斷機制的一種模擬,其主要有以下幾種來源:

  1. 程序錯誤:除零,非法內存訪問…
  2. 外部信號:終端Ctrl-C產生SGINT信號,定時器到期產生SIGALRM…
  3. 顯式請求:kill函數允許進程發送任何信號給其他進程或進程組。


Linux對每種信號都規定了默認操作,如下表所示:

   



信號的處理包括信號的發送、捕捉和處理,它們有各自相對應的常見函數:

    ●  發生信號的函數: kill()、raise()。 

    ●  捕捉信號的函數: alarm()、pause()。

    ●  處理信號的函數: signal()、sigaction()。

  本節主要講信號的發送與捕捉,下一節再講處理

信號發送函數 kill()和raise()

函數說明

   kill()函數同咱們的kill系統命令一樣(但不能誤以爲kill()就是kill哈),可以發送信號給進程或進程組(實際上,kill系統命令只是kill()函數的一個用戶接口)。這裏需要注意的是,kill()函數不僅可以終止進程(實際上是通過發出SIGKILL信號終止),也可以向進程發送其他信號。

   與kill()函數不同的是,raise()函數允許進程向自身發送信號



在Linux下,可以通過以下命令查看系統所有的信號:

kill-l

可以通過類似下面的命令顯式的給一個進程發送一個信號:

kill-2 pid

上面的命令將2號信號發送給進程id爲pid的進程。不存在編號爲0的信號。

目前Linux支持64種信號。信號分爲非實時信號(不可靠信號)和實時信號(可靠信號)兩種類型,對應於 Linux 的信號值爲 1-31 和 34-64信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什麼時候到達。本文着重於Linux的信號處理機制,對信號更多的介紹可以參考這裏

一般情況下一個進程接受到信號後,會有如下的行爲:

進程對信號的響應

  1. 忽略信號:大部分信號可被忽略,除SIGSTOP和SIGKILL信號外(這是超級用戶殺掉或停掉任意進程的手段)。
  2. 捕獲信號:註冊信號處理函數,它對產生的特定信號做處理。
  3. 讓信號默認動作起作用:unix內核定義的默認動作,有5種情況:
    • a) 流產abort:終止進程併產生core文件。
    • b) 終止stop:終止進程但不生成core文件。
    • c) 忽略:忽略信號。
    • d) 掛起suspend:掛起進程。
    • e) 繼續continue:若進程是掛起的,則resume進程,否則忽略此信號。

註冊信號處理函數

如果想要進程捕獲某個信號,然後作出相應的處理,就需要註冊信號處理函數。同中斷類似,內核也爲每個進程準備了一個信號向量表,信號向量表中記錄着每個信號所對應的處理機制,默認情況下是調用默認處理機制。當進程爲某個信號註冊了信號處理程序後,發生該信號時,內核就會調用註冊的函數

註冊信號處理函數是通過系統調用signal()、sigaction()。其中signal()在可靠信號系統調用的基礎上實現, 是庫函數。它只有兩個參數,不支持信號傳遞信息,主要是用於前32種非實時信號的安裝;而sigaction()是較新的函數(由兩個系統調用實 現:sys_signal以及sys_rt_sigaction),有三個參數,支持信號傳遞信息,主要用來與 sigqueue() 系統調用配合使用,當然,sigaction()同樣支持非實時信號的安裝。sigaction()優於signal()主要體現在支持信號帶有參數。關於這方面的內容,如果想獲取更多,也可參考這裏

Linux下信號處理機制

進程如何發現和接受信號?

我們知道,信號是異步的一個進程不可能等待信號的到來,也不知道信號會到來,那麼,進程是如何發現和接受信號呢?實際上,信號的接收不是由用戶進程來完成的,而是由內核代理。當一個進程P2向另一個進程P1發送信號後,內核接受到信號,並將其放在P1的信號隊列當中。當P1再次陷入內核態時,會檢查信號隊列,並根據相應的信號調取相應的信號處理函數。如下圖所示:


其中,動作c:發現和捕捉信號

信號檢測和響應時機

剛纔我們說,當P1再次陷入內核時,會檢查信號隊列。那麼,P1什麼時候會再次陷入內核呢?陷入內核後在什麼時機會檢測信號隊列呢?

  1. 當前進程由於系統調用、中斷或異常而進入系統空間以後,從系統空間返回到用戶空間的前夕。
  2. 當前進程在內核中進入睡眠以後剛被喚醒的時候(必定是在系統調用中),或者由於不可忽略信號的存在而提前返回到用戶空間

進入信號處理函數

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

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

如圖中所見,處理信號的整個過程是這樣的:進程由於  系統調用或者中斷  進入內核,完成相應任務返回用戶空間的前夕,檢查信號隊列,如果有信號,則根據信號向量表找到信號處理函數,設置好“frame”後,跳到用戶態執行信號處理函數。信號處理函數執行完畢後,返回內核態,設置“frame”,再返回到用戶態繼續執行程序。


信號處理函數執行完後怎麼辦?

信號處理程序執行完畢之後,進程會主動調用sigreturn()系統調用再次回到內核,查看有沒有其他信號需要處理,如果沒有,這時內核就會做一些善後工作,將之前保存的frame恢復到內核棧,恢復eip的值爲old_eip,然後返回用戶空間,程序就能夠繼續執行。至此,內核遍完成了一次(或幾次)信號處理工作。

正常退出

  • 從main函數返回return

  • 調用exit

  • 調用_exit

異常退出

  • 調用abort

  • 由信號終止

_exit, exit和_Exit的區別和聯繫


_exit是Linux系統調用,關閉所有文件描述符,然後退出進程。

exit是C語言的庫函數,他最終調用_exit。在此之前,先清洗標準輸出的緩存,調用用atexit註冊的函數等, 在c語言的main函數中調用return就等價於調用exit。

_Exit是c語言的庫函數,自c99後加入,等價於_exit,即可以認爲它直接調用_Exit。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章