信號(hào)就是軟中斷。
信號(hào)提供了異步處理事件的一種方式。例如,用戶在終端按下結(jié)束進(jìn)程鍵,使一個(gè)進(jìn)程提前終止。
?
1 信號(hào)的概念每一個(gè)信號(hào)都有一個(gè)名字,它們的名字都以SIG打頭。例如,每當(dāng)進(jìn)程調(diào)用了abort函數(shù)時(shí),都會(huì)產(chǎn)生一個(gè)SIGABRT信號(hào)。
每一個(gè)信號(hào)對(duì)應(yīng)一個(gè)正整數(shù),定義在頭文件<signal.h>中。
沒有信號(hào)對(duì)應(yīng)整數(shù)0,kill函數(shù)使用信號(hào)編號(hào)0表示一種特殊情況,所以信號(hào)編號(hào)0又叫做空信號(hào)(null signal)。
下面的各種情況會(huì)產(chǎn)生一個(gè)信號(hào):
對(duì)于進(jìn)程來說,信號(hào)是隨機(jī)產(chǎn)生的,所以進(jìn)程不能簡(jiǎn)單地根據(jù)檢測(cè)某個(gè)變量是否改變來判斷信號(hào)是否發(fā)生,而應(yīng)該告訴內(nèi)核“當(dāng)這個(gè)信號(hào)發(fā)生時(shí),做下面的這些事情”。
我們告訴內(nèi)核當(dāng)某個(gè)信號(hào)發(fā)生時(shí)做的事情叫做信號(hào)處理函數(shù)。信號(hào)處理函數(shù)有三種功能可供選擇:
?
對(duì)于一些信號(hào)發(fā)生時(shí),會(huì)造成進(jìn)程終止,同時(shí)生成一個(gè)core文件,該core文件記錄了該進(jìn)程終止時(shí)的內(nèi)存情況,可以幫助調(diào)試和調(diào)查進(jìn)程的終止?fàn)顟B(tài)。
有幾種情況不會(huì)生成core文件:
函數(shù)聲明
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
? ? Returns: PRevious disposition of signal if OK, SIG_ERR on err.
?函數(shù)聲明解析:
void ? (*signal(int ? signr, ? void ? (*handler)(int)))(int);?================================================?handler是一個(gè)函數(shù)指針,指向參數(shù)為單參數(shù)int,返回類型void的函數(shù)?signal是一個(gè)函數(shù)指針、這個(gè)函數(shù)指針指向一個(gè)參數(shù)為一個(gè)int型和一個(gè)handler型的指針、返回值是一個(gè)指向參數(shù)為int、返回值是void的函數(shù)的指針的指針。
總結(jié)一下:?? ? 這個(gè)復(fù)雜的聲明可以用下面2種比較簡(jiǎn)單的型式表達(dá)出來,如下:?第一種型式如下: ? ??typedef ? void ? (*handler_pt)(int);?handler_pt ? signal1(int ? signum,handler_pt ? ahandler);?第二種型式如下:?typedef ? void ? handler_t(int);?handler_t* ? signal2(int ? signum, ? handler_t* ? ahandler);?------------------------------------------------------?以上這兩種形式結(jié)果是等價(jià)的,但也有區(qū)別,第一種形式定義的是函數(shù)指針類型,?sizeof(handler_pt)=4//borland ? c++ ? 5.6.4 ? for ? win ? 32,windos ? xp ? 32 ? platform?第二種形式定義的是函數(shù)類型,如果對(duì)他使用sizeof(handler_t)會(huì)提示:?sizeof ? may ? not ? be ? applied ? to ? a ? function
參數(shù)說明:
在上面的聲明解析中我們可以看到,使用typedef可以簡(jiǎn)化signal函數(shù)的聲明,后面對(duì)signal函數(shù)的調(diào)用也將使用簡(jiǎn)化后的聲明:
typedef void Sigfunc(int);
Sigfunc *signal(int, Sigfunc *);
?
?Example
該例子的作用是捕獲兩個(gè)用戶自定義的信號(hào),并打印相關(guān)的信號(hào)信息。
使用函數(shù)pause來使程序掛起,知道接收到信號(hào)。
Code
#include "apue.h"
?
staticvoid sig_usr(int); ? /* one handler for both signals */
?? ? ? ? ?
int ? ? ?
main(void)
{ ? ? ? ?
? ? if (signal(SIGUSR1, sig_usr) == SIG_ERR)
? ? ? ? err_sys("can't catch SIGUSR1");
? ? if (signal(SIGUSR2, sig_usr) == SIG_ERR)
? ? ? ? err_sys("can't catch SIGUSR2");
? ? for ( ; ; )
? ? ? ? pause();
}? ?
?? ?
staticvoid
sig_usr(int signo)? ? ? /* argument is signal number */
{? ?
? ? if (signo == SIGUSR1)
? ? ? ? printf("received SIGUSR1/n");
? ? else if (signo == SIGUSR2)
? ? ? ? printf("received SIGUSR2/n");
? ??else
? ? ? ??err_dump("received signal %d/n", signo);
}
執(zhí)行結(jié)果:

