第1節 信號
信號基本原理
Linux是一種多用戶多任務的操作系統,系統內會有多個進程存在。無論是操作系統與用戶進程之間,還是用戶進程之間,經常需要共享數據和交換信息。進程間相互通信的方法有多種,信號便是其中最為簡單的一種,它用以指出某事件的發生。在Linux系統中,根據具體的的軟硬件情況,內核程序會發出不同的信號來通知進程某個事件的發生。對於信號的發送,盡管可以由某些用戶進程發出,但是大多數情況下,都是由內核程序在遇到以下幾種特定情況的時候向進程發送的,例如:
1. 系統測出一個可能出現的硬件故障,如電源故障。
2. 程序出現異常行為,如企圖使用該進程之外的存貯器。
3. 該進程的子進程已經終止。
4. 用戶從終端向目標程序發出中斷(BREAK)鍵、繼續(CTRL-Q)鍵等。
當一個信號正在被處理時,所有同樣的信號都將暫時擱置(注意,並沒有刪除),直到這個信號處理完成後,才加以理會。
當一個進程收到信號後,用下列方式之一做出反應∶
1.忽略該信號;
2.捕獲該信號(即,內核在繼續執行該進程之前先運行一個由用戶定義的函數);
3.讓內核執行與該信號相關的默認動作。
現在用一個例子來簡要說明信號的發送、捕獲和處理,通過它,你就可以對信號有一個大致的印象。例如,當某程序正在執行期間,如果發現它的運行有問題,我們可以用ctrl-c或delete鍵打斷它的執行,這實際上就是向進程發送了一個中止信號。該進程收到這個中止信號後,可以根據事先的設定,對該信號做出相應的處理,如ctrl-c或delete鍵被定義為一個中止信號,進程接受到這個信號,便中途退出了。上面是用信號去中斷另一個進程的實例。除此以外,內核還可以通過發信號來通知一個進程:它的子進程已經終止,或通知一個超時進程:它已被設置警報(alarm)。
接下來我們開始詳細介紹Linux系統中的一些與信號相關的函數。
我們介紹的第一個函數是signal ()函數,它定義在ANSI <signal.h>庫中 ,該函數原型如下:
void * signal(int signum, void * handler);
它的第一個參數是將要處理的信號。第二參數是一個指針,該指針指向以下類型的涵數∶
void func();
當信號signum產生時,內核會盡快執行handler函數。一旦handler返回,內核便從中斷點繼續執行進程。第二參數可以取兩個特殊值:SIG_IGN和SIG_DFL。SIG_IGN用以指出該信號應該被忽略;SIG_DFL用以指示,內核收到信號後將執行默認動作。盡管一個進程不能捕獲SIGSTOP和SIGKILL信號,但是內核可以執行與該信號有關的默認動作作為替代,這些默認動作分別是暫停進程和終止進程。
重發信號
當一個正在為SIGx運行handler的進程收到另一個SIGx信號時,它應該如何處理?通常,人們會希望內核中斷該進程而再一次運行handler。為此,就要求無論何時調用handler,它都必須完全可用——即使當時她正在運行也必須如此。也就是說,要求handler必須是“可重入的(re - entrant)”。然而,設計可重入的handlers是件相當復雜的事情,因此, linux沒有采用此種方案。
對於重發信號問題,起初的解決方案是:在執行用戶定義的handler之前,重新將handler設置為SIG_DFL。然而,事實證明這是一個拙劣的解決方案,因為當兩個信號迅速出現時,每個都給以不同地處理。內核為第一個信號執行handler而為第二個執行默認動作。這樣,第二信號有時會導致該進程終止。因此,這個實現被稱作"不可靠的信號( unreliable signals) "。
在下一節中,我們將看到POSIX signal API是怎樣“漂亮地”解決這個難題的。