級(jí)別: 初級(jí)
鄭彥興 (mlinux@163.com)國(guó)防科大
2003 年 1 月 01 日
linux信號(hào)機(jī)制遠(yuǎn)遠(yuǎn)比想象的復(fù)雜,本文力爭(zhēng)用最短的篇幅,對(duì)該機(jī)制做了深入細(xì)致的分析。讀者可以先讀一下信號(hào)應(yīng)用實(shí)例(在信號(hào)(下)中),這樣可以對(duì)信號(hào)發(fā)送直到相應(yīng)的處理函數(shù)執(zhí)行完畢這一過(guò)程有個(gè)大致的印象。本文盡量給出了較新函數(shù)的應(yīng)用實(shí)例,著重說(shuō)明這些的功能。
一、信號(hào)及信號(hào)來(lái)源
信號(hào)本質(zhì)
信號(hào)是在軟件層次上對(duì)中斷機(jī)制的一種模擬,在原理上,一個(gè)進(jìn)程收到一個(gè)信號(hào)與處理器收到一個(gè)中斷請(qǐng)求可以說(shuō)是一樣的。信號(hào)是異步的,一個(gè)進(jìn)程不必通過(guò)任何操作來(lái)等待信號(hào)的到達(dá),事實(shí)上,進(jìn)程也不知道信號(hào)到底什么時(shí)候到達(dá)。
信號(hào)是進(jìn)程間通信機(jī)制中唯一的異步通信機(jī)制,可以看作是異步通知,通知接收信號(hào)的進(jìn)程有哪些事情發(fā)生了。信號(hào)機(jī)制經(jīng)過(guò)POSIX實(shí)時(shí)擴(kuò)展后,功能更加強(qiáng)大,除了基本通知功能外,還可以傳遞附加信息。
信號(hào)來(lái)源
信號(hào)事件的發(fā)生有兩個(gè)來(lái)源:硬件來(lái)源(比如我們按下了鍵盤(pán)或者其它硬件故障);軟件來(lái)源,最常用發(fā)送信號(hào)的系統(tǒng)函數(shù)是kill, raise, alarm和setitimer以及sigqueue函數(shù),軟件來(lái)源還包括一些非法運(yùn)算等操作。
二、信號(hào)的種類
可以從兩個(gè)不同的分類角度對(duì)信號(hào)進(jìn)行分類:(1)可靠性方面:可靠信號(hào)與不可靠信號(hào);(2)與時(shí)間的關(guān)系上:實(shí)時(shí)信號(hào)與非實(shí)時(shí)信號(hào)。在《Linux環(huán)境進(jìn)程間通信(一):管道及有名管道》的附1中列出了系統(tǒng)所支持的所有信號(hào)。
1、可靠信號(hào)與不可靠信號(hào)
"不可靠信號(hào)"
Linux信號(hào)機(jī)制基本上是從Unix系統(tǒng)中繼承過(guò)來(lái)的。早期Unix系統(tǒng)中的信號(hào)機(jī)制比較簡(jiǎn)單和原始,后來(lái)在實(shí)踐中暴露出一些問(wèn)題,因此,把那些建立在早期機(jī)制上的信號(hào)叫做"不可靠信號(hào)",信號(hào)值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信號(hào)都是不可靠信號(hào)。這就是"不可靠信號(hào)"的來(lái)源。它的主要問(wèn)題是:
- 進(jìn)程每次處理信號(hào)后,就將對(duì)信號(hào)的響應(yīng)設(shè)置為默認(rèn)動(dòng)作。在某些情況下,將導(dǎo)致對(duì)信號(hào)的錯(cuò)誤處理;因此,用戶如果不希望這樣的操作,那么就要在信號(hào)處理函數(shù)結(jié)尾再一次調(diào)用signal(),重新安裝該信號(hào)。
- 信號(hào)可能丟失,后面將對(duì)此詳細(xì)闡述。
因此,早期unix下的不可靠信號(hào)主要指的是進(jìn)程可能對(duì)信號(hào)做出錯(cuò)誤的反應(yīng)以及信號(hào)可能丟失。
Linux支持不可靠信號(hào),但是對(duì)不可靠信號(hào)機(jī)制做了改進(jìn):在調(diào)用完信號(hào)處理函數(shù)后,不必重新調(diào)用該信號(hào)的安裝函數(shù)(信號(hào)安裝函數(shù)是在可靠機(jī)制上的實(shí)現(xiàn))。因此,Linux下的不可靠信號(hào)問(wèn)題主要指的是信號(hào)可能丟失。
"可靠信號(hào)"
隨著時(shí)間的發(fā)展,實(shí)踐證明了有必要對(duì)信號(hào)的原始機(jī)制加以改進(jìn)和擴(kuò)充。所以,后來(lái)出現(xiàn)的各種Unix版本分別在這方面進(jìn)行了研究,力圖實(shí)現(xiàn)"可靠信號(hào)"。由于原來(lái)定義的信號(hào)已有許多應(yīng)用,不好再做改動(dòng),最終只好又新增加了一些信號(hào),并在一開(kāi)始就把它們定義為可靠信號(hào),這些信號(hào)支持排隊(duì),不會(huì)丟失。同時(shí),信號(hào)的發(fā)送和安裝也出現(xiàn)了新版本:信號(hào)發(fā)送函數(shù)sigqueue()及信號(hào)安裝函數(shù)sigaction()。POSIX.4對(duì)可靠信號(hào)機(jī)制做了標(biāo)準(zhǔn)化。但是,POSIX只對(duì)可靠信號(hào)機(jī)制應(yīng)具有的功能以及信號(hào)機(jī)制的對(duì)外接口做了標(biāo)準(zhǔn)化,對(duì)信號(hào)機(jī)制的實(shí)現(xiàn)沒(méi)有作具體的規(guī)定。
信號(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()。
注:不要有這樣的誤解:由sigqueue()發(fā)送、sigaction安裝的信號(hào)就是可靠的。事實(shí)上,可靠信號(hào)是指后來(lái)添加的新信號(hào)(信號(hào)值位于SIGRTMIN及SIGRTMAX之間);不可靠信號(hào)是信號(hào)值小于SIGRTMIN的信號(hào)。信號(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ù)(對(duì)所有信號(hào)這一點(diǎn)都成立),而經(jīng)過(guò)signal安裝的信號(hào)卻不能向信號(hào)處理函數(shù)傳遞信息。對(duì)于信號(hào)發(fā)送函數(shù)來(lái)說(shuō)也是一樣的。
2、實(shí)時(shí)信號(hào)與非實(shí)時(shí)信號(hào)
早期Unix系統(tǒng)只定義了32種信號(hào),Ret hat7.2支持64種信號(hào),編號(hào)0-63(SIGRTMIN=31,SIGRTMAX=63),將來(lái)可能進(jìn)一步增加,這需要得到內(nèi)核的支持。前32種信號(hào)已經(jīng)有了預(yù)定義值,每個(gè)信號(hào)有了確定的用途及含義,并且每種信號(hào)都有各自的缺省動(dòng)作。如按鍵盤(pán)的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)是POSIX標(biāo)準(zhǔn)的一部分,可用于應(yīng)用進(jìn)程。
非實(shí)時(shí)信號(hào)都不支持排隊(duì),都是不可靠信號(hào);實(shí)時(shí)信號(hào)都支持排隊(duì),都是可靠信號(hào)。
三、進(jìn)程對(duì)信號(hào)的響應(yīng)
進(jìn)程可以通過(guò)三種方式來(lái)響應(yīng)一個(gè)信號(hào):(1)忽略信號(hào),即對(duì)信號(hào)不做任何處理,其中,有兩個(gè)信號(hào)不能忽略:SIGKILL及SIGSTOP;(2)捕捉信號(hào)。定義信號(hào)處理函數(shù),當(dāng)信號(hào)發(fā)生時(shí),執(zhí)行相應(yīng)的處理函數(shù);(3)執(zhí)行缺省操作,Linux對(duì)每種信號(hào)都規(guī)定了默認(rèn)操作,詳細(xì)情況請(qǐng)參考[2]以及其它資料。注意,進(jìn)程對(duì)實(shí)時(shí)信號(hào)的缺省反應(yīng)是進(jìn)程終止。
Linux究竟采用上述三種方式的哪一個(gè)來(lái)響應(yīng)信號(hào),取決于傳遞給相應(yīng)API函數(shù)的參數(shù)。
四、信號(hào)的發(fā)送
發(fā)送信號(hào)的主要函數(shù)有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。
1、kill() #include <sys/types.h> #include <signal.h> int kill(pid_t pid,int signo)
參數(shù)pid的值 |
信號(hào)的接收進(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是信號(hào)值,當(dāng)為0時(shí)(即空信號(hào)),實(shí)際不發(fā)送任何信號(hào),但照常進(jìn)行錯(cuò)誤檢查,因此,可用于檢查目標(biāo)進(jìn)程是否存在,以及當(dāng)前進(jìn)程是否具有向目標(biāo)發(fā)送信號(hào)的權(quán)限(root權(quán)限的進(jìn)程可以向任何進(jìn)程發(fā)送信號(hào),非root權(quán)限的進(jìn)程只能向?qū)儆谕粋€(gè)session或者同一個(gè)用戶的進(jìn)程發(fā)送信號(hào))。
Kill()最常用于pid>0時(shí)的信號(hào)發(fā)送,調(diào)用成功返回 0; 否則,返回 -1。注:對(duì)于pid<0時(shí)的情況,對(duì)于哪些進(jìn)程將接受信號(hào),各種版本說(shuō)法不一,其實(shí)很簡(jiǎn)單,參閱內(nèi)核源碼kernal/signal.c即可,上表中的規(guī)則是參考red hat 7.2。
2、raise() #include <signal.h> int raise(int signo) 向進(jìn)程本身發(fā)送信號(hào),參數(shù)為即將發(fā)送的信號(hào)值。調(diào)用成功返回 0;否則,返回 -1。
3、sigqueue() #include <sys/types.h> #include <signal.h> int sigqueue(pid_t pid, int sig, const union sigval val) 調(diào)用成功返回 0;否則,返回 -1。
sigqueue()是比較新的發(fā)送信號(hào)系統(tǒng)調(diào)用,主要是針對(duì)實(shí)時(shí)信號(hào)提出的(當(dāng)然也支持前32種),支持信號(hào)帶有參數(shù),與函數(shù)sigaction()配合使用。
sigqueue的第一個(gè)參數(shù)是指定接收信號(hào)的進(jìn)程ID,第二個(gè)參數(shù)確定即將發(fā)送的信號(hào),第三個(gè)參數(shù)是一個(gè)聯(lián)合數(shù)據(jù)結(jié)構(gòu)union sigval,指定了信號(hào)傳遞的參數(shù),即通常所說(shuō)的4字節(jié)值。
typedef union sigval {
int sival_int;
void *sival_ptr;
}sigval_t;
|
sigqueue()比kill()傳遞了更多的附加信息,但sigqueue()只能向一個(gè)進(jìn)程發(fā)送信號(hào),而不能發(fā)送信號(hào)給一個(gè)進(jìn)程組。如果signo=0,將會(huì)執(zhí)行錯(cuò)誤檢查,但實(shí)際上不發(fā)送任何信號(hào),0值信號(hào)可用于檢查pid的有效性以及當(dāng)前進(jìn)程是否有權(quán)限向目標(biāo)進(jìn)程發(fā)送信號(hào)。
在調(diào)用sigqueue時(shí),sigval_t指定的信息會(huì)拷貝到3參數(shù)信號(hào)處理函數(shù)(3參數(shù)信號(hào)處理函數(shù)指的是信號(hào)處理函數(shù)由sigaction安裝,并設(shè)定了sa_sigaction指針,稍后將闡述)的siginfo_t結(jié)構(gòu)中,這樣信號(hào)處理函數(shù)就可以處理這些信息了。由于sigqueue系統(tǒng)調(diào)用支持發(fā)送帶參數(shù)信號(hào),所以比kill()系統(tǒng)調(diào)用的功能要靈活和強(qiáng)大得多。
注:sigqueue()發(fā)送非實(shí)時(shí)信號(hào)時(shí),第三個(gè)參數(shù)包含的信息仍然能夠傳遞給信號(hào)處理函數(shù); sigqueue()發(fā)送非實(shí)時(shí)信號(hào)時(shí),仍然不支持排隊(duì),即在信號(hào)處理函數(shù)執(zhí)行過(guò)程中到來(lái)的所有相同信號(hào),都被合并為一個(gè)信號(hào)。
4、alarm() #include <unistd.h> unsigned int alarm(unsigned int seconds) 專門(mén)為SIGALRM信號(hào)而設(shè),在指定的時(shí)間seconds秒后,將向進(jìn)程本身發(fā)送SIGALRM信號(hào),又稱為鬧鐘時(shí)間。進(jìn)程調(diào)用alarm后,任何以前的alarm()調(diào)用都將無(wú)效。如果參數(shù)seconds為零,那么進(jìn)程內(nèi)將不再包含任何鬧鐘時(shí)間。 返回值,如果調(diào)用alarm()前,進(jìn)程中已經(jīng)設(shè)置了鬧鐘時(shí)間,則返回上一個(gè)鬧鐘時(shí)間的剩余時(shí)間,否則返回0。
5、setitimer() #include <sys/time.h> int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue)); setitimer()比alarm功能強(qiáng)大,支持3種類型的定時(shí)器:
- ITIMER_REAL: 設(shè)定絕對(duì)時(shí)間;經(jīng)過(guò)指定的時(shí)間后,內(nèi)核將發(fā)送SIGALRM信號(hào)給本進(jìn)程;
- ITIMER_VIRTUAL 設(shè)定程序執(zhí)行時(shí)間;經(jīng)過(guò)指定的時(shí)間后,內(nèi)核將發(fā)送SIGVTALRM信號(hào)給本進(jìn)程;
- ITIMER_PROF 設(shè)定進(jìn)程執(zhí)行以及內(nèi)核因本進(jìn)程而消耗的時(shí)間和,經(jīng)過(guò)指定的時(shí)間后,內(nèi)核將發(fā)送ITIMER_VIRTUAL信號(hào)給本進(jìn)程;
Setitimer()第一個(gè)參數(shù)which指定定時(shí)器類型(上面三種之一);第二個(gè)參數(shù)是結(jié)構(gòu)itimerval的一個(gè)實(shí)例,結(jié)構(gòu)itimerval形式見(jiàn)附錄1。第三個(gè)參數(shù)可不做處理。
Setitimer()調(diào)用成功返回0,否則返回-1。
6、abort() #include <stdlib.h> void abort(void);
向進(jìn)程發(fā)送SIGABORT信號(hào),默認(rèn)情況下進(jìn)程會(huì)異常退出,當(dāng)然可定義自己的信號(hào)處理函數(shù)。即使SIGABORT被進(jìn)程設(shè)置為阻塞信號(hào),調(diào)用abort()后,SIGABORT仍然能被進(jìn)程接收。該函數(shù)無(wú)返回值。
五、信號(hào)的安裝(設(shè)置信號(hào)關(guān)聯(lián)動(dòng)作)
如果進(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()在可靠信號(hào)系統(tǒng)調(diào)用的基礎(chǔ)上實(shí)現(xiàn), 是庫(kù)函數(shù)。它只有兩個(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ù)。
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。
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)屏蔽掉哪些函數(shù)等等。
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;
void (*sa_restorer)(void);
}
|
其中,sa_restorer,已過(guò)時(shí),POSIX不支持它,不應(yīng)再被使用。
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_handler指定的處理函數(shù)只有一個(gè)參數(shù),即信號(hào)值,所以信號(hào)不能傳遞除信號(hào)值之外的任何信息;由_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)有使用(posix沒(méi)有規(guī)范使用該參數(shù)的標(biāo)準(zhǔn)),第二個(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{
...
}...
... ...
}
}
|
注:為了更便于閱讀,在說(shuō)明問(wèn)題時(shí)常把該結(jié)構(gòu)表示為附錄2所表示的形式。
siginfo_t結(jié)構(gòu)中的聯(lián)合數(shù)據(jù)成員確保該結(jié)構(gòu)適應(yīng)所有的信號(hào),比如對(duì)于實(shí)時(shí)信號(hào)來(lái)說(shuō),則實(shí)際采用下面的結(jié)構(gòu)形式:
typedef struct {
int si_signo;
int si_errno;
int si_code;
union sigval si_value;
} siginfo_t;
|
結(jié)構(gòu)的第四個(gè)域同樣為一個(gè)聯(lián)合數(shù)據(jù)結(jié)構(gòu):
union sigval {
int sival_int;
void *sival_ptr;
}
|
采用聯(lián)合數(shù)據(jù)結(jié)構(gòu),說(shuō)明siginfo_t結(jié)構(gòu)中的si_value要么持有一個(gè)4字節(jié)的整數(shù)值,要么持有一個(gè)指針,這就構(gòu)成了與信號(hào)相關(guān)的數(shù)據(jù)。在信號(hào)的處理函數(shù)中,包含這樣的信號(hào)相關(guān)數(shù)據(jù)指針,但沒(méi)有規(guī)定具體如何對(duì)這些數(shù)據(jù)進(jìn)行操作,操作方法應(yīng)該由程序開(kāi)發(fā)人員根據(jù)具體任務(wù)事先約定。
前面在討論系統(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ì)程序開(kāi)發(fā)是非常有意義的。
信號(hào)參數(shù)的傳遞過(guò)程可圖示如下:
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)。
注:很多文獻(xiàn)在闡述該標(biāo)志位時(shí)都認(rèn)為,如果設(shè)置了該標(biāo)志位,就必須定義三參數(shù)信號(hào)處理函數(shù)。實(shí)際不是這樣的,驗(yàn)證方法很簡(jiǎn)單:自己實(shí)現(xiàn)一個(gè)單一參數(shù)信號(hào)處理函數(shù),并在程序中設(shè)置該標(biāo)志位,可以察看程序的運(yùn)行結(jié)果。實(shí)際上,可以把該標(biāo)志位看成信號(hào)是否傳遞參數(shù)的開(kāi)關(guān),如果設(shè)置該位,則傳遞參數(shù);否則,不傳遞參數(shù)。
六、信號(hào)集及信號(hào)集操作函數(shù):
信號(hào)集被定義為一種數(shù)據(jù)類型:
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t
|
信號(hào)集用來(lái)描述信號(hào)的集合,linux所支持的所有信號(hào)可以全部或部分的出現(xiàn)在信號(hào)集中,主要與信號(hào)阻塞相關(guān)函數(shù)配合使用。下面是為信號(hào)集操作定義的相關(guān)函數(shù):
#include <signal.h>
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指定的信號(hào)集,信號(hào)集里面的所有信號(hào)被清空;
sigfillset(sigset_t *set)調(diào)用該函數(shù)后,set指向的信號(hào)集中將包含linux支持的64種信號(hào);
sigaddset(sigset_t *set, int signum)在set指向的信號(hào)集中加入signum信號(hào);
sigdelset(sigset_t *set, int signum)在set指向的信號(hào)集中刪除signum信號(hào);
sigismember(const sigset_t *set, int signum)判定信號(hào)signum是否在set指向的信號(hào)集中。
|
七、信號(hào)阻塞與信號(hào)未決:
每個(gè)進(jìn)程都有一個(gè)用來(lái)描述哪些信號(hào)遞送到進(jìn)程時(shí)將被阻塞的信號(hào)集,該信號(hào)集中的所有信號(hào)在遞送到進(jìn)程后都將被阻塞。下面是與信號(hào)阻塞相關(guān)的幾個(gè)函數(shù):
#include <signal.h>
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來(lái)實(shí)現(xiàn)對(duì)信號(hào)集的操作,操作主要有三種:
參數(shù)how |
進(jìn)程當(dāng)前信號(hào)集 |
SIG_BLOCK |
在進(jìn)程當(dāng)前阻塞信號(hào)集中添加set指向信號(hào)集中的信號(hào) |
SIG_UNBLOCK |
如果進(jìn)程阻塞信號(hào)集中包含set指向信號(hào)集中的信號(hào),則解除對(duì)該信號(hào)的阻塞 |
SIG_SETMASK |
更新進(jìn)程阻塞信號(hào)集為set指向的信號(hào)集 |
sigpending(sigset_t *set))獲得當(dāng)前已遞送到進(jìn)程,卻被阻塞的所有信號(hào),在set指向的信號(hào)集中返回結(jié)果。
sigsuspend(const sigset_t *mask))用于在接收到某個(gè)信號(hào)之前, 臨時(shí)用mask替換進(jìn)程的信號(hào)掩碼, 并暫停進(jìn)程執(zhí)行,直到收到信號(hào)為止。sigsuspend 返回后將恢復(fù)調(diào)用之前的信號(hào)掩碼。信號(hào)處理函數(shù)完成后,進(jìn)程將繼續(xù)執(zhí)行。該系統(tǒng)調(diào)用始終返回-1,并將errno設(shè)置為EINTR。
附錄1:結(jié)構(gòu)itimerval:
struct itimerval {
struct timeval it_interval; /* next value */
struct timeval it_value; /* current value */
};
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
|
附錄2:三參數(shù)信號(hào)處理函數(shù)中第二個(gè)參數(shù)的說(shuō)明性描述:
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)有意義*/
pid_t si_pid; /* 發(fā)送信號(hào)的進(jìn)程ID,對(duì)kill(2),實(shí)時(shí)信號(hào)以及SIGCHLD有意義 */
uid_t si_uid; /* 發(fā)送信號(hào)進(jìn)程的真實(shí)用戶ID,對(duì)kill(2),實(shí)時(shí)信號(hào)以及SIGCHLD有意義 */
int si_status; /* 退出狀態(tài),對(duì)SIGCHLD有意義*/
clock_t si_utime; /* 用戶消耗的時(shí)間,對(duì)SIGCHLD有意義 */
clock_t si_stime; /* 內(nèi)核消耗的時(shí)間,對(duì)SIGCHLD有意義 */
sigval_t si_value; /* 信號(hào)值,對(duì)所有實(shí)時(shí)有意義,是一個(gè)聯(lián)合數(shù)據(jù)結(jié)構(gòu),可以為一個(gè)整數(shù)(由si_int標(biāo)示,也可以為一個(gè)指針,由si_ptr標(biāo)示)*/
void * si_addr; /* 觸發(fā)fault的內(nèi)存地址,對(duì)SIGILL,SIGFPE,SIGSEGV,SIGBUS 信號(hào)有意義*/
int si_band; /* 對(duì)SIGPOLL信號(hào)有意義 */
int si_fd; /* 對(duì)SIGPOLL信號(hào)有意義 */
}
|
實(shí)際上,除了前三個(gè)元素外,其他元素組織在一個(gè)聯(lián)合結(jié)構(gòu)中,在聯(lián)合數(shù)據(jù)結(jié)構(gòu)中,又根據(jù)不同的信號(hào)組織成不同的結(jié)構(gòu)。注釋中提到的對(duì)某種信號(hào)有意義指的是,在該信號(hào)的處理函數(shù)中可以訪問(wèn)這些域來(lái)獲得與信號(hào)相關(guān)的有意義的信息,只不過(guò)特定信號(hào)只對(duì)特定信息感興趣而已。
參考資料
- linux內(nèi)核源代碼情景分析(上),毛德操、胡希明著,浙江大學(xué)出版社,當(dāng)要驗(yàn)證某個(gè)結(jié)論、想法時(shí),最好的參考資料;
- UNIX環(huán)境高級(jí)編程,作者:W.Richard Stevens,譯者:尤晉元等,機(jī)械工業(yè)出版社。對(duì)信號(hào)機(jī)制的發(fā)展過(guò)程闡述的比較詳細(xì)。
- signal、sigaction、kill等手冊(cè),最直接而可靠的參考資料。
- http://www./modules.php?op=modload&name=NS-help&file=man提供了許多系統(tǒng)調(diào)用、庫(kù)函數(shù)等的在線指南。
- http://www./onlinepubs/007904975/可以在這里對(duì)許多關(guān)鍵函數(shù)(包括系統(tǒng)調(diào)用)進(jìn)行查詢,非常好的一個(gè)網(wǎng)址。
- http:///whitepapers/reentrant.html對(duì)函數(shù)可重入進(jìn)行了闡述。
- http://www./~compsvcs/doc-cdrom/DOCS/HTML/APS33DTE/DOCU_006.HTM對(duì)實(shí)時(shí)信號(hào)給出了相當(dāng)好的描述。
關(guān)于作者
|