1、產(chǎn)生信號
-
如何產(chǎn)生信號呢?
<1> 鍵盤。當(dāng)某個進(jìn)程正在運(yùn)行時(shí),按下Ctrl_C便可終止進(jìn)程。
<2> 命令。如kill -9 3212 將id為3212的進(jìn)程終止。
<3> 函數(shù)。如kill,alarm,abort函數(shù)。
<4> 操作系統(tǒng)捕捉。軟件,硬件異常
2、如何處理信號 ?
<1> 忽略信號
<2> 執(zhí)行該信號的默認(rèn)動作,一般是將該進(jìn)程終止
<3> 捕捉。自定義函數(shù)。
3、進(jìn)程捕捉到信號時(shí),并不是立即處理,而是在合適的時(shí)候處理。應(yīng)先將所接受到的信號保存在自己的PCB中。等待合適的時(shí)機(jī)去處理(具體的處理時(shí)機(jī),下面會有專門的講解)。
2、信號阻塞
1、基本概念:
信號遞達(dá):實(shí)際執(zhí)行信號的處理動作稱為信號遞達(dá)。忽略是遞達(dá)的一種。
信號未決:信號從產(chǎn)生到遞達(dá)之間的狀態(tài)稱為信號未決。
信號阻塞 進(jìn)程可以選擇阻塞某一信號。被阻塞的信號將保持在未決狀態(tài),直到進(jìn)程解除對此信號的阻塞,才能執(zhí)行遞達(dá)的動作。
2、 信號在內(nèi)核中的表示示意圖(圖片來源于網(wǎng)絡(luò)):

3、每個信號都有兩個標(biāo)志位分別為block(阻塞)和pending(未決) + 一個函數(shù)指針表示處理動作。
1、SIGUP:信號未產(chǎn)生也為阻塞,當(dāng)它遞達(dá)時(shí)執(zhí)行默認(rèn)處理動作
2、SIGINT:信號產(chǎn)生,但是阻塞。它的處理動作是忽略。阻塞不解除,都不會執(zhí)行處理動作。
3、SIGQUIT:沒有信號產(chǎn)生,一旦產(chǎn)生就會被阻塞,處理動作是用戶自定義函數(shù)sighandler。
1)每個信號只有一個bit的 “未決標(biāo)志” 和 “阻塞標(biāo)志”,非0即1,不記錄該信號產(chǎn)生的次數(shù)。因此,未決和阻塞標(biāo)志可以用相同的數(shù)據(jù)類型sigset_t儲存,sigset_t稱為信號集。這個類型表示信號的有效和無效。
2)在阻塞信號集(信號屏蔽字)中即block表,1表示信號阻塞,0表示不阻塞。在未決信號集即pending表,1表示信號產(chǎn)生未決狀態(tài),0表示沒有產(chǎn)生信號??偠灾?信號集為能夠表示信號類型的0,1序列。
注:不可使用位操作操作信號集,有專有的函數(shù)操作
4、信號集操作函數(shù):
縮略圖(信號集操作函數(shù)包含在signal.h中,需要導(dǎo)入該頭文件):

1、sigemptyset: 初始化set所指向的信號集,使其中所有的信號對應(yīng)的標(biāo)志位置為0。
2、sigfillset: 初始化set所指向的信號集,使其中所有的信號對應(yīng)的bit置1。
3、sigaddset: 在該信號集中 添加 某一信號。
4、sigdelset: 在該信號集中刪除某一信號。
5、sigismember: 判斷一個信號集的有效信號中 是否包含某種信號,若包含返回1,不包含返回0,出錯返回-1。
6、sigprocmask函數(shù): 讀取或更改進(jìn)程的信號屏蔽字(阻塞信號集)
返回值:成功為0,失敗為-1.
sigprocmask函數(shù)原型.png
(1)若oset是非空指針,那么進(jìn)程的當(dāng)前信號屏蔽字通過oset返回。
(2)若set是一個非空指針,則參數(shù)how指示如何修改當(dāng)前信號屏蔽字。
(3)下圖說明了how參數(shù)可以取用的值:
how參數(shù)可選值及作用.png
如果set是空指針,則不改變該進(jìn)程的信號屏蔽字,how的值也無意義。
關(guān)于sigpromask()函數(shù)的詳細(xì)使用,這有一篇講的比較細(xì)的文章:
http://blog.csdn.net/big_bit/article/details/51338523
7、sigpending函數(shù):讀取當(dāng)前進(jìn)程的未決信號集
int sigpending(sigset_t *set);
返回值:成功為0,出錯為-1
3、信號捕捉
信號捕捉過程<4次權(quán)限轉(zhuǎn)換>:

