信號(signal)就是通知某個進程發生了某個事件,有時也稱為軟件中斷(software interrupt)。信號通常是異步發生的,也就是說進程預先不知道信號准確發生的時刻。
信號可以:
由一個進程發送給另一個進程(或自身)。
由內核發給某個進程。
每個信號都有一個與之關聯的處置(disposition),也稱為行為(action)。我們通過sigaction函數來設定一個信號的處置,並有三種選擇。
1. 我們提供一個函數,他將在特定信號發生的任何時刻被調用。
2. 我們可以發某個信號設置為SIG_IGN來忽略(ignore)它。SIGKILL和SIGSTOP這兩個信號不能被忽略。
3. 我們可以把某個信號的處置設定為SIG_DFL來啟用他的缺省(default)處理。(缺省處置通常是在收到信號后終止進程,有些信號可能會產生一個core image, 內存影像)
signal函數
建立信號處理的POSIX方法就是調用sigaction函數,不過這個函數有點復雜,它需要接受一個結構參數;比較簡單的方法是調用signal函數,signal函數的第一個參數為信號名,第二個參數可以是一個指向函數的指針,也可以是一個常值,如:SIG_IGN,SIG_DFL。不過signal是早於POSIX出現的悠久函數,調用它時,不同的實現提供不同的信號語義以達成后向兼容,而POSIX則明確規定調用sigaction時的信號語義。為了方便,我們一般會定義一個自己的signal函數,在這個函數里邊調用sigaction函數,這樣就以所期望的POSIX語義提供了一個簡單的接口。下邊我們看來自《UNIX網絡編程》卷一的實現:
sigfunc* signal( int signal, sigfunc *func ) { struct sigaction act, oact; act.sa_handler = func; sigemptyset( &act.sa_mask ); act.sa_flags = 0; if ( signo == SIGALRM ) { #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */ #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; /* SVR4, 4.4BSD */ #endif } if ( sigaction(signo, &act, &oact) < 0 ) { return SIG_ERR; } return oac.sa_handler; }
在上邊這段代碼中,sigfunc為一個函數指針,在由signal指定的信號發生的時候調用的處理函數;
sigemptyset( &act.sa_mask ); 設置處理函數的信號掩碼
POSIX允許我們指定這樣一組信號,他們在信號處理函數被調用時阻塞(這里的阻塞是指阻塞某個信號或某個信號集,防止它們在阻塞期間遞交;不同於系統調用的阻塞),任何阻塞的信號都不能遞交給進程。我們把sa_mask成員設置為空集,意味着在該信號處理函數運行期間,不阻塞額外的信號。POSIX保證被捕獲的信號在其信號處理函數運行期間總是阻塞的(其他的信號不能遞交給進程)。
“if ( signo == SIGALRM ) { " if語句中設置SA_RESTART標志
SA_RESTART標志是可選的。如果設置,由相應信號中斷的系統調用將有內核自動重啟。如果被捕獲的信號不是SIGALRM且SA_RESTART有定義,我們就設置該標志,一些較早期的系統(如SunOS 4.x)缺省設置成自動重啟被中斷的系統調用,並定義了與SA_RESTART互補的SA_INTERRUPT標志,如果定義了該標志,我們就在被捕獲的信號是SIGALRM時設置它。假設一個進程正在進行accept系統調用,此時收到一個信號,如果在4.4BSD下,則內核會自動重啟被中斷的系統調用,accept不會返回錯誤;如果是在Solaris9下, 由於SA_RESTART標志並沒有設置,那么accept會返回一個EINTR錯誤(被中斷的系統調用)。
最后我們調用sigaction函數,並將相應信號舊的行為作為signal的返回值。
符合POSIX的系統信號處理總結:
1. 一旦安裝了信號處理函數,它便一直安裝者(較早期的系統是每執行一次就將其拆除)。
2. 在一個信號處理函數運行期間,正被遞交的信號是阻塞的。
3. 如果一個信號在被阻塞期間產生了一次或多次,那么該信號被解阻塞之后通常只遞交一次,也就是說Unix信號缺省是不排隊的。
4. 利用sigprocmask函數選擇性地阻塞或解阻塞一組信號是可能的。這使得我們可以做到在一段臨界區代碼執行期間,防止捕獲某些信號,以此保護這段代碼。