Linux信號(hào)(signal) 機(jī)制分析(1)
【摘要】本文分析了Linux內(nèi)核對(duì)于信號(hào)的實(shí)現(xiàn)機(jī)制和應(yīng)用層的相關(guān)處理。首先介紹了軟中斷信號(hào)的本質(zhì)及信號(hào)的兩種不同分類方法尤其是不可靠信號(hào)的原理。接著分析了內(nèi)核對(duì)于信號(hào)的處理流程包括信號(hào)的觸發(fā)/注冊(cè)/執(zhí)行及注銷等。***介紹了應(yīng)用層的相關(guān)處理,主要包括信號(hào)處理函數(shù)的安裝、信號(hào)的發(fā)送、屏蔽阻塞等,***給了幾個(gè)簡(jiǎn)單的應(yīng)用實(shí)例。
【關(guān)鍵字】軟中斷信號(hào),signal,sigaction,kill,sigqueue,settimer,sigmask,sigprocmask,sigset_t
1. 信號(hào)本質(zhì)
軟中斷信號(hào)(signal,又簡(jiǎn)稱為信號(hào))用來(lái)通知進(jìn)程發(fā)生了異步事件。在軟件層次上是對(duì)中斷機(jī)制的一種模擬,在原理上,一個(gè)進(jìn)程收到一個(gè)信號(hào)與處理器收到一個(gè)中斷請(qǐng)求可以說(shuō)是一樣的。信號(hào)是進(jìn)程間通信機(jī)制中唯一的異步通信機(jī)制,一個(gè)進(jìn)程不必通過(guò)任何操作來(lái)等待信號(hào)的到達(dá),事實(shí)上,進(jìn)程也不知道信號(hào)到底什么時(shí)候到達(dá)。進(jìn)程之間可以互相通過(guò)系統(tǒng)調(diào)用kill發(fā)送軟中斷信號(hào)。內(nèi)核也可以因?yàn)閮?nèi)部事件而給進(jìn)程發(fā)送信號(hào),通知進(jìn)程發(fā)生了某個(gè)事件。信號(hào)機(jī)制除了基本通知功能外,還可以傳遞附加信息。
收到信號(hào)的進(jìn)程對(duì)各種信號(hào)有不同的處理方法。處理方法可以分為三類:
***種是類似中斷的處理程序,對(duì)于需要處理的信號(hào),進(jìn)程可以指定處理函數(shù),由該函數(shù)來(lái)處理。
第二種方法是,忽略某個(gè)信號(hào),對(duì)該信號(hào)不做任何處理,就象未發(fā)生過(guò)一樣。
第三種方法是,對(duì)該信號(hào)的處理保留系統(tǒng)的默認(rèn)值,這種缺省操作,對(duì)大部分的信號(hào)的缺省操作是使得進(jìn)程終止。進(jìn)程通過(guò)系統(tǒng)調(diào)用signal來(lái)指定進(jìn)程對(duì)某個(gè)信號(hào)的處理行為。
2. 信號(hào)的種類
可以從兩個(gè)不同的分類角度對(duì)信號(hào)進(jìn)行分類:
- 可靠性方面:可靠信號(hào)與不可靠信號(hào);
- 與時(shí)間的關(guān)系上:實(shí)時(shí)信號(hào)與非實(shí)時(shí)信號(hào)。
2.1 可靠信號(hào)與不可靠信號(hào)
Linux信號(hào)機(jī)制基本上是從Unix系統(tǒng)中繼承過(guò)來(lái)的。早期Unix系統(tǒng)中的信號(hào)機(jī)制比較簡(jiǎn)單和原始,信號(hào)值小于SIGRTMIN的信號(hào)都是不可靠信號(hào)。這就是"不可靠信號(hào)"的來(lái)源。它的主要問(wèn)題是信號(hào)可能丟失。
隨著時(shí)間的發(fā)展,實(shí)踐證明了有必要對(duì)信號(hào)的原始機(jī)制加以改進(jìn)和擴(kuò)充。由于原來(lái)定義的信號(hào)已有許多應(yīng)用,不好再做改動(dòng),最終只好又新增加了一些信號(hào),并在一開始就把它們定義為可靠信號(hào),這些信號(hào)支持排隊(duì),不會(huì)丟失。
信號(hào)值位于SIGRTMIN和SIGRTMAX之間的信號(hào)都是可靠信號(hào),可靠信號(hào)克服了信號(hào)可能丟失的問(wèn)題。Linux在支持新版本的信號(hào)安裝函數(shù)sigation()以及信號(hào)發(fā)送函數(shù)sigqueue()的同時(shí),仍然支持早期的signal()信號(hào)安裝函數(shù),支持信號(hào)發(fā)送函數(shù)kill()。
信號(hào)的可靠與不可靠只與信號(hào)值有關(guān),與信號(hào)的發(fā)送及安裝函數(shù)無(wú)關(guān)。目前l(fā)inux中的signal()是通過(guò)sigation()函數(shù)實(shí)現(xiàn)的,因此,即使通過(guò)signal()安裝的信號(hào),在信號(hào)處理函數(shù)的結(jié)尾也不必再調(diào)用一次信號(hào)安裝函數(shù)。同時(shí),由signal()安裝的實(shí)時(shí)信號(hào)支持排隊(duì),同樣不會(huì)丟失。
對(duì)于目前l(fā)inux的兩個(gè)信號(hào)安裝函數(shù):signal()及sigaction()來(lái)說(shuō),它們都不能把SIGRTMIN以前的信號(hào)變成可靠信號(hào)(都不支持排隊(duì),仍有可能丟失,仍然是不可靠信號(hào)),而且對(duì)SIGRTMIN以后的信號(hào)都支持排隊(duì)。這兩個(gè)函數(shù)的***區(qū)別在于,經(jīng)過(guò)sigaction安裝的信號(hào)都能傳遞信息給信號(hào)處理函數(shù),而經(jīng)過(guò)signal安裝的信號(hào)不能向信號(hào)處理函數(shù)傳遞信息。對(duì)于信號(hào)發(fā)送函數(shù)來(lái)說(shuō)也是一樣的。
2.2 實(shí)時(shí)信號(hào)與非實(shí)時(shí)信號(hào)
早期Unix系統(tǒng)只定義了32種信號(hào),前32種信號(hào)已經(jīng)有了預(yù)定義值,每個(gè)信號(hào)有了確定的用途及含義,并且每種信號(hào)都有各自的缺省動(dòng)作。如按鍵盤的CTRL ^C時(shí),會(huì)產(chǎn)生SIGINT信號(hào),對(duì)該信號(hào)的默認(rèn)反應(yīng)就是進(jìn)程終止。后32個(gè)信號(hào)表示實(shí)時(shí)信號(hào),等同于前面闡述的可靠信號(hào)。這保證了發(fā)送的多個(gè)實(shí)時(shí)信號(hào)都被接收。
非實(shí)時(shí)信號(hào)都不支持排隊(duì),都是不可靠信號(hào);實(shí)時(shí)信號(hào)都支持排隊(duì),都是可靠信號(hào)。
3. 信號(hào)處理流程
對(duì)于一個(gè)完整的信號(hào)生命周期(從信號(hào)發(fā)送到相應(yīng)的處理函數(shù)執(zhí)行完畢)來(lái)說(shuō),可以分為三個(gè)階段:
- 信號(hào)誕生
- 信號(hào)在進(jìn)程中注冊(cè)
- 信號(hào)的執(zhí)行和注銷
3.1 信號(hào)誕生
信號(hào)事件的發(fā)生有兩個(gè)來(lái)源:硬件來(lái)源(比如我們按下了鍵盤或者其它硬件故障);軟件來(lái)源,最常用發(fā)送信號(hào)的系統(tǒng)函數(shù)是kill, raise, alarm和setitimer以及sigqueue函數(shù),軟件來(lái)源還包括一些非法運(yùn)算等操作。
這里按發(fā)出信號(hào)的原因簡(jiǎn)單分類,以了解各種信號(hào):
(1) 與進(jìn)程終止相關(guān)的信號(hào)。當(dāng)進(jìn)程退出,或者子進(jìn)程終止時(shí),發(fā)出這類信號(hào)。
(2) 與進(jìn)程例外事件相關(guān)的信號(hào)。如進(jìn)程越界,或企圖寫一個(gè)只讀的內(nèi)存區(qū)域(如程序正文區(qū)),或執(zhí)行一個(gè)特權(quán)指令及其他各種硬件錯(cuò)誤。
(3) 與在系統(tǒng)調(diào)用期間遇到不可恢復(fù)條件相關(guān)的信號(hào)。如執(zhí)行系統(tǒng)調(diào)用exec時(shí),原有資源已經(jīng)釋放,而目前系統(tǒng)資源又已經(jīng)耗盡。
(4) 與執(zhí)行系統(tǒng)調(diào)用時(shí)遇到非預(yù)測(cè)錯(cuò)誤條件相關(guān)的信號(hào)。如執(zhí)行一個(gè)并不存在的系統(tǒng)調(diào)用。
(5) 在用戶態(tài)下的進(jìn)程發(fā)出的信號(hào)。如進(jìn)程調(diào)用系統(tǒng)調(diào)用kill向其他進(jìn)程發(fā)送信號(hào)。
(6) 與終端交互相關(guān)的信號(hào)。如用戶關(guān)閉一個(gè)終端,或按下break鍵等情況。
(7) 跟蹤進(jìn)程執(zhí)行的信號(hào)。
Linux支持的信號(hào)列表如下。很多信號(hào)是與機(jī)器的體系結(jié)構(gòu)相關(guān)的
信號(hào)值 默認(rèn)處理動(dòng)作 發(fā)出信號(hào)的原因
SIGHUP 1 A 終端掛起或者控制進(jìn)程終止
SIGINT 2 A 鍵盤中斷(如break鍵被按下)
SIGQUIT 3 C 鍵盤的退出鍵被按下
SIGILL 4 C 非法指令
SIGABRT 6 C 由abort(3)發(fā)出的退出指令
SIGFPE 8 C 浮點(diǎn)異常
SIGKILL 9 AEF Kill信號(hào)
SIGSEGV 11 C 無(wú)效的內(nèi)存引用
SIGPIPE 13 A 管道破裂: 寫一個(gè)沒(méi)有讀端口的管道
SIGALRM 14 A 由alarm(2)發(fā)出的信號(hào)
SIGTERM 1*** 終止信號(hào)
SIGUSR1 30,10,16 A 用戶自定義信號(hào)1
SIGUSR2 31,12,17 A 用戶自定義信號(hào)2
SIGCHLD 20,17,18 B 子進(jìn)程結(jié)束信號(hào)
SIGCONT 19,18,25 進(jìn)程繼續(xù)(曾被停止的進(jìn)程)
SIGS***7,19,23 DEF 終止進(jìn)程
SIGTSTP 18,20,24 D 控制終端(tty)上按下停止鍵
SIGTTIN 21,21,26 D 后臺(tái)進(jìn)程企圖從控制終端讀
SIGTTOU 22,22,27 D 后臺(tái)進(jìn)程企圖從控制終端寫
處理動(dòng)作一項(xiàng)中的字母含義如下
A 缺省的動(dòng)作是終止進(jìn)程
B 缺省的動(dòng)作是忽略此信號(hào),將該信號(hào)丟棄,不做處理
C 缺省的動(dòng)作是終止進(jìn)程并進(jìn)行內(nèi)核映像轉(zhuǎn)儲(chǔ)(dump core),內(nèi)核映像轉(zhuǎn)儲(chǔ)是指將進(jìn)程數(shù)據(jù)在內(nèi)存的映像和進(jìn)程在內(nèi)核結(jié)構(gòu)中的部分內(nèi)容以一定格式轉(zhuǎn)儲(chǔ)到文件系統(tǒng),并且進(jìn)程退出執(zhí)行,這樣做的好處是為程序員提供了方便,使得他們可以得到進(jìn)程當(dāng)時(shí)執(zhí)行時(shí)的數(shù)據(jù)值,允許他們確定轉(zhuǎn)儲(chǔ)的原因,并且可以調(diào)試他們的程序。
D 缺省的動(dòng)作是停止進(jìn)程,進(jìn)入停止?fàn)顩r以后還能重新進(jìn)行下去,一般是在調(diào)試的過(guò)程中(例如ptrace系統(tǒng)調(diào)用)
E 信號(hào)不能被捕獲
F 信號(hào)不能被忽略
3.2 信號(hào)在目標(biāo)進(jìn)程中注冊(cè)
在進(jìn)程表的表項(xiàng)中有一個(gè)軟中斷信號(hào)域,該域中每一位對(duì)應(yīng)一個(gè)信號(hào)。內(nèi)核給一個(gè)進(jìn)程發(fā)送軟中斷信號(hào)的方法,是在進(jìn)程所在的進(jìn)程表項(xiàng)的信號(hào)域設(shè)置對(duì)應(yīng)于該信號(hào)的位。如果信號(hào)發(fā)送給一個(gè)正在睡眠的進(jìn)程,如果進(jìn)程睡眠在可被中斷的優(yōu)先級(jí)上,則喚醒進(jìn)程;否則僅設(shè)置進(jìn)程表中信號(hào)域相應(yīng)的位,而不喚醒進(jìn)程。如果發(fā)送給一個(gè)處于可運(yùn)行狀態(tài)的進(jìn)程,則只置相應(yīng)的域即可。
進(jìn)程的task_struct結(jié)構(gòu)中有關(guān)于本進(jìn)程中未決信號(hào)的數(shù)據(jù)成員: struct sigpending pending:
- struct sigpending{
- struct sigqueue *head, *tail;
- sigset_t signal;
- };
第三個(gè)成員是進(jìn)程中所有未決信號(hào)集,***、第二個(gè)成員分別指向一個(gè)sigqueue類型的結(jié)構(gòu)鏈(稱之為"未決信號(hào)信息鏈")的首尾,信息鏈中的每個(gè)sigqueue結(jié)構(gòu)刻畫一個(gè)特定信號(hào)所攜帶的信息,并指向下一個(gè)sigqueue結(jié)構(gòu):
- struct sigqueue{
- struct sigqueue *next;
- siginfo_t info;
- }
信號(hào)在進(jìn)程中注冊(cè)指的就是信號(hào)值加入到進(jìn)程的未決信號(hào)集sigset_t signal(每個(gè)信號(hào)占用一位)中,并且信號(hào)所攜帶的信息被保留到未決信號(hào)信息鏈的某個(gè)sigqueue結(jié)構(gòu)中。只要信號(hào)在進(jìn)程的未決信號(hào)集中,表明進(jìn)程已經(jīng)知道這些信號(hào)的存在,但還沒(méi)來(lái)得及處理,或者該信號(hào)被進(jìn)程阻塞。
當(dāng)一個(gè)實(shí)時(shí)信號(hào)發(fā)送給一個(gè)進(jìn)程時(shí),不管該信號(hào)是否已經(jīng)在進(jìn)程中注冊(cè),都會(huì)被再注冊(cè)一次,因此,信號(hào)不會(huì)丟失,因此,實(shí)時(shí)信號(hào)又叫做"可靠信號(hào)"。這意味著同一個(gè)實(shí)時(shí)信號(hào)可以在同一個(gè)進(jìn)程的未決信號(hào)信息鏈中占有多個(gè)sigqueue結(jié)構(gòu)(進(jìn)程每收到一個(gè)實(shí)時(shí)信號(hào),都會(huì)為它分配一個(gè)結(jié)構(gòu)來(lái)登記該信號(hào)信息,并把該結(jié)構(gòu)添加在未決信號(hào)鏈尾,即所有誕生的實(shí)時(shí)信號(hào)都會(huì)在目標(biāo)進(jìn)程中注冊(cè))。
當(dāng)一個(gè)非實(shí)時(shí)信號(hào)發(fā)送給一個(gè)進(jìn)程時(shí),如果該信號(hào)已經(jīng)在進(jìn)程中注冊(cè)(通過(guò)sigset_t signal指示),則該信號(hào)將被丟棄,造成信號(hào)丟失。因此,非實(shí)時(shí)信號(hào)又叫做"不可靠信號(hào)"。這意味著同一個(gè)非實(shí)時(shí)信號(hào)在進(jìn)程的未決信號(hào)信息鏈中,至多占有一個(gè)sigqueue結(jié)構(gòu)。
總之信號(hào)注冊(cè)與否,與發(fā)送信號(hào)的函數(shù)(如kill()或sigqueue()等)以及信號(hào)安裝函數(shù)(signal()及sigaction())無(wú)關(guān),只與信號(hào)值有關(guān)(信號(hào)值小于SIGRTMIN的信號(hào)最多只注冊(cè)一次,信號(hào)值在SIGRTMIN及SIGRTMAX之間的信號(hào),只要被進(jìn)程接收到就被注冊(cè))
3.3 信號(hào)的執(zhí)行和注銷
內(nèi)核處理一個(gè)進(jìn)程收到的軟中斷信號(hào)是在該進(jìn)程的上下文中,因此,進(jìn)程必須處于運(yùn)行狀態(tài)。當(dāng)其由于被信號(hào)喚醒或者正常調(diào)度重新獲得CPU時(shí),在其從內(nèi)核空間返回到用戶空間時(shí)會(huì)檢測(cè)是否有信號(hào)等待處理。如果存在未決信號(hào)等待處理且該信號(hào)沒(méi)有被進(jìn)程阻塞,則在運(yùn)行相應(yīng)的信號(hào)處理函數(shù)前,進(jìn)程會(huì)把信號(hào)在未決信號(hào)鏈中占有的結(jié)構(gòu)卸掉。
對(duì)于非實(shí)時(shí)信號(hào)來(lái)說(shuō),由于在未決信號(hào)信息鏈中最多只占用一個(gè)sigqueue結(jié)構(gòu),因此該結(jié)構(gòu)被釋放后,應(yīng)該把信號(hào)在進(jìn)程未決信號(hào)集中刪除(信號(hào)注銷完畢);而對(duì)于實(shí)時(shí)信號(hào)來(lái)說(shuō),可能在未決信號(hào)信息鏈中占用多個(gè)sigqueue結(jié)構(gòu),因此應(yīng)該針對(duì)占用sigqueue結(jié)構(gòu)的數(shù)目區(qū)別對(duì)待:如果只占用一個(gè)sigqueue結(jié)構(gòu)(進(jìn)程只收到該信號(hào)一次),則執(zhí)行完相應(yīng)的處理函數(shù)后應(yīng)該把信號(hào)在進(jìn)程的未決信號(hào)集中刪除(信號(hào)注銷完畢)。否則待該信號(hào)的所有sigqueue處理完畢后再在進(jìn)程的未決信號(hào)集中刪除該信號(hào)。
當(dāng)所有未被屏蔽的信號(hào)都處理完畢后,即可返回用戶空間。對(duì)于被屏蔽的信號(hào),當(dāng)取消屏蔽后,在返回到用戶空間時(shí)會(huì)再次執(zhí)行上述檢查處理的一套流程。
內(nèi)核處理一個(gè)進(jìn)程收到的信號(hào)的時(shí)機(jī)是在一個(gè)進(jìn)程從內(nèi)核態(tài)返回用戶態(tài)時(shí)。所以,當(dāng)一個(gè)進(jìn)程在內(nèi)核態(tài)下運(yùn)行時(shí),軟中斷信號(hào)并不立即起作用,要等到將返回用戶態(tài)時(shí)才處理。進(jìn)程只有處理完信號(hào)才會(huì)返回用戶態(tài),進(jìn)程在用戶態(tài)下不會(huì)有未處理完的信號(hào)。
處理信號(hào)有三種類型:進(jìn)程接收到信號(hào)后退出;進(jìn)程忽略該信號(hào);進(jìn)程收到信號(hào)后執(zhí)行用戶設(shè)定用系統(tǒng)調(diào)用signal的函數(shù)。當(dāng)進(jìn)程接收到一個(gè)它忽略的信號(hào)時(shí),進(jìn)程丟棄該信號(hào),就象沒(méi)有收到該信號(hào)似的繼續(xù)運(yùn)行。如果進(jìn)程收到一個(gè)要捕捉的信號(hào),那么進(jìn)程從內(nèi)核態(tài)返回用戶態(tài)時(shí)執(zhí)行用戶定義的函數(shù)。而且執(zhí)行用戶定義的函數(shù)的方法很巧妙,內(nèi)核是在用戶棧上創(chuàng)建一個(gè)新的層,該層中將返回地址的值設(shè)置成用戶定義的處理函數(shù)的地址,這樣進(jìn)程從內(nèi)核返回彈出棧頂時(shí)就返回到用戶定義的函數(shù)處,從函數(shù)返回再?gòu)棾鰲m敃r(shí),才返回原先進(jìn)入內(nèi)核的地方。這樣做的原因是用戶定義的處理函數(shù)不能且不允許在內(nèi)核態(tài)下執(zhí)行(如果用戶定義的函數(shù)在內(nèi)核態(tài)下運(yùn)行的話,用戶就可以獲得任何權(quán)限)。
4. 信號(hào)的安裝
如果進(jìn)程要處理某一信號(hào),那么就要在進(jìn)程中安裝該信號(hào)。安裝信號(hào)主要用來(lái)確定信號(hào)值及進(jìn)程針對(duì)該信號(hào)值的動(dòng)作之間的映射關(guān)系,即進(jìn)程將要處理哪個(gè)信號(hào);該信號(hào)被傳遞給進(jìn)程時(shí),將執(zhí)行何種操作。
linux主要有兩個(gè)函數(shù)實(shí)現(xiàn)信號(hào)的安裝:signal()、sigaction()。其中signal()只有兩個(gè)參數(shù),不支持信號(hào)傳遞信息,主要是用于前32種非實(shí)時(shí)信號(hào)的安裝;而sigaction()是較新的函數(shù)(由兩個(gè)系統(tǒng)調(diào)用實(shí)現(xiàn):sys_signal以及sys_rt_sigaction),有三個(gè)參數(shù),支持信號(hào)傳遞信息,主要用來(lái)與 sigqueue() 系統(tǒng)調(diào)用配合使用,當(dāng)然,sigaction()同樣支持非實(shí)時(shí)信號(hào)的安裝。sigaction()優(yōu)于signal()主要體現(xiàn)在支持信號(hào)帶有參數(shù)。
4.1 signal()
- #include <signal.h>
- void (*signal(int signum, void (*handler))(int)))(int);
如果該函數(shù)原型不容易理解的話,可以參考下面的分解方式來(lái)理解:
- typedef void (*sighandler_t)(int);
- sighandler_t signal(int signum, sighandler_t handler));
***個(gè)參數(shù)指定信號(hào)的值,第二個(gè)參數(shù)指定針對(duì)前面信號(hào)值的處理,可以忽略該信號(hào)(參數(shù)設(shè)為SIG_IGN);可以采用系統(tǒng)默認(rèn)方式處理信號(hào)(參數(shù)設(shè)為SIG_DFL);也可以自己實(shí)現(xiàn)處理方式(參數(shù)指定一個(gè)函數(shù)地址)。
如果signal()調(diào)用成功,返回***一次為安裝信號(hào)signum而調(diào)用signal()時(shí)的handler值;失敗則返回SIG_ERR。
傳遞給信號(hào)處理例程的整數(shù)參數(shù)是信號(hào)值,這樣可以使得一個(gè)信號(hào)處理例程處理多個(gè)信號(hào)。
- #include <signal.h>
- #include <unistd.h>
- #include <stdio.h>
- void sigroutine(int dunno)
- { /* 信號(hào)處理例程,其中dunno將會(huì)得到信號(hào)的值 */
- switch (dunno) {
- case 1:
- printf("Get a signal -- SIGHUP ");
- break;
- case 2:
- printf("Get a signal -- SIGINT ");
- break;
- case 3:
- printf("Get a signal -- SIGQUIT ");
- break;
- }
- return;
- }
- int main() {
- printf("process id is %d ",getpid());
- signal(SIGHUP, sigroutine); //* 下面設(shè)置三個(gè)信號(hào)的處理方法
- signal(SIGINT, sigroutine);
- signal(SIGQUIT, sigroutine);
- for (;;) ;
- }
其中信號(hào)SIGINT由按下Ctrl-C發(fā)出,信號(hào)SIGQUIT由按下Ctrl-發(fā)出。該程序執(zhí)行的結(jié)果如下:
- localhost:~$ ./sig_test
- process id is 463
- Get a signal -SIGINT //按下Ctrl-C得到的結(jié)果
- Get a signal -SIGQUIT //按下Ctrl-得到的結(jié)果
- //按下Ctrl-z將進(jìn)程置于后臺(tái)
- [1]+ Stopped ./sig_test
- localhost:~$ bg
- [1]+ ./sig_test &
- localhost:~$ kill -HUP 463 //向進(jìn)程發(fā)送SIGHUP信號(hào)
- localhost:~$ Get a signal – SIGHUP
- kill -9 463 //向進(jìn)程發(fā)送SIGKILL信號(hào),終止進(jìn)程
- localhost:~$
4.2 sigaction()
- #include <signal.h>
- int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
sigaction函數(shù)用于改變進(jìn)程接收到特定信號(hào)后的行為。該函數(shù)的***個(gè)參數(shù)為信號(hào)的值,可以為除SIGKILL及SIGSTOP外的任何一個(gè)特定有效的信號(hào)(為這兩個(gè)信號(hào)定義自己的處理函數(shù),將導(dǎo)致信號(hào)安裝錯(cuò)誤)。第二個(gè)參數(shù)是指向結(jié)構(gòu)sigaction的一個(gè)實(shí)例的指針,在結(jié)構(gòu)sigaction的實(shí)例中,指定了對(duì)特定信號(hào)的處理,可以為空,進(jìn)程會(huì)以缺省方式對(duì)信號(hào)處理;第三個(gè)參數(shù)oldact指向的對(duì)象用來(lái)保存返回的原來(lái)對(duì)相應(yīng)信號(hào)的處理,可指定oldact為NULL。如果把第二、第三個(gè)參數(shù)都設(shè)為NULL,那么該函數(shù)可用于檢查信號(hào)的有效性。
第二個(gè)參數(shù)最為重要,其中包含了對(duì)指定信號(hào)的處理、信號(hào)所傳遞的信息、信號(hào)處理函數(shù)執(zhí)行過(guò)程中應(yīng)屏蔽掉哪些信號(hào)等等。
sigaction結(jié)構(gòu)定義如下:
- struct sigaction {
- union{
- __sighandler_t _sa_handler;
- void (*_sa_sigaction)(int,struct siginfo *, void *);
- }_u
- sigset_t sa_mask;
- unsigned long sa_flags;
- }
1、聯(lián)合數(shù)據(jù)結(jié)構(gòu)中的兩個(gè)元素_sa_handler以及*_sa_sigaction指定信號(hào)關(guān)聯(lián)函數(shù),即用戶指定的信號(hào)處理函數(shù)。除了可以是用戶自定義的處理函數(shù)外,還可以為SIG_DFL(采用缺省的處理方式),也可以為SIG_IGN(忽略信號(hào))。
2、由_sa_sigaction是指定的信號(hào)處理函數(shù)帶有三個(gè)參數(shù),是為實(shí)時(shí)信號(hào)而設(shè)的(當(dāng)然同樣支持非實(shí)時(shí)信號(hào)),它指定一個(gè)3參數(shù)信號(hào)處理函數(shù)。***個(gè)參數(shù)為信號(hào)值,第三個(gè)參數(shù)沒(méi)有使用,第二個(gè)參數(shù)是指向siginfo_t結(jié)構(gòu)的指針,結(jié)構(gòu)中包含信號(hào)攜帶的數(shù)據(jù)值,參數(shù)所指向的結(jié)構(gòu)如下:
- siginfo_t {
- int si_signo; /* 信號(hào)值,對(duì)所有信號(hào)有意義*/
- int si_errno; /* errno值,對(duì)所有信號(hào)有意義*/
- int si_code; /* 信號(hào)產(chǎn)生的原因,對(duì)所有信號(hào)有意義*/
- union{ /* 聯(lián)合數(shù)據(jù)結(jié)構(gòu),不同成員適應(yīng)不同信號(hào) */
- //確保分配足夠大的存儲(chǔ)空間
- int _pad[SI_PAD_SIZE];
- //對(duì)SIGKILL有意義的結(jié)構(gòu)
- struct{
- ...
- }...
- ... ...
- ... ...
- //對(duì)SIGILL, SIGFPE, SIGSEGV, SIGBUS有意義的結(jié)構(gòu)
- struct{
- ...
- }...
- ... ...
- }
- }
前面在討論系統(tǒng)調(diào)用sigqueue發(fā)送信號(hào)時(shí),sigqueue的第三個(gè)參數(shù)就是sigval聯(lián)合數(shù)據(jù)結(jié)構(gòu),當(dāng)調(diào)用sigqueue時(shí),該數(shù)據(jù)結(jié)構(gòu)中的數(shù)據(jù)就將拷貝到信號(hào)處理函數(shù)的第二個(gè)參數(shù)中。這樣,在發(fā)送信號(hào)同時(shí),就可以讓信號(hào)傳遞一些附加信息。信號(hào)可以傳遞信息對(duì)程序開發(fā)是非常有意義的。
3、sa_mask指定在信號(hào)處理程序執(zhí)行過(guò)程中,哪些信號(hào)應(yīng)當(dāng)被阻塞。缺省情況下當(dāng)前信號(hào)本身被阻塞,防止信號(hào)的嵌套發(fā)送,除非指定SA_NODEFER或者SA_NOMASK標(biāo)志位。
注:請(qǐng)注意sa_mask指定的信號(hào)阻塞的前提條件,是在由sigaction()安裝信號(hào)的處理函數(shù)執(zhí)行過(guò)程中由sa_mask指定的信號(hào)才被阻塞。
4、sa_flags中包含了許多標(biāo)志位,包括剛剛提到的SA_NODEFER及SA_NOMASK標(biāo)志位。另一個(gè)比較重要的標(biāo)志位是SA_SIGINFO,當(dāng)設(shè)定了該標(biāo)志位時(shí),表示信號(hào)附帶的參數(shù)可以被傳遞到信號(hào)處理函數(shù)中,因此,應(yīng)該為sigaction結(jié)構(gòu)中的sa_sigaction指定處理函數(shù),而不應(yīng)該為sa_handler指定信號(hào)處理函數(shù),否則,設(shè)置該標(biāo)志變得毫無(wú)意義。即使為sa_sigaction指定了信號(hào)處理函數(shù),如果不設(shè)置SA_SIGINFO,信號(hào)處理函數(shù)同樣不能得到信號(hào)傳遞過(guò)來(lái)的數(shù)據(jù),在信號(hào)處理函數(shù)中對(duì)這些信息的訪問(wèn)都將導(dǎo)致段錯(cuò)誤(Segmentation fault)。