執(zhí)行時(shí),我們先讓該程序后臺(tái)執(zhí)行,然后調(diào)用kill命令向該進(jìn)程發(fā)送信號(hào)。
kill并不真的會(huì)殺死進(jìn)程,而只是發(fā)送信號(hào)。所以kill并不是很準(zhǔn)確的描述了該命令的作用。
當(dāng)我們調(diào)用kill 2081命令時(shí),進(jìn)程被終止,因?yàn)樵谛盘?hào)處理函數(shù)中并沒有處理該信號(hào),而該信號(hào)的默認(rèn)處理程序?yàn)榻K止進(jìn)程。
?
程序啟動(dòng)態(tài)程序執(zhí)行時(shí),所有信號(hào)的狀態(tài)都為默認(rèn)值或者被忽略。
如果程序調(diào)用了exec系函數(shù),則會(huì)改變信號(hào)的自定義處理函數(shù)為它的默認(rèn)處理程序,因?yàn)樵谠瓉淼某绦蛑械奶幚砗瘮?shù)地址對(duì)于新的程序來說是沒有意義的。
例如,在一個(gè)交互式的shell中,啟動(dòng)一個(gè)后臺(tái)進(jìn)程,會(huì)設(shè)置該進(jìn)程的中斷和退出信號(hào)的處理動(dòng)作為忽略,這樣,當(dāng)用戶在shell中鍵入中斷命令時(shí),只會(huì)中斷前臺(tái)進(jìn)程,而不會(huì)影響后臺(tái)進(jìn)程。
這個(gè)例子也告訴我們了signal函數(shù)的一個(gè)限制:我們無法確認(rèn)當(dāng)前進(jìn)程的一些信號(hào)的處理動(dòng)作,除非我們現(xiàn)在改變它們。后面我們將學(xué)習(xí)sigaction函數(shù)來確認(rèn)一個(gè)信號(hào)的處理動(dòng)作,而不需要改變它們。
?
程序創(chuàng)建當(dāng)調(diào)用fork函數(shù)時(shí),子進(jìn)程繼承了父進(jìn)程的信號(hào)處理函數(shù)。因?yàn)樽舆M(jìn)程拷貝了父進(jìn)程的內(nèi)存,所以信號(hào)處理函數(shù)的地址對(duì)于子進(jìn)程來說也是有意義的。
?
3 不可靠的信號(hào)(Unreliable Signals)在早期的Unix系統(tǒng)中,信號(hào)是不可靠的。
不可靠的意思是,信號(hào)是有可能丟失的。即信號(hào)發(fā)生了,但是進(jìn)程沒有捕獲它。
我們希望內(nèi)核可以記住信號(hào),當(dāng)我們r(jià)eady時(shí),告訴我們?cè)撔盘?hào)發(fā)生,讓我們?nèi)ヌ幚怼?/p>
早期的系統(tǒng),對(duì)于信號(hào)機(jī)制的實(shí)現(xiàn)還有一個(gè)問題:當(dāng)信號(hào)發(fā)生,執(zhí)行了信號(hào)處理函數(shù),該信號(hào)的處理函數(shù)就被置為默認(rèn)的信號(hào)處理程序。因此,早期的關(guān)于信號(hào)的程序框架如下所示:

這段代碼的問題在于,在SIGINT信號(hào)發(fā)生后,且在對(duì)它的信號(hào)處理函數(shù)重置為sig_int前,有一個(gè)時(shí)間差,在這個(gè)時(shí)間差內(nèi),可能再發(fā)生一次SIGINT信號(hào)。
如果第二次SIGINT發(fā)生在信號(hào)處理函數(shù)重置前,則會(huì)執(zhí)行它的默認(rèn)處理動(dòng)作,即終止進(jìn)程。
早期實(shí)現(xiàn)還有一個(gè)問題,就是如果進(jìn)程不希望某個(gè)信號(hào)發(fā)生,它只能選擇忽略它,而無法將該信號(hào)關(guān)閉。
一種使用場(chǎng)景是:我們不希望被信號(hào)打斷,但是希望記住它們發(fā)生過。代碼可能如下:

