一、可靠性與不可靠性:
??? 1. 不可靠信號(hào)
??????? 主要由以下兩個(gè)問(wèn)題導(dǎo)致不可靠問(wèn)題的發(fā)生:
??????? a. 進(jìn)程每次處理信號(hào)后, 就會(huì)對(duì)信號(hào)的響應(yīng)設(shè)置為默認(rèn)動(dòng)作;如果用戶不希望這樣操作,就要在信號(hào)處理函數(shù)結(jié)尾再調(diào)用一次signal,進(jìn)行重裝。
??????? b. 信號(hào)會(huì)丟失。
????????Linux支持不可靠信號(hào),信號(hào)值小于SIGRTMIN的都是不可靠的, 但是做了改進(jìn),在調(diào)用后,不用重新安裝函數(shù),信號(hào)會(huì)自動(dòng)重裝。Linux下的不可靠信號(hào)主要指信號(hào)丟失。
??? 2. 可靠信號(hào):
?????? 信號(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ì)丟失。
?
二、實(shí)時(shí)信號(hào)與非實(shí)時(shí)信號(hào)
SIGRTMIN(31)往后的都是實(shí)時(shí)信號(hào),前32號(hào)信號(hào)已經(jīng)有了預(yù)定義值,有了特定的用途和含義。 后32個(gè)信號(hào)表示實(shí)時(shí)信號(hào)。
非實(shí)時(shí)信號(hào)都不支持排隊(duì),都是不可靠信號(hào);實(shí)時(shí)信號(hào)都支持排隊(duì),都是可靠信號(hào)。
?
三、響應(yīng):
三種方式:1、忽略,不做任何處理。2、捕捉,并調(diào)用處理函數(shù)。 3、執(zhí)行缺省動(dòng)作。
?
四、發(fā)送函數(shù):
常用的有kill(),? raise(), sigqueue(), alarm(), setitimer(),abort()。詳見(jiàn)man手冊(cè)...
?
五、信號(hào)的安裝:
主要使用signal() 和 sigaction()函數(shù)。
1、signal()不支持信號(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ù)。
2、兩者都支持裝填這樣的處理函數(shù):typedef void (*sighandler_t)(int);sigaction()還支持裝填帶有參數(shù)的處理函數(shù):
void (*_sa_sigaction)(int,struct siginfo *, void *);下面是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); }很巧妙地使用union來(lái)支持兩種不同的信號(hào)處理函數(shù)。帶有參數(shù)的處理函數(shù)主要為實(shí)時(shí)信號(hào)準(zhǔn)備,當(dāng)然也支持舊的信號(hào)(0~31)。
?
再看看那個(gè)保存參數(shù)的結(jié)構(gòu)體struct siginfo:
?
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{ ... }... ... ... } }其中使用了一個(gè)_pad成員,為所有信號(hào)處理的結(jié)構(gòu)體分配足夠的空間,union會(huì)以成員中最大的那個(gè)來(lái)分配空間,這樣處理我認(rèn)為有這樣的考慮:1、可以設(shè)定足夠的大小,使得整個(gè)siginfo結(jié)構(gòu)體對(duì)齊到cache line,提高cache命中。2、參數(shù)拷貝和傳遞時(shí)更加方便了,不管目前存放的參數(shù)是什么,有多大,要拷貝的話,直接使用_pad標(biāo)簽,一次性就可以全部拷貝過(guò)去。?
?
那個(gè)sa_mask,是個(gè)很好用的東西,用于指定在信號(hào)處理程序執(zhí)行的過(guò)程中,哪些喜好應(yīng)當(dāng)被阻塞。缺省情況下,當(dāng)前信號(hào)本身被阻塞,防止信號(hào)的嵌套發(fā)送,除非指定SA_NODEFER或者SA_NOMASK標(biāo)志位。這個(gè)標(biāo)記位,在一定程度上解決了信號(hào)丟失的問(wèn)題,被阻塞的信號(hào)并不是被丟失掉,而是在當(dāng)前信號(hào)處理過(guò)之后,繼續(xù)處理。只不過(guò)不打斷當(dāng)前的處理過(guò)程而已。
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)
?
信號(hào)作為異步通知,安全保障比較少, 在開(kāi)發(fā)應(yīng)用程序的時(shí)候,盡量不要使用信號(hào)來(lái)傳遞參數(shù),
?
六、進(jìn)一步討論sigaction:信號(hào)集及信號(hào)操作函數(shù):
信號(hào)集其實(shí)是一個(gè)位圖:
?
typedef struct {unsigned long sig[_NSIG_WORDS];} sigset_t;
?
linux所支持的所有信號(hào)可以全部或部分的出現(xiàn)在信號(hào)集中,主要與信號(hào)阻塞相關(guān)函數(shù)配合使用,支持以下的操作函數(shù)來(lái)操作信號(hào)集合:
?
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)未決:
使用函數(shù)sigprocmask()阻塞信號(hào)的傳遞,只是延遲信號(hào)的到達(dá)。信號(hào)會(huì)在解除阻塞后繼續(xù)傳遞。這種情況往往需要在信號(hào)程序和其它程序共享全局變量時(shí),如果全局變量的類型不是sig_atomic_t類型,當(dāng)一部分程序恰好讀、寫到變量過(guò)程中,產(chǎn)生某個(gè)信號(hào),而信號(hào)程序里會(huì)改變?cè)撟兞浚敲淳蜁?huì)產(chǎn)生混亂。為了避免這種混亂,提供程序的可靠性,必須在操作這類變量前阻塞信號(hào),操作完成后恢復(fù)信號(hào)的傳遞。
每個(gè)進(jìn)程都有一個(gè)用來(lái)描述哪些信號(hào)遞送到進(jìn)程時(shí)將被阻塞的信號(hào)集,該信號(hào)集中的所有信號(hào)在遞送到進(jìn)程后都將被阻塞。我們稱正在阻塞的信號(hào)的集合為信號(hào)掩碼(signal mask)。每個(gè)進(jìn)程都有自己的信號(hào)掩碼,創(chuàng)建子進(jìn)程時(shí)子進(jìn)程將繼承父進(jìn)程的信號(hào)掩碼。我們可以通過(guò)修改當(dāng)前的信號(hào)掩碼來(lái)改變信號(hào)的阻塞情況。
??????int sigprocmask(int how, const sigset_t *set,sigset_t *oldset),該函數(shù)用來(lái)檢查和改變調(diào)用進(jìn)程的信號(hào)掩碼,其中的how參數(shù)指出信號(hào)掩碼改變的方式,必須是下面的值之一:
????? SIG_BLOCK,阻塞set中包含的信號(hào)。意思是說(shuō)把set中的信號(hào)加到當(dāng)前的信號(hào)掩碼中去,新的信號(hào)掩碼是set和舊信號(hào)掩碼的并集。
????? SIG_UNBLOCK,解除set中信號(hào)的阻塞,從當(dāng)前信號(hào)掩碼中去除set中的信號(hào)。
????? SIG_SETMASK,設(shè)置信號(hào)掩碼,既按照set中的信號(hào)重新設(shè)置信號(hào)掩碼。
????? 最后一個(gè)參數(shù)是進(jìn)程原來(lái)的信號(hào)集。如果你只需要改變信號(hào)的阻塞情況而不需要關(guān)心原來(lái)的值,可以傳遞NULL指針給函數(shù)。如果你希望什么也不改變,只是想獲得當(dāng)前信號(hào)掩碼的信息,那么把set設(shè)置成NULL,old中返回當(dāng)前的設(shè)置。
????? 使用sigpending(sigset_t *set)可以獲得當(dāng)前已經(jīng)送到進(jìn)程,但是被阻塞的所有信號(hào),在set指向的信號(hào)集中返回結(jié)果。是一個(gè)用于查詢的函數(shù),暫時(shí)沒(méi)有想到好的用處。
?
????? 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。
?????? signsuspend的操作是原子的(要么都執(zhí)行,要么一點(diǎn)都不執(zhí)行),主要做了一下工作:???
????? (1) 設(shè)置新的mask阻塞當(dāng)前進(jìn)程;
????? (2) 收到信號(hào),調(diào)用該進(jìn)程設(shè)置的信號(hào)處理函數(shù);
????? (3) 待信號(hào)處理函數(shù)返回后,恢復(fù)原先mask;
????? (4) sigsuspend返回。
?
八、信號(hào)的生命周期:
1、信號(hào)"誕生"。信號(hào)的誕生指的是觸發(fā)信號(hào)的事件發(fā)生(如檢測(cè)到硬件異常、定時(shí)器超時(shí)以及調(diào)用信號(hào)發(fā)送函數(shù)kill()或sigqueue()等)。
2、信號(hào)在目標(biāo)進(jìn)程中"注冊(cè)";進(jìn)程的task_struct結(jié)構(gòu)中有關(guān)于本進(jìn)程中未決信號(hào)的數(shù)據(jù)成員:
?
?
sigpending結(jié)構(gòu)體中,signal域保存所有待處理信號(hào)的集合,每個(gè)信號(hào)占一位。list域指向sigqueue鏈表,對(duì)于非實(shí)時(shí)信號(hào)(1-31),每個(gè)信號(hào)在鏈表中只能擁有一個(gè)sigqueue;對(duì)于實(shí)時(shí)信號(hào)(32-63),如果接收到多個(gè)相同的信號(hào),每個(gè)信號(hào)都會(huì)在鏈表中擁有一個(gè)sigqueue。
信號(hào)在進(jìn)程中注冊(cè)指的就是信號(hào)值加入到進(jìn)程的未決信號(hào)集中(sigpending結(jié)構(gòu)的第二個(gè)成員sigset_t signal),并且信號(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è),則該信號(hào)將被丟棄,造成信號(hào)丟失。因此,非實(shí)時(shí)信號(hào)又叫做"不可靠信號(hào)"。這意味著同一個(gè)非實(shí)時(shí)信號(hào)在進(jìn)程的未決信號(hào)信息鏈中,至多占有一個(gè)sigqueue結(jié)構(gòu)(一個(gè)非實(shí)時(shí)信號(hào)誕生后,(1)、如果發(fā)現(xiàn)相同的信號(hào)已經(jīng)在目標(biāo)結(jié)構(gòu)中注冊(cè),則不再注冊(cè),對(duì)于進(jìn)程來(lái)說(shuō),相當(dāng)于不知道本次信號(hào)發(fā)生,信號(hào)丟失;(2)、如果進(jìn)程的未決信號(hào)中沒(méi)有相同信號(hào),則在進(jìn)程中注冊(cè)自己)。?
?
3、信號(hào)在進(jìn)程中的注銷。在目標(biāo)進(jìn)程執(zhí)行過(guò)程中,會(huì)檢測(cè)是否有信號(hào)等待處理(每次從系統(tǒng)空間返回到用戶空間時(shí)都做這樣的檢查)。如果存在未決信號(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)卸掉。是否將信號(hào)從進(jìn)程未決信號(hào)集中刪除對(duì)于實(shí)時(shí)與非實(shí)時(shí)信號(hào)是不同的。對(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)一次),則應(yīng)該把信號(hào)在進(jìn)程的未決信號(hào)集中刪除(信號(hào)注銷完畢)。否則,不應(yīng)該在進(jìn)程的未決信號(hào)集中刪除該信號(hào)(信號(hào)注銷完畢)。?
進(jìn)程在執(zhí)行信號(hào)相應(yīng)處理函數(shù)之前,首先要把信號(hào)在進(jìn)程中注銷。
?
4、信號(hào)生命終止。進(jìn)程注銷信號(hào)后,立即執(zhí)行相應(yīng)的信號(hào)處理函數(shù),執(zhí)行完畢后,信號(hào)的本次發(fā)送對(duì)進(jìn)程的影響徹底結(jié)束。
注:?
1)信號(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è))。?
2)在信號(hào)被注銷到相應(yīng)的信號(hào)處理函數(shù)執(zhí)行完畢這段時(shí)間內(nèi),如果進(jìn)程又收到同一信號(hào)多次,則對(duì)實(shí)時(shí)信號(hào)來(lái)說(shuō),每一次都會(huì)在進(jìn)程中注冊(cè);而對(duì)于非實(shí)時(shí)信號(hào)來(lái)說(shuō),無(wú)論收到多少次信號(hào),都會(huì)視為只收到一個(gè)信號(hào),只在進(jìn)程中注冊(cè)一次。
評(píng)論
查看更多