天天看點

linux信号機制分析

以前一直對linux的信号機制似懂非懂,今天找到了一篇好文章,講的比較清楚,是以忍不住轉了一下,原文位址為:

【摘要】本文分析了Linux核心對于信号的實作機制和應用層的相關處理。首先介紹了軟中斷信号的本質及信号的兩種不同分類方法

尤其是不可靠信号的原理。接着分析了核心對于信号的處理流程包括信号的觸發/注冊/執行及登出等。最後介紹了應用層的相關處理

,主要包括信号處理函數的安裝、信号的發送、屏蔽阻塞等,最後給了幾個簡單的應用執行個體。

【關鍵字】軟中斷信号,signal,sigaction,kill,sigqueue,settimer,sigmask,sigprocmask,sigset_t

1 信号本質

        軟中斷信号(signal,又簡稱為信号)用來通知程序發生了異步事件。在軟體層次上是對中斷機制的一種模拟,在原理上,一個程序收到一個信号與處理器收到一個中斷請求可以說是一樣的。信号是程序間通信機制中唯一的異步通信機制,一個程序不必通過任何操作來等待信号的到達,事實上,程序也不知道信号到底什麼時候到達。程序之間可以互相通過系統調用kill發送軟中斷信号。核心也可以因為内部事件而給程序發送信号,通知程序發生了某個事件。信号機制除了基本通知功能外,還可以傳遞附加資訊。

       收到信号的程序對各種信号有不同的處理方法。處理方法可以分為三類:

第一種是類似中斷的處理程式,對于需要處理的信号,程序可以指定處理函數,由該函數來處理。

第二種方法是,忽略某個信号,對該信号不做任何處理,就象未發生過一樣。

第三種方法是,對該信号的處理保留系統的預設值,這種預設操作,對大部分的信号的預設操作是使得程序終止。程序通過系統調用signal來指定程序對某個信号的處理行為。

2       信号的種類

可以從兩個不同的分類角度對信号進行分類:

可靠性方面:可靠信号與不可靠信号;

與時間的關系上:實時信号與非實時信号。

2.1    可靠信号與不可靠信号

        Linux信号機制基本上是從Unix系統中繼承過來的。早期Unix系統中的信号機制比較簡單和原始,信号值小于SIGRTMIN的信号都是不可靠信号。這就是"不可靠信号"的來源。它的主要問題是信号可能丢失。随着時間的發展,實踐證明了有必要對信号的原始機制加以改進和擴充。由于原來定義的信号已有許多應用,不好再做改動,最終隻好又新增加了一些信号,并在一開始就把它們定義為可靠信号,這些信号支援排隊,不會丢失。

信号值位于SIGRTMIN和SIGRTMAX之間的信号都是可靠信号,可靠信号克服了信号可能丢失的問題。Linux在支援新版本的信号安裝函數sigation()以及信号發送函數sigqueue()的同時,仍然支援早期的signal()信号安裝函數,支援信号發送函數kill()。信号的可靠與不可靠隻與信号值有關,與信号的發送及安裝函數無關。目前linux中的signal()是通過sigation()函數實作的,是以,即使通過signal()安裝的信号,在信号處理函數的結尾也不必再調用一次信号安裝函數。同時,由signal()安裝的實時信号支援排隊,同樣不會丢失。

對于目前linux的兩個信号安裝函數:signal()及sigaction()來說,它們都不能把SIGRTMIN以前的信号變成可靠信号(都不支援排隊,仍有可能丢失,仍然是不可靠信号),而且對SIGRTMIN以後的信号都支援排隊。這兩個函數的最大差別在于,經過sigaction安裝的信号都能傳遞資訊給信号處理函數,而經過signal安裝的信号不能向信号處理函數傳遞資訊。對于信号發送函數來說也是一樣的。

2.2    實時信号與非實時信号

       早期Unix系統隻定義了32種信号,前32種信号已經有了預定義值,每個信号有了确定的用途及含義,并且每種信号都有各自的預設動作。如按鍵盤的CTRL ^C時,會産生SIGINT信号,對該信号的預設反應就是程序終止。後32個信号表示實時信号,等同于前面闡述的可靠信号。這保證了發送的多個實時信号都被接收。

        非實時信号都不支援排隊,都是不可靠信号;實時信号都支援排隊,都是可靠信号。

3       信号處理流程

對于一個完整的信号生命周期(從信号發送到相應的處理函數執行完畢)來說,可以分為三個階段:

信号誕生

信号在程序中注冊

信号的執行和登出

3.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 信号不能被忽略

3.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.3    信号的執行和登出

核心處理一個程序收到的軟中斷信号是在該程序的上下文中,是以,程序必須處于運作狀态。當其由于被信号喚醒或者正常排程重新獲得CPU時,在其從核心空間傳回到使用者空間時會檢測是否有信号等待處理。如果存在未決信号等待處理且該信号沒有被程序阻塞,則在運作相應的信号處理函數前,程序會把信号在未決信号鍊中占有的結構卸掉。

對于非實時信号來說,由于在未決信号資訊鍊中最多隻占用一個sigqueue結構,是以該結構被釋放後,應該把信号在程序未決信号集中删除(信号登出完畢);而對于實時信号來說,可能在未決信号資訊鍊中占用多個sigqueue結構,是以應該針對占用sigqueue結構的數目差別對待:如果隻占用一個sigqueue結構(程序隻收到該信号一次),則執行完相應的處理函數後應該把信号在程序的未決信号集中删除(信号登出完畢)。否則待該信号的所有sigqueue處理完畢後再在程序的未決信号集中删除該信号。

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

核心處理一個程序收到的信号的時機是在一個程序從核心态傳回使用者态時。是以,當一個程序在核心态下運作時,軟中斷信号并不立即起作用,要等到将傳回使用者态時才處理。程序隻有處理完信号才會傳回使用者态,程序在使用者态下不會有未處理完的信号。

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

4       信号的安裝

如果程序要處理某一信号,那麼就要在程序中安裝該信号。安裝信号主要用來确定信号值及程序針對該信号值的動作之間的映射關系,即程序将要處理哪個信号;該信号被傳遞給程序時,将執行何種操作。

linux主要有兩個函數實作信号的安裝:signal()、sigaction()。其中signal()隻有兩個參數,不支援信号傳遞資訊,主要是用于前32種非實時信号的安裝;而sigaction()是較新的函數(由兩個系統調用實作:sys_signal以及sys_rt_sigaction),有三個參數,支援信号傳遞資訊,主要用來與 sigqueue() 系統調用配合使用,當然,sigaction()同樣支援非實時信号的安裝。sigaction()優于signal()主要展現在支援信号帶有參數。

ult)。

5       信号的發送

繼續閱讀