在這里,我們假設(shè)該信號(hào)只發(fā)生一次。
代碼的目的在于:我們等待信號(hào)發(fā)生,信號(hào)發(fā)生之前,進(jìn)程停止,等待。
代碼的問題在于,有一個(gè)時(shí)間差,可能會(huì)發(fā)生異常情況,如果代碼的執(zhí)行序列如下:
1 信號(hào)發(fā)生
2 while (sig_int_flag == 0)
3 sig_int_flag= 1
4 pause()
這時(shí),進(jìn)程暫停掛起,等待信號(hào)發(fā)生,但是實(shí)際上該信號(hào)已經(jīng)發(fā)生過了。這就導(dǎo)致了信號(hào)沒有被捕獲。
?
4 可中斷系統(tǒng)調(diào)用早期Unix操作系統(tǒng)的一個(gè)特性是:如果一個(gè)進(jìn)程阻塞在一個(gè)“慢”系統(tǒng)調(diào)用,則該進(jìn)程會(huì)收到一個(gè)信號(hào),導(dǎo)致該進(jìn)程被中斷。該系統(tǒng)調(diào)用返回一個(gè)錯(cuò)誤,并且errno設(shè)置為EINTR。
系統(tǒng)調(diào)用被分為兩類:慢系統(tǒng)調(diào)用和其他系統(tǒng)調(diào)用。慢系統(tǒng)調(diào)用是那些可能永久阻塞的系統(tǒng)調(diào)用。慢系統(tǒng)調(diào)用包括:
對(duì)于可中斷系統(tǒng)調(diào)用,我們需要在代碼中處理errno EINTR:

為了避免需要顯式處理可中斷系統(tǒng)調(diào)用,一些可中斷系統(tǒng)調(diào)用在發(fā)生阻塞時(shí)會(huì)自動(dòng)重啟。
這些會(huì)自動(dòng)重啟的可中斷系統(tǒng)調(diào)用包括:ioctl, read, readv, write, writev, wait和waitepid。
如果某些應(yīng)用并不希望這些系統(tǒng)調(diào)用自動(dòng)重啟,可以該系統(tǒng)調(diào)用單獨(dú)設(shè)置SA_RESTART。
?
5 可重入函數(shù)信號(hào)的發(fā)生導(dǎo)致程序的指令執(zhí)行順序被打亂。
但是在信號(hào)處理函數(shù)中,無法知道原進(jìn)程的執(zhí)行情況。
如果原進(jìn)程這個(gè)在分配內(nèi)存或者釋放內(nèi)存,或者調(diào)用了修改static變量的函數(shù),并在信號(hào)處理函數(shù)中再次調(diào)用該函數(shù),會(huì)發(fā)生不可預(yù)期的結(jié)果。
在信號(hào)處理函數(shù)中可以安全調(diào)用的函數(shù)稱為可重入函數(shù),也叫做異步信號(hào)安全的函數(shù)。除了保證可重入,這些函數(shù)還會(huì)阻塞可能導(dǎo)致結(jié)果不一致的信號(hào)。
如果函數(shù)滿足下面的一種或者幾種條件,則說明是不可重入的函數(shù):
?
6 SIGCLD語義一直容易混淆的兩個(gè)信號(hào)是SIGCLD和SIGCHLD。
SIGCLD來自System V,而SIGCHLD來自BSD和POSIX.1。
BSD SIGCHLD的語義:當(dāng)該信號(hào)發(fā)生時(shí),說明子進(jìn)程的狀態(tài)發(fā)生了改變,這時(shí)我們需要調(diào)用wait函數(shù)確認(rèn)狀態(tài)的變化。
對(duì)于System V系統(tǒng)中,對(duì)信號(hào)SIGCLD的處理說明如下:
?
7 可靠信號(hào)及其語義?我們先定義幾個(gè)信號(hào)相關(guān)的概念:
?可靠機(jī)制,不同的標(biāo)準(zhǔn)對(duì)于異常情況有不同的處理:
?
?參考資料:
《Advanced Programming in the UNIX Envinronment 3rd》
新聞熱點(diǎn)
疑難解答
圖片精選