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