前面提到:當(dāng)進(jìn)程捕捉到信號后,并不是立即處理,而是在合適的時(shí)候進(jìn)行處理,這個合適的時(shí)機(jī)就是:從內(nèi)核態(tài)返回到用戶態(tài)時(shí)。
1、內(nèi)核處理完異常或者中斷時(shí),先檢查當(dāng)前進(jìn)程中是否有可以被遞達(dá)的信號。如果有則處理。(一般產(chǎn)生異?;蛘咧袛鄷r(shí)都會隨之產(chǎn)生相應(yīng)的信號)
2、如果信號的處理方式為自定義的函數(shù):則返回用戶態(tài)(第二次權(quán)限變更),執(zhí)行自定義的信號處理函數(shù)(注:不是回到主控制流程)。執(zhí)行完處理函數(shù)后,通過調(diào)用sigreturn再次進(jìn)入內(nèi)核(第三次權(quán)限變更),最后從內(nèi)核態(tài)返回主控制流程中上次被中斷的地方執(zhí)行。(第四次權(quán)限變更)。
為什么要有第四次權(quán)限變更?筆者認(rèn)為,在異?;蛘咧袛喈a(chǎn)生的時(shí)候,直接進(jìn)入了內(nèi)核態(tài),
那么再次回到中斷處時(shí),自然應(yīng)該也是從內(nèi)核態(tài)回去,而不是從用戶態(tài)回去。這樣相當(dāng)于把信號的處理操作封裝成黑盒操作。
3、如果信號的處理方式為默認(rèn):則終止進(jìn)程。
4、如果信號的處理方式為忽略:從未決表(pending表)中刪除該信號,即將1變?yōu)?,直接跳到用戶態(tài)。
4、兩個特殊的函數(shù)及區(qū)別:
1、signal函數(shù):指定某一信號的處理函數(shù)。
函數(shù)原型:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(in signum,sighanler_t handler);
2、sigaction函數(shù)
函數(shù)原型:
intsigaction(int signo, conststruct sigaction*restrict act,
struct sigaction*restrict oact);
//結(jié)構(gòu)體sigaction定義如下:
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flag;
void (*sa_sigaction)(int,siginfo_t*,void*);
};
作用:讀取和修改與指定信號相關(guān)聯(lián)的處理動作(處理函數(shù))。
返回值:成功為0,失敗為-1
實(shí)際使用:
在構(gòu)造sigaction這個結(jié)構(gòu)體時(shí),需要注意一下幾點(diǎn):
1、sa_mask成員的賦值:必須用sigemptyset函數(shù)初始化act結(jié)構(gòu)的sa_mask成員。不能保證:act.sa_mask = 0;會做同樣的事情。
2、sa_flags成員值:設(shè)置不同的值系統(tǒng)會有不同的處理:
1、SA_ONSTACK:捕獲在信號調(diào)用棧中的信號。
2、SA_RESTART:由此信號中斷的系統(tǒng)調(diào)用會自動重啟。
3、SA_NODEFER: 一般情況下, 當(dāng)信號處理函數(shù)運(yùn)行時(shí),內(nèi)核將阻塞<該給定信號 -- SIGINT>。但是如果設(shè)置了SA_NODEFER標(biāo)記, 那么在該信號處理函數(shù)運(yùn)行時(shí),內(nèi)核將不會阻塞該信號。
4、SA_RESETHAND: 當(dāng)調(diào)用信號處理函數(shù)時(shí),將信號的處理函數(shù)重置為缺省值。(一般我們不需要在處理函數(shù)被調(diào)用后,立馬設(shè)置為缺省值)
這兩個標(biāo)志對應(yīng)早期的不可靠信號機(jī)制,除非明確要求使用早期的不可靠信號,否則不應(yīng)該使用。這里也不多做介紹。
5、SA_NOCLDSTOP: 一般當(dāng)進(jìn)程終止或停止時(shí)都會產(chǎn)生SIGCHLD信號,但若對SIGCHLD信號設(shè)置了該標(biāo)志,當(dāng)**子進(jìn)程停止**時(shí)不產(chǎn)生此信號。當(dāng)子進(jìn)程終止時(shí)才產(chǎn)生此信號。
6、SA_NOCLDWAIT: (字面理解是:子進(jìn)程不等待,也就是直接退出 ,這樣就不會存在僵尸進(jìn)城) 若信號是 SIGCHLD時(shí),當(dāng)使用此標(biāo)志時(shí),
1 )當(dāng)調(diào)用進(jìn)程的子進(jìn)程終止時(shí)不創(chuàng)建僵尸進(jìn)程。
2 )若調(diào)用進(jìn)程在后面調(diào)用wait。則調(diào)用進(jìn)程阻塞,直到其所有子進(jìn)程都終止
7、SA_SIGINFO:簡單講就是,可是使我們定義的處理函數(shù)中,多一個info參數(shù),這個參數(shù)中包含了,信號的相關(guān)信息。
在開頭我們看到 struct sigacton結(jié)構(gòu)有一個 void (*sa_tramp)(void *, int, int, siginfo_t *, void *); 字段,該字段是一個 替代的信號處理函數(shù)。
當(dāng)我們沒有使用 SA_SIGINFO 標(biāo)志時(shí),調(diào)用的是 sa_handler指定的信號處理函數(shù)。
當(dāng)指定了該標(biāo)志后,該標(biāo)志對信號處理函數(shù)提供了附加的信息,一個指向siginfo結(jié)構(gòu)的消息和一個指向進(jìn)程上下文標(biāo)識符
的指針這時(shí)我們就能調(diào)用sa_sigaction指定的信號處理函數(shù)
8、SA_USERTRAMP:字面意思理解:不從內(nèi)核態(tài)跳出。(信號捕捉過程中,有四次權(quán)限變更,該標(biāo)志位的具體作用,暫時(shí)還不清楚....)
以上是對sa_flags設(shè)置不同值,會帶來的影響到簡單總結(jié),這里涉及到內(nèi)核編程,一定注意在不同平臺上會有不同的影響,這里只是基于蘋果的signal.h文件來編寫。
筆者在調(diào)研過程中,發(fā)現(xiàn)有一篇不錯的學(xué)習(xí)文檔,可更參考:
http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28852942&id=3754478/
3、附上sigaction的使用:使用sigaction來模仿signal的實(shí)現(xiàn):
signal(int signo, Sigfunc *func) {
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask); //這里需要注意
act.sa_flags = 0; //flags值的設(shè)置,參考上面的講解
if(signo == SIGALRM) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
}
else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART;
#endif
}
if(sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
4、關(guān)于signal 和sigaction 二者區(qū)別:簡單提一下,二者實(shí)際上都是用來指定信號的處理函數(shù)的,只不過前者是較早版本中的提供一種方式。此處可參考下文:http://blog.csdn.net/wangzuxi/article/details/44814825
所以這里筆者只重點(diǎn)對sigaction函數(shù)講解。
5、筆者之所以會對這里做深入調(diào)研,是因?yàn)樵谧鰅OS平臺的crash捕捉以及防護(hù)時(shí),需要用到signal相關(guān)知識,因此做了系統(tǒng)學(xué)習(xí)并總結(jié)出來分享給大家,當(dāng)然對于這塊知識,如有理解不準(zhǔn)確,還希望指正。

