【摘要】本文分析了Linux內(nèi)核對于信號的實(shí)現(xiàn)機(jī)制和應(yīng)用層的相關(guān)處理。首先介紹了軟中斷信號的本質(zhì)及信號的兩種不同分類方法尤其是不可靠信號的原理。接著分析了內(nèi)核對于信號的處理流程包括信號的觸發(fā)/注冊/執(zhí)行及注銷等。最后介紹了應(yīng)用層的相關(guān)處理,主要包括信號處理函數(shù)的安裝、信號的發(fā)送、屏蔽阻塞等,最后給了幾個(gè)簡單的應(yīng)用實(shí)例。
?
【關(guān)鍵字】軟中斷信號,signal,sigaction,kill,sigqueue,settimer,sigmask,sigprocmask,sigset_t
?
1???????信號本質(zhì)
軟中斷信號(signal,又簡稱為信號)用來通知進(jìn)程發(fā)生了異步事件。在軟件層次上是對中斷機(jī)制的一種模擬,在原理上,一個(gè)進(jìn)程收到一個(gè)信號與處理器收到一個(gè)中斷請求可以說是一樣的。信號是進(jìn)程間通信機(jī)制中唯一的異步通信機(jī)制,一個(gè)進(jìn)程不必通過任何操作來等待信號的到達(dá),事實(shí)上,進(jìn)程也不知道信號到底什么時(shí)候到達(dá)。進(jìn)程之間可以互相通過系統(tǒng)調(diào)用kill發(fā)送軟中斷信號。內(nèi)核也可以因?yàn)閮?nèi)部事件而給進(jìn)程發(fā)送信號,通知進(jìn)程發(fā)生了某個(gè)事件。信號機(jī)制除了基本通知功能外,還可以傳遞附加信息。
?
收到信號的進(jìn)程對各種信號有不同的處理方法。處理方法可以分為三類:
第一種是類似中斷的處理程序,對于需要處理的信號,進(jìn)程可以指定處理函數(shù),由該函數(shù)來處理。
第二種方法是,忽略某個(gè)信號,對該信號不做任何處理,就象未發(fā)生過一樣。
第三種方法是,對該信號的處理保留系統(tǒng)的默認(rèn)值,這種缺省操作,對大部分的信號的缺省操作是使得進(jìn)程終止。進(jìn)程通過系統(tǒng)調(diào)用signal來指定進(jìn)程對某個(gè)信號的處理行為。
?
2???????信號的種類
可以從兩個(gè)不同的分類角度對信號進(jìn)行分類:
可靠性方面:可靠信號與不可靠信號;
與時(shí)間的關(guān)系上:實(shí)時(shí)信號與非實(shí)時(shí)信號。
?
2.1????可靠信號與不可靠信號
Linux信號機(jī)制基本上是從Unix系統(tǒng)中繼承過來的。早期Unix系統(tǒng)中的信號機(jī)制比較簡單和原始,信號值小于SIGRTMIN的信號都是不可靠信號。這就是"不可靠信號"的來源。它的主要問題是信號可能丟失。
?
隨著時(shí)間的發(fā)展,實(shí)踐證明了有必要對信號的原始機(jī)制加以改進(jìn)和擴(kuò)充。由于原來定義的信號已有許多應(yīng)用,不好再做改動(dòng),最終只好又新增加了一些信號,并在一開始就把它們定義為可靠信號,這些信號支持排隊(duì),不會(huì)丟失。
?
信號值位于SIGRTMIN和SIGRTMAX之間的信號都是可靠信號,可靠信號克服了信號可能丟失的問題。Linux在支持新版本的信號安裝函數(shù)sigation()以及信號發(fā)送函數(shù)sigqueue()的同時(shí),仍然支持早期的signal()信號安裝函數(shù),支持信號發(fā)送函數(shù)kill()。
?
信號的可靠與不可靠只與信號值有關(guān),與信號的發(fā)送及安裝函數(shù)無關(guān)。目前l(fā)inux中的signal()是通過sigation()函數(shù)實(shí)現(xiàn)的,因此,即使通過signal()安裝的信號,在信號處理函數(shù)的結(jié)尾也不必再調(diào)用一次信號安裝函數(shù)。同時(shí),由signal()安裝的實(shí)時(shí)信號支持排隊(duì),同樣不會(huì)丟失。
?
對于目前l(fā)inux的兩個(gè)信號安裝函數(shù):signal()及sigaction()來說,它們都不能把SIGRTMIN以前的信號變成可靠信號(都不支持排隊(duì),仍有可能丟失,仍然是不可靠信號),而且對SIGRTMIN以后的信號都支持排隊(duì)。這兩個(gè)函數(shù)的最大區(qū)別在于,經(jīng)過sigaction安裝的信號都能傳遞信息給信號處理函數(shù),而經(jīng)過signal安裝的信號不能向信號處理函數(shù)傳遞信息。對于信號發(fā)送函數(shù)來說也是一樣的。
?
2.2????實(shí)時(shí)信號與非實(shí)時(shí)信號
早期Unix系統(tǒng)只定義了32種信號,前32種信號已經(jīng)有了預(yù)定義值,每個(gè)信號有了確定的用途及含義,并且每種信號都有各自的缺省動(dòng)作。如按鍵盤的CTRL ^C時(shí),會(huì)產(chǎn)生SIGINT信號,對該信號的默認(rèn)反應(yīng)就是進(jìn)程終止。后32個(gè)信號表示實(shí)時(shí)信號,等同于前面闡述的可靠信號。這保證了發(fā)送的多個(gè)實(shí)時(shí)信號都被接收。
?
非實(shí)時(shí)信號都不支持排隊(duì),都是不可靠信號;實(shí)時(shí)信號都支持排隊(duì),都是可靠信號。
?
3???????信號處理流程
?
?
對于一個(gè)完整的信號生命周期(從信號發(fā)送到相應(yīng)的處理函數(shù)執(zhí)行完畢)來說,可以分為三個(gè)階段:
信號誕生
信號在進(jìn)程中注冊
信號的執(zhí)行和注銷
?
?
3.1??? 信號誕生
信號事件的發(fā)生有兩個(gè)來源:硬件來源(比如我們按下了鍵盤或者其它硬件故障);軟件來源,最常用發(fā)送信號的系統(tǒng)函數(shù)是kill, raise, alarm和setitimer以及sigqueue函數(shù),軟件來源還包括一些非法運(yùn)算等操作。
?
這里按發(fā)出信號的原因簡單分類,以了解各種信號:
(1) 與進(jìn)程終止相關(guān)的信號。當(dāng)進(jìn)程退出,或者子進(jìn)程終止時(shí),發(fā)出這類信號。
(2)?與進(jìn)程例外事件相關(guān)的信號。如進(jìn)程越界,或企圖寫一個(gè)只讀的內(nèi)存區(qū)域(如程序正文區(qū)),或執(zhí)行一個(gè)特權(quán)指令及其他各種硬件錯(cuò)誤。
(3) 與在系統(tǒng)調(diào)用期間遇到不可恢復(fù)條件相關(guān)的信號。如執(zhí)行系統(tǒng)調(diào)用exec時(shí),原有資源已經(jīng)釋放,而目前系統(tǒng)資源又已經(jīng)耗盡。
(4) 與執(zhí)行系統(tǒng)調(diào)用時(shí)遇到非預(yù)測錯(cuò)誤條件相關(guān)的信號。如執(zhí)行一個(gè)并不存在的系統(tǒng)調(diào)用。
(5)?在用戶態(tài)下的進(jìn)程發(fā)出的信號。如進(jìn)程調(diào)用系統(tǒng)調(diào)用kill向其他進(jìn)程發(fā)送信號。
(6)?與終端交互相關(guān)的信號。如用戶關(guān)閉一個(gè)終端,或按下break鍵等情況。
(7) 跟蹤進(jìn)程執(zhí)行的信號。
?
Linux支持的信號列表如下。很多信號是與機(jī)器的體系結(jié)構(gòu)相關(guān)的
信號值 默認(rèn)處理動(dòng)作 發(fā)出信號的原因
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信號
SIGSEGV 11 C 無效的內(nèi)存引用
SIGPIPE 13 A 管道破裂: 寫一個(gè)沒有讀端口的管道
SIGALRM 14 A 由alarm(2)發(fā)出的信號
SIGTERM 15 A 終止信號
SIGUSR1 30,10,16 A 用戶自定義信號1
SIGUSR2 31,12,17 A 用戶自定義信號2
SIGCHLD 20,17,18 B 子進(jìn)程結(jié)束信號
SIGCONT 19,18,25 進(jìn)程繼續(xù)(曾被停止的進(jìn)程)
SIGSTOP 17,19,23 DEF 終止進(jìn)程
SIGTSTP 18,20,24 D 控制終端(tty)上按下停止鍵
SIGTTIN 21,21,26 D 后臺進(jìn)程企圖從控制終端讀
SIGTTOU 22,22,27 D 后臺進(jìn)程企圖從控制終端寫
?
處理動(dòng)作一項(xiàng)中的字母含義如下
A 缺省的動(dòng)作是終止進(jìn)程
B 缺省的動(dòng)作是忽略此信號,將該信號丟棄,不做處理
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)試的過程中(例如ptrace系統(tǒng)調(diào)用)
E 信號不能被捕獲
F 信號不能被忽略
?
3.2????信號在目標(biāo)進(jìn)程中注冊
在進(jìn)程表的表項(xiàng)中有一個(gè)軟中斷信號域,該域中每一位對應(yīng)一個(gè)信號。內(nèi)核給一個(gè)進(jìn)程發(fā)送軟中斷信號的方法,是在進(jìn)程所在的進(jìn)程表項(xiàng)的信號域設(shè)置對應(yīng)于該信號的位。如果信號發(fā)送給一個(gè)正在睡眠的進(jìn)程,如果進(jìn)程睡眠在可被中斷的優(yōu)先級上,則喚醒進(jìn)程;否則僅設(shè)置進(jìn)程表中信號域相應(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)程中未決信號的數(shù)據(jù)成員:?struct sigpending pending:
struct sigpending{
??????? struct sigqueue *head, *tail;
??????? sigset_t signal;
};
?
第三個(gè)成員是進(jìn)程中所有未決信號集,第一、第二個(gè)成員分別指向一個(gè)sigqueue類型的結(jié)構(gòu)鏈(稱之為"未決信號信息鏈")的首尾,信息鏈中的每個(gè)sigqueue結(jié)構(gòu)刻畫一個(gè)特定信號所攜帶的信息,并指向下一個(gè)sigqueue結(jié)構(gòu):
struct sigqueue{
??????? struct sigqueue *next;
??????? siginfo_t info;
}
?
信號在進(jìn)程中注冊指的就是信號值加入到進(jìn)程的未決信號集sigset_t signal(每個(gè)信號占用一位)中,并且信號所攜帶的信息被保留到未決信號信息鏈的某個(gè)sigqueue結(jié)構(gòu)中。只要信號在進(jìn)程的未決信號集中,表明進(jìn)程已經(jīng)知道這些信號的存在,但還沒來得及處理,或者該信號被進(jìn)程阻塞。
?
當(dāng)一個(gè)實(shí)時(shí)信號發(fā)送給一個(gè)進(jìn)程時(shí),不管該信號是否已經(jīng)在進(jìn)程中注冊,都會(huì)被再注冊一次,因此,信號不會(huì)丟失,因此,實(shí)時(shí)信號又叫做"可靠信號"。這意味著同一個(gè)實(shí)時(shí)信號可以在同一個(gè)進(jìn)程的未決信號信息鏈中占有多個(gè)sigqueue結(jié)構(gòu)(進(jìn)程每收到一個(gè)實(shí)時(shí)信號,都會(huì)為它分配一個(gè)結(jié)構(gòu)來登記該信號信息,并把該結(jié)構(gòu)添加在未決信號鏈尾,即所有誕生的實(shí)時(shí)信號都會(huì)在目標(biāo)進(jìn)程中注冊)。
?
當(dāng)一個(gè)非實(shí)時(shí)信號發(fā)送給一個(gè)進(jìn)程時(shí),如果該信號已經(jīng)在進(jìn)程中注冊(通過sigset_t signal指示),則該信號將被丟棄,造成信號丟失。因此,非實(shí)時(shí)信號又叫做"不可靠信號"。這意味著同一個(gè)非實(shí)時(shí)信號在進(jìn)程的未決信號信息鏈中,至多占有一個(gè)sigqueue結(jié)構(gòu)。
?
總之信號注冊與否,與發(fā)送信號的函數(shù)(如kill()或sigqueue()等)以及信號安裝函數(shù)(signal()及sigaction())無關(guān),只與信號值有關(guān)(信號值小于SIGRTMIN的信號最多只注冊一次,信號值在SIGRTMIN及SIGRTMAX之間的信號,只要被進(jìn)程接收到就被注冊)
?
?
3.3????信號的執(zhí)行和注銷
內(nèi)核處理一個(gè)進(jìn)程收到的軟中斷信號是在該進(jìn)程的上下文中,因此,進(jìn)程必須處于運(yùn)行狀態(tài)。當(dāng)其由于被信號喚醒或者正常調(diào)度重新獲得CPU時(shí),在其從內(nèi)核空間返回到用戶空間時(shí)會(huì)檢測是否有信號等待處理。如果存在未決信號等待處理且該信號沒有被進(jìn)程阻塞,則在運(yùn)行相應(yīng)的信號處理函數(shù)前,進(jìn)程會(huì)把信號在未決信號鏈中占有的結(jié)構(gòu)卸掉。
?
對于非實(shí)時(shí)信號來說,由于在未決信號信息鏈中最多只占用一個(gè)sigqueue結(jié)構(gòu),因此該結(jié)構(gòu)被釋放后,應(yīng)該把信號在進(jìn)程未決信號集中刪除(信號注銷完畢);而對于實(shí)時(shí)信號來說,可能在未決信號信息鏈中占用多個(gè)sigqueue結(jié)構(gòu),因此應(yīng)該針對占用sigqueue結(jié)構(gòu)的數(shù)目區(qū)別對待:如果只占用一個(gè)sigqueue結(jié)構(gòu)(進(jìn)程只收到該信號一次),則執(zhí)行完相應(yīng)的處理函數(shù)后應(yīng)該把信號在進(jìn)程的未決信號集中刪除(信號注銷完畢)。否則待該信號的所有sigqueue處理完畢后再在進(jìn)程的未決信號集中刪除該信號。
?
當(dāng)所有未被屏蔽的信號都處理完畢后,即可返回用戶空間。對于被屏蔽的信號,當(dāng)取消屏蔽后,在返回到用戶空間時(shí)會(huì)再次執(zhí)行上述檢查處理的一套流程。
?
內(nèi)核處理一個(gè)進(jìn)程收到的信號的時(shí)機(jī)是在一個(gè)進(jìn)程從內(nèi)核態(tài)返回用戶態(tài)時(shí)。所以,當(dāng)一個(gè)進(jìn)程在內(nèi)核態(tài)下運(yùn)行時(shí),軟中斷信號并不立即起作用,要等到將返回用戶態(tài)時(shí)才處理。進(jìn)程只有處理完信號才會(huì)返回用戶態(tài),進(jìn)程在用戶態(tài)下不會(huì)有未處理完的信號。
?
處理信號有三種類型:進(jìn)程接收到信號后退出;進(jìn)程忽略該信號;進(jìn)程收到信號后執(zhí)行用戶設(shè)定用系統(tǒng)調(diào)用signal的函數(shù)。當(dāng)進(jìn)程接收到一個(gè)它忽略的信號時(shí),進(jìn)程丟棄該信號,就象沒有收到該信號似的繼續(xù)運(yùn)行。如果進(jìn)程收到一個(gè)要捕捉的信號,那么進(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ù)返回再彈出棧頂時(shí),才返回原先進(jìn)入內(nèi)核的地方。這樣做的原因是用戶定義的處理函數(shù)不能且不允許在內(nèi)核態(tài)下執(zhí)行(如果用戶定義的函數(shù)在內(nèi)核態(tài)下運(yùn)行的話,用戶就可以獲得任何權(quán)限)。
?
4???????信號的安裝
如果進(jìn)程要處理某一信號,那么就要在進(jìn)程中安裝該信號。安裝信號主要用來確定信號值及進(jìn)程針對該信號值的動(dòng)作之間的映射關(guān)系,即進(jìn)程將要處理哪個(gè)信號;該信號被傳遞給進(jìn)程時(shí),將執(zhí)行何種操作。
?
linux主要有兩個(gè)函數(shù)實(shí)現(xiàn)信號的安裝:signal()、sigaction()。其中signal()只有兩個(gè)參數(shù),不支持信號傳遞信息,主要是用于前32種非實(shí)時(shí)信號的安裝;而sigaction()是較新的函數(shù)(由兩個(gè)系統(tǒng)調(diào)用實(shí)現(xiàn):sys_signal以及sys_rt_sigaction),有三個(gè)參數(shù),支持信號傳遞信息,主要用來與 sigqueue() 系統(tǒng)調(diào)用配合使用,當(dāng)然,sigaction()同樣支持非實(shí)時(shí)信號的安裝。sigaction()優(yōu)于signal()主要體現(xiàn)在支持信號帶有參數(shù)。
?
4.1????signal()
#include
void (*signal(int signum, void (*handler))(int)))(int);
如果該函數(shù)原型不容易理解的話,可以參考下面的分解方式來理解:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler));
第一個(gè)參數(shù)指定信號的值,第二個(gè)參數(shù)指定針對前面信號值的處理,可以忽略該信號(參數(shù)設(shè)為SIG_IGN);可以采用系統(tǒng)默認(rèn)方式處理信號(參數(shù)設(shè)為SIG_DFL);也可以自己實(shí)現(xiàn)處理方式(參數(shù)指定一個(gè)函數(shù)地址)。
如果signal()調(diào)用成功,返回最后一次為安裝信號signum而調(diào)用signal()時(shí)的handler值;失敗則返回SIG_ERR。
傳遞給信號處理例程的整數(shù)參數(shù)是信號值,這樣可以使得一個(gè)信號處理例程處理多個(gè)信號。
?
#include
#include
#include
void sigroutine(int dunno)
{ /* 信號處理例程,其中dunno將會(huì)得到信號的值 */
??????? 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è)信號的處理方法
??????? signal(SIGINT, sigroutine);
??????? signal(SIGQUIT, sigroutine);
??????? for (;;) ;
}
?
其中信號SIGINT由按下Ctrl-C發(fā)出,信號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)程置于后臺
?[1]+ Stopped ./sig_test
localhost:~$ bg
?[1]+ ./sig_test &
localhost:~$ kill -HUP 463 //向進(jìn)程發(fā)送SIGHUP信號
localhost:~$ Get a signal – SIGHUP
kill -9 463 //向進(jìn)程發(fā)送SIGKILL信號,終止進(jìn)程
localhost:~$
?
4.2????sigaction()
#include
int sigaction(int signum,const?struct sigaction *act,struct sigaction *oldact));
sigaction函數(shù)用于改變進(jìn)程接收到特定信號后的行為。該函數(shù)的第一個(gè)參數(shù)為信號的值,可以為除SIGKILL及SIGSTOP外的任何一個(gè)特定有效的信號(為這兩個(gè)信號定義自己的處理函數(shù),將導(dǎo)致信號安裝錯(cuò)誤)。第二個(gè)參數(shù)是指向結(jié)構(gòu)sigaction的一個(gè)實(shí)例的指針,在結(jié)構(gòu)sigaction的實(shí)例中,指定了對特定信號的處理,可以為空,進(jìn)程會(huì)以缺省方式對信號處理;第三個(gè)參數(shù)oldact指向的對象用來保存返回的原來對相應(yīng)信號的處理,可指定oldact為NULL。如果把第二、第三個(gè)參數(shù)都設(shè)為NULL,那么該函數(shù)可用于檢查信號的有效性。
?
第二個(gè)參數(shù)最為重要,其中包含了對指定信號的處理、信號所傳遞的信息、信號處理函數(shù)執(zhí)行過程中應(yīng)屏蔽掉哪些信號等等。
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指定信號關(guān)聯(lián)函數(shù),即用戶指定的信號處理函數(shù)。除了可以是用戶自定義的處理函數(shù)外,還可以為SIG_DFL(采用缺省的處理方式),也可以為SIG_IGN(忽略信號)。
?
2、由_sa_sigaction是指定的信號處理函數(shù)帶有三個(gè)參數(shù),是為實(shí)時(shí)信號而設(shè)的(當(dāng)然同樣支持非實(shí)時(shí)信號),它指定一個(gè)3參數(shù)信號處理函數(shù)。第一個(gè)參數(shù)為信號值,第三個(gè)參數(shù)沒有使用,第二個(gè)參數(shù)是指向siginfo_t結(jié)構(gòu)的指針,結(jié)構(gòu)中包含信號攜帶的數(shù)據(jù)值,參數(shù)所指向的結(jié)構(gòu)如下:
siginfo_t {
????????????????? int????? si_signo;? /* 信號值,對所有信號有意義*/
????????????????? int????? si_errno;? /* errno值,對所有信號有意義*/
????????????????? int????? si_code;?? /* 信號產(chǎn)生的原因,對所有信號有意義*/
?????????????????????????????? union{???????????????????????????? ? /* 聯(lián)合數(shù)據(jù)結(jié)構(gòu),不同成員適應(yīng)不同信號 */
?????????????????????????????????????? //確保分配足夠大的存儲(chǔ)空間
?????????????????????????????????????? int _pad[SI_PAD_SIZE];
?????????????????????????????????????? //對SIGKILL有意義的結(jié)構(gòu)
?????????????????????????????????????? struct{
????????????????????????????????????????????????????? ...
?????????????????????????????????????????????? ? }...
?????????????????????????????????????????????? ... ...
?????????????????????????????????????????????? ... ...???????????????????????????????
?????????????????????????????????????? //對SIGILL, SIGFPE, SIGSEGV, SIGBUS有意義的結(jié)構(gòu)
????? ??????????????????????????? struct{
????????????????????????????????????????????????????? ...
?????????????????????????????????????????????? ? }...
?????????????????????????????????????????????? ... ...
?????????????????????????????????????? ? }
}
?
前面在討論系統(tǒng)調(diào)用sigqueue發(fā)送信號時(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ù)就將拷貝到信號處理函數(shù)的第二個(gè)參數(shù)中。這樣,在發(fā)送信號同時(shí),就可以讓信號傳遞一些附加信息。信號可以傳遞信息對程序開發(fā)是非常有意義的。
?
3、sa_mask指定在信號處理程序執(zhí)行過程中,哪些信號應(yīng)當(dāng)被阻塞。缺省情況下當(dāng)前信號本身被阻塞,防止信號的嵌套發(fā)送,除非指定SA_NODEFER或者SA_NOMASK標(biāo)志位。
注:請注意sa_mask指定的信號阻塞的前提條件,是在由sigaction()安裝信號的處理函數(shù)執(zhí)行過程中由sa_mask指定的信號才被阻塞。
?
4、sa_flags中包含了許多標(biāo)志位,包括剛剛提到的SA_NODEFER及SA_NOMASK標(biāo)志位。另一個(gè)比較重要的標(biāo)志位是SA_SIGINFO,當(dāng)設(shè)定了該標(biāo)志位時(shí),表示信號附帶的參數(shù)可以被傳遞到信號處理函數(shù)中,因此,應(yīng)該為sigaction結(jié)構(gòu)中的sa_sigaction指定處理函數(shù),而不應(yīng)該為sa_handler指定信號處理函數(shù),否則,設(shè)置該標(biāo)志變得毫無意義。即使為sa_sigaction指定了信號處理函數(shù),如果不設(shè)置SA_SIGINFO,信號處理函數(shù)同樣不能得到信號傳遞過來的數(shù)據(jù),在信號處理函數(shù)中對這些信息的訪問都將導(dǎo)致段錯(cuò)誤(Segmentation fault)。
?
?
5???????信號的發(fā)送
發(fā)送信號的主要函數(shù)有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。
?
5.1????kill()
#include
#include
int kill(pid_t pid,int signo)
?
該系統(tǒng)調(diào)用可以用來向任何進(jìn)程或進(jìn)程組發(fā)送任何信號。參數(shù)pid的值為信號的接收進(jìn)程
pid>0 進(jìn)程ID為pid的進(jìn)程
pid=0 同一個(gè)進(jìn)程組的進(jìn)程
pid<0 pid!=-1 進(jìn)程組ID為 -pid的所有進(jìn)程
pid=-1 除發(fā)送進(jìn)程自身外,所有進(jìn)程ID大于1的進(jìn)程
?
Sinno是信號值,當(dāng)為0時(shí)(即空信號),實(shí)際不發(fā)送任何信號,但照常進(jìn)行錯(cuò)誤檢查,因此,可用于檢查目標(biāo)進(jìn)程是否存在,以及當(dāng)前進(jìn)程是否具有向目標(biāo)發(fā)送信號的權(quán)限(root權(quán)限的進(jìn)程可以向任何進(jìn)程發(fā)送信號,非root權(quán)限的進(jìn)程只能向?qū)儆谕粋€(gè)session或者同一個(gè)用戶的進(jìn)程發(fā)送信號)。
?
Kill()最常用于pid>0時(shí)的信號發(fā)送。該調(diào)用執(zhí)行成功時(shí),返回值為0;錯(cuò)誤時(shí),返回-1,并設(shè)置相應(yīng)的錯(cuò)誤代碼errno。下面是一些可能返回的錯(cuò)誤代碼:
EINVAL:指定的信號sig無效。
ESRCH:參數(shù)pid指定的進(jìn)程或進(jìn)程組不存在。注意,在進(jìn)程表項(xiàng)中存在的進(jìn)程,可能是一個(gè)還沒有被wait收回,但已經(jīng)終止執(zhí)行的僵死進(jìn)程。
EPERM:?進(jìn)程沒有權(quán)力將這個(gè)信號發(fā)送到指定接收信號的進(jìn)程。因?yàn)椋粋€(gè)進(jìn)程被允許將信號發(fā)送到進(jìn)程pid時(shí),必須擁有root權(quán)力,或者是發(fā)出調(diào)用的進(jìn)程的UID 或EUID與指定接收的進(jìn)程的UID或保存用戶ID(savedset-user-ID)相同。如果參數(shù)pid小于-1,即該信號發(fā)送給一個(gè)組,則該錯(cuò)誤表示組中有成員進(jìn)程不能接收該信號。
?
5.2????sigqueue()
#include
#include
int sigqueue(pid_t pid, int sig,?const union sigval val)
調(diào)用成功返回 0;否則,返回 -1。
?
sigqueue()是比較新的發(fā)送信號系統(tǒng)調(diào)用,主要是針對實(shí)時(shí)信號提出的(當(dāng)然也支持前32種),支持信號帶有參數(shù),與函數(shù)sigaction()配合使用。
sigqueue的第一個(gè)參數(shù)是指定接收信號的進(jìn)程ID,第二個(gè)參數(shù)確定即將發(fā)送的信號,第三個(gè)參數(shù)是一個(gè)聯(lián)合數(shù)據(jù)結(jié)構(gòu)union sigval,指定了信號傳遞的參數(shù),即通常所說的4字節(jié)值。
typedef union sigval {
?????????????? int? sival_int;
?????????????? void *sival_ptr;
}sigval_t;
?
sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個(gè)進(jìn)程發(fā)送信號,而不能發(fā)送信號給一個(gè)進(jìn)程組。如果signo=0,將會(huì)執(zhí)行錯(cuò)誤檢查,但實(shí)際上不發(fā)送任何信號,0值信號可用于檢查pid的有效性以及當(dāng)前進(jìn)程是否有權(quán)限向目標(biāo)進(jìn)程發(fā)送信號。
?
在調(diào)用sigqueue時(shí),sigval_t指定的信息會(huì)拷貝到對應(yīng)sig 注冊的3參數(shù)信號處理函數(shù)的siginfo_t結(jié)構(gòu)中,這樣信號處理函數(shù)就可以處理這些信息了。由于sigqueue系統(tǒng)調(diào)用支持發(fā)送帶參數(shù)信號,所以比kill()系統(tǒng)調(diào)用的功能要靈活和強(qiáng)大得多。
?
5.3????alarm()
#include
unsigned int alarm(unsigned int seconds)
系統(tǒng)調(diào)用alarm安排內(nèi)核為調(diào)用進(jìn)程在指定的seconds秒后發(fā)出一個(gè)SIGALRM的信號。如果指定的參數(shù)seconds為0,則不再發(fā)送 SIGALRM信號。后一次設(shè)定將取消前一次的設(shè)定。該調(diào)用返回值為上次定時(shí)調(diào)用到發(fā)送之間剩余的時(shí)間,或者因?yàn)闆]有前一次定時(shí)調(diào)用而返回0。
?
注意,在使用時(shí),alarm只設(shè)定為發(fā)送一次信號,如果要多次發(fā)送,就要多次使用alarm調(diào)用。
?
5.4????setitimer()
現(xiàn)在的系統(tǒng)中很多程序不再使用alarm調(diào)用,而是使用setitimer調(diào)用來設(shè)置定時(shí)器,用getitimer來得到定時(shí)器的狀態(tài),這兩個(gè)調(diào)用的聲明格式如下:
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
在使用這兩個(gè)調(diào)用的進(jìn)程中加入以下頭文件:
#include
?
該系統(tǒng)調(diào)用給進(jìn)程提供了三個(gè)定時(shí)器,它們各自有其獨(dú)有的計(jì)時(shí)域,當(dāng)其中任何一個(gè)到達(dá),就發(fā)送一個(gè)相應(yīng)的信號給進(jìn)程,并使得計(jì)時(shí)器重新開始。三個(gè)計(jì)時(shí)器由參數(shù)which指定,如下所示:
TIMER_REAL:按實(shí)際時(shí)間計(jì)時(shí),計(jì)時(shí)到達(dá)將給進(jìn)程發(fā)送SIGALRM信號。
ITIMER_VIRTUAL:僅當(dāng)進(jìn)程執(zhí)行時(shí)才進(jìn)行計(jì)時(shí)。計(jì)時(shí)到達(dá)將發(fā)送SIGVTALRM信號給進(jìn)程。
ITIMER_PROF:當(dāng)進(jìn)程執(zhí)行時(shí)和系統(tǒng)為該進(jìn)程執(zhí)行動(dòng)作時(shí)都計(jì)時(shí)。與ITIMER_VIR-TUAL是一對,該定時(shí)器經(jīng)常用來統(tǒng)計(jì)進(jìn)程在用戶態(tài)和內(nèi)核態(tài)花費(fèi)的時(shí)間。計(jì)時(shí)到達(dá)將發(fā)送SIGPROF信號給進(jìn)程。
?
定時(shí)器中的參數(shù)value用來指明定時(shí)器的時(shí)間,其結(jié)構(gòu)如下:
struct itimerval {
??????? struct timeval it_interval; /* 下一次的取值 */
??????? struct timeval it_value; /* 本次的設(shè)定值 */
};
?
該結(jié)構(gòu)中timeval結(jié)構(gòu)定義如下:
struct timeval {
??????? long tv_sec; /* 秒 */
??????? long tv_usec; /* 微秒,1秒 = 1000000 微秒*/
};
?
在setitimer 調(diào)用中,參數(shù)ovalue如果不為空,則其中保留的是上次調(diào)用設(shè)定的值。定時(shí)器將it_value遞減到0時(shí),產(chǎn)生一個(gè)信號,并將it_value的值設(shè)定為it_interval的值,然后重新開始計(jì)時(shí),如此往復(fù)。當(dāng)it_value設(shè)定為0時(shí),計(jì)時(shí)器停止,或者當(dāng)它計(jì)時(shí)到期,而it_interval 為0時(shí)停止。調(diào)用成功時(shí),返回0;錯(cuò)誤時(shí),返回-1,并設(shè)置相應(yīng)的錯(cuò)誤代碼errno:
EFAULT:參數(shù)value或ovalue是無效的指針。
EINVAL:參數(shù)which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一個(gè)。
下面是關(guān)于setitimer調(diào)用的一個(gè)簡單示范,在該例子中,每隔一秒發(fā)出一個(gè)SIGALRM,每隔0.5秒發(fā)出一個(gè)SIGVTALRM信號:
?
#include
#include
#include
#include
int sec;
?
void sigroutine(int signo) {
??????? switch (signo) {
?????? ?case SIGALRM:
??????? printf("Catch a signal -- SIGALRM ");
??????? break;
??????? case SIGVTALRM:
??????? printf("Catch a signal -- SIGVTALRM ");
??????? break;
??????? }
??????? return;
}
?
int main()
{
??????? struct itimerval value,ovalue,value2;
???? ???sec = 5;
?
??????? printf("process id is %d ",getpid());
??????? signal(SIGALRM, sigroutine);
??????? signal(SIGVTALRM, sigroutine);
?
??????? value.it_value.tv_sec = 1;
??????? value.it_value.tv_usec = 0;
??????? value.it_interval.tv_sec = 1;
??????? value.it_interval.tv_usec = 0;
??????? setitimer(ITIMER_REAL, &value, &ovalue);
?
??????? value2.it_value.tv_sec = 0;
??????? value2.it_value.tv_usec = 500000;
??????? value2.it_interval.tv_sec = 0;
??????? value2.it_interval.tv_usec = 500000;
??????? setitimer(ITIMER_VIRTUAL, &value2, &ovalue);
?
??????? for (;;) ;
}
?
該例子的屏幕拷貝如下:
localhost:~$ ./timer_test
process id is 579
Catch a signal – SIGVTALRM
Catch a signal – SIGALRM
Catch a signal – SIGVTALRM
Catch a signal – SIGVTALRM
Catch a signal – SIGALRM
Catch a signal –GVTALRM
?
5.5????abort()
#include
void abort(void);
向進(jìn)程發(fā)送SIGABORT信號,默認(rèn)情況下進(jìn)程會(huì)異常退出,當(dāng)然可定義自己的信號處理函數(shù)。即使SIGABORT被進(jìn)程設(shè)置為阻塞信號,調(diào)用abort()后,SIGABORT仍然能被進(jìn)程接收。該函數(shù)無返回值。
?
5.6????raise()
#include
int raise(int signo)
向進(jìn)程本身發(fā)送信號,參數(shù)為即將發(fā)送的信號值。調(diào)用成功返回 0;否則,返回 -1。
?
6???????信號集及信號集操作函數(shù):
信號集被定義為一種數(shù)據(jù)類型:
typedef struct {
?????????????????????? unsigned long sig[_NSIG_WORDS];
} sigset_t
信號集用來描述信號的集合,每個(gè)信號占用一位。Linux所支持的所有信號可以全部或部分的出現(xiàn)在信號集中,主要與信號阻塞相關(guān)函數(shù)配合使用。下面是為信號集操作定義的相關(guān)函數(shù):
?
#include
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum)
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
sigemptyset(sigset_t *set)初始化由set指定的信號集,信號集里面的所有信號被清空;
sigfillset(sigset_t *set)調(diào)用該函數(shù)后,set指向的信號集中將包含linux支持的64種信號;
sigaddset(sigset_t *set, int signum)在set指向的信號集中加入signum信號;
sigdelset(sigset_t *set, int signum)在set指向的信號集中刪除signum信號;
sigismember(const sigset_t *set, int signum)判定信號signum是否在set指向的信號集中。
?
7???????信號阻塞與信號未決:
每個(gè)進(jìn)程都有一個(gè)用來描述哪些信號遞送到進(jìn)程時(shí)將被阻塞的信號集,該信號集中的所有信號在遞送到進(jìn)程后都將被阻塞。下面是與信號阻塞相關(guān)的幾個(gè)函數(shù):
#include
int? sigprocmask(int? how,? const? sigset_t *set, sigset_t *oldset));
int sigpending(sigset_t *set));
int sigsuspend(const sigset_t *mask));
?
sigprocmask()函數(shù)能夠根據(jù)參數(shù)how來實(shí)現(xiàn)對信號集的操作,操作主要有三種:
SIG_BLOCK 在進(jìn)程當(dāng)前阻塞信號集中添加set指向信號集中的信號
SIG_UNBLOCK 如果進(jìn)程阻塞信號集中包含set指向信號集中的信號,則解除對該信號的阻塞
SIG_SETMASK 更新進(jìn)程阻塞信號集為set指向的信號集
?
sigpending(sigset_t *set))獲得當(dāng)前已遞送到進(jìn)程,卻被阻塞的所有信號,在set指向的信號集中返回結(jié)果。
?
sigsuspend(const sigset_t *mask))用于在接收到某個(gè)信號之前, 臨時(shí)用mask替換進(jìn)程的信號掩碼, 并暫停進(jìn)程執(zhí)行,直到收到信號為止。sigsuspend 返回后將恢復(fù)調(diào)用之前的信號掩碼。信號處理函數(shù)完成后,進(jìn)程將繼續(xù)執(zhí)行。該系統(tǒng)調(diào)用始終返回-1,并將errno設(shè)置為EINTR。
?
?
8???????信號應(yīng)用實(shí)例
linux下的信號應(yīng)用并沒有想象的那么恐怖,程序員所要做的最多只有三件事情:
安裝信號(推薦使用sigaction());
實(shí)現(xiàn)三參數(shù)信號處理函數(shù),handler(int signal,struct siginfo *info, void *);
發(fā)送信號,推薦使用sigqueue()。
實(shí)際上,對有些信號來說,只要安裝信號就足夠了(信號處理方式采用缺省或忽略)。其他可能要做的無非是與信號集相關(guān)的幾種操作。
?
實(shí)例一:信號發(fā)送及處理
實(shí)現(xiàn)一個(gè)信號接收程序sigreceive(其中信號安裝由sigaction())。
#include
#include
#include
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
??????? struct sigaction act;??
??????? int sig;
????????sig=atoi(argv[1]);
???????
??????? sigemptyset(&act.sa_mask);
??????? act.sa_flags=SA_SIGINFO;
??????? act.sa_sigaction=new_op;
???????
??????? if(sigaction(sig,&act,NULL) < 0)
??????? {
??????????????? printf("install sigal error\n");
??????? }
???????
??????? while(1)
??????? {
??????????????? sleep(2);
??????????????? printf("wait for the signal\n");
??????? }
}
?
void new_op(int signum,siginfo_t *info,void *myact)
{
??????? printf("receive signal %d", signum);
??????? sleep(5);
}
說明,命令行參數(shù)為信號值,后臺運(yùn)行sigreceive signo &,可獲得該進(jìn)程的ID,假設(shè)為pid,然后再另一終端上運(yùn)行kill -s signo pid驗(yàn)證信號的發(fā)送接收及處理。同時(shí),可驗(yàn)證信號的排隊(duì)問題。
?
實(shí)例二:信號傳遞附加信息
主要包括兩個(gè)實(shí)例:
向進(jìn)程本身發(fā)送信號,并傳遞指針參數(shù)
#include
#include
#include
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
??????? struct sigaction act;??
????????union sigval mysigval;
??????? int i;
??????? int sig;
??????? pid_t pid;?????????
??????? char data[10];
??????? memset(data,0,sizeof(data));
??????? for(i=0;i < 5;i++)
??????????????? data[i]='2';
??????? mysigval.sival_ptr=data;
???????
??????? sig=atoi(argv[1]);
??????? pid=getpid();
???????
??????? sigemptyset(&act.sa_mask);
??????? act.sa_sigaction=new_op;//三參數(shù)信號處理函數(shù)
????????act.sa_flags=SA_SIGINFO;//信息傳遞開關(guān),允許傳說參數(shù)信息給new_op
??????? if(sigaction(sig,&act,NULL) < 0)
??????? {
??????????????? printf("install sigal error\n");
??????? }
??????? while(1)
??????? {
??????????????? sleep(2);
??????????????? printf("wait for the signal\n");
????????????????sigqueue(pid,sig,mysigval);//向本進(jìn)程發(fā)送信號,并傳遞附加信息
??????? }
}
void new_op(int signum,siginfo_t *info,void *myact)//三參數(shù)信號處理函數(shù)的實(shí)現(xiàn)
{
??????? int i;
??????? for(i=0;i<10;i++)
??????? {
??????????????? printf("%c\n ",(*( (char*)((*info).si_ptr)+i)));
??????? }
??????? printf("handle signal %d over;",signum);
}
?
這個(gè)例子中,信號實(shí)現(xiàn)了附加信息的傳遞,信號究竟如何對這些信息進(jìn)行處理則取決于具體的應(yīng)用。
?
不同進(jìn)程間傳遞整型參數(shù):
把1中的信號發(fā)送和接收放在兩個(gè)程序中,并且在發(fā)送過程中傳遞整型參數(shù)。
信號接收程序:
#include
#include
#include
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
??????? struct sigaction act;
??????? int sig;
??????? pid_t pid;?????????
???????
??????? pid=getpid();
??????? sig=atoi(argv[1]);?????
???????
??????? sigemptyset(&act.sa_mask);
??????? act.sa_sigaction=new_op;
??????? act.sa_flags=SA_SIGINFO;
??????? if(sigaction(sig,&act,NULL)<0)
??????? {
??????????????? printf("install sigal error\n");
??????? }
??????? while(1)
??????? {
??????????????? sleep(2);
??????????????? printf("wait for the signal\n");
??????? }
}
void new_op(int signum,siginfo_t *info,void *myact)
{
??????? printf("the int value is %d \n",info->si_int);
}
?
?
信號發(fā)送程序:
命令行第二個(gè)參數(shù)為信號值,第三個(gè)參數(shù)為接收進(jìn)程ID。
?
#include
#include
#include
#include
main(int argc,char**argv)
{
??????? pid_t pid;
??????? int signum;
??????? union sigval mysigval;
??????? signum=atoi(argv[1]);
??????? pid=(pid_t)atoi(argv[2]);
??????? mysigval.sival_int=8;//不代表具體含義,只用于說明問題
??????? if(sigqueue(pid,signum,mysigval)==-1)
??????????????? printf("send error\n");
??????? sleep(2);
}
?
?
注:實(shí)例2的兩個(gè)例子側(cè)重點(diǎn)在于用信號來傳遞信息,目前關(guān)于在linux下通過信號傳遞信息的實(shí)例非常少,倒是Unix下有一些,但傳遞的基本上都是關(guān)于傳遞一個(gè)整數(shù)
?
實(shí)例三:信號阻塞及信號集操作
#include "signal.h"
#include "unistd.h"
static void my_op(int);
main()
{
??????? sigset_t new_mask,old_mask,pending_mask;
??????? struct sigaction act;
??????? sigemptyset(&act.sa_mask);
??????? act.sa_flags=SA_SIGINFO;
??????? act.sa_sigaction=(void*)my_op;
??????? if(sigaction(SIGRTMIN+10,&act,NULL))
??????????????? printf("install signal SIGRTMIN+10 error\n");
??????? sigemptyset(&new_mask);
??????? sigaddset(&new_mask,SIGRTMIN+10);
??????? if(sigprocmask(SIG_BLOCK, &new_mask,&old_mask))
??????????????? printf("block signal SIGRTMIN+10 error\n");
??????? sleep(10);
??????? printf("now begin to get pending mask and unblock SIGRTMIN+10\n");
??????? if(sigpending(&pending_mask)<0)
??????????????? printf("get pending mask error\n");
??????? if(sigismember(&pending_mask,SIGRTMIN+10))
??????????????? printf("signal SIGRTMIN+10 is pending\n");
??????? if(sigprocmask(SIG_SETMASK,&old_mask,NULL)<0)
??????????????? printf("unblock signal error\n");
??????? printf("signal unblocked\n");
??????? sleep(10);
}
?
static void my_op(int signum)
{
??????? printf("receive signal %d \n",signum);
}
?
編譯該程序,并以后臺方式運(yùn)行。在另一終端向該進(jìn)程發(fā)送信號(運(yùn)行kill -s 42 pid,SIGRTMIN+10為42),查看結(jié)果可以看出幾個(gè)關(guān)鍵函數(shù)的運(yùn)行機(jī)制,信號集相關(guān)操作比較簡單。
?
評論
查看更多