1. 訊號屏蔽字和sigprocmask函數
每個進程都有一個訊號屏蔽字(signal mask),它規定了當前要阻塞遞送到該進程的訊號集。對於每個可能的訊號,該屏蔽字中都有一位與之對應。
進程可以調用sigprocmask函數來擷取或設定這個訊號屏蔽字。
2. 訊號的產生、遞送、未決狀態
1)首先,當引發訊號的事件發生時,為進程產生(generation)一個訊號(或向進程發送一個訊號)。
註:事件可以是硬體異常(例如,除以0)、軟體條件(例如,alarm計時器逾時)、終端產生的訊號或調用kill函數。
2)在產生了訊號時,核心通常在進程表中設定一個某種形式的標誌。當對訊號採取這種動作時,我們說向進程遞送(delivery)了一個訊號。
3)在訊號產生和遞送之間的時間間隔內,稱訊號是未決(pending)的。
3. 阻塞訊號的未決狀態
如1中所說,可以調用sigprocmask函數對訊號進行阻塞。
如果為進程產生了一個選擇為阻塞的訊號,而且對該訊號的動作是預設動作或捕捉該訊號,則為該進程將此訊號保持為未決狀態。直到該進程(a)對此訊號解除了阻塞(同樣通過調用sigprocmask函數),或者(b)將對此訊號的動作更改為忽略。
進程可以調用sigpending函數來判斷哪些訊號是設定為阻塞並處於未決狀態的。
註:如果某個訊號在阻塞期間發生了多次。在大多數UNIX系統中,並不會對訊號遞送多次,而只會遞送一次。
總結上述知識:當一個被設定為阻塞的訊號發生時,如果進程對該訊號的動作是預設(SIG_DEF)或者捕捉(通過設定自訂處理函數)時,核心使得該訊號保持處於未決狀態,等待阻塞取消後遞送;如果進程對該訊號的動作是忽略,核心不會保留該訊號,直接忽略。
下面來看這種機制在多線程環境中的應用:
首先需要瞭解的是:在多線程環境中,每個線程都有自己的訊號屏蔽字,但是一個進程中的所有線程共用訊號的處理。
即:如果在一個線程中修改了對某個訊號的處理方式,相當於修改了進程中所有線程對該訊號的處理方式。
1. 線程對訊號的屏蔽
sigprocmask函數只能用於單線程環境,在多線程環境中與之對應的函數是——pthread_sigmask。該函數與sigprocmask函數用法基本相同。
2. sigwait函數
線程可以通過調用sigwait函數等待一個或多個訊號發生。該函數的第一個參數指定了等待的訊號集合,第二個參數作為傳回值,表明實際接收到的訊號。
如果第一個參數指定的訊號集中的某個訊號在sigwait調用的時候處於未決狀態,那麼sigwait將無阻塞地返回(立即返回)。
結合pthread_mask和sigwait函數,可以簡化多線程環境中訊號的處理:
為了防止某訊號中斷線程,可以
(1)將該訊號的處理方式設定為系統預設
(2)然後加到每個線程的訊號屏蔽字中(調用pthread_mask函數)
(3)安排專用線程,在該線程中調用sigwait函數等待該訊號,並在該線程中對該訊號進行處理(如果等待到的話)
如果進程中產生了某個訊號,由於在所有線程中都對該訊號進行了屏蔽,而且該訊號的處理方式為系統預設,因此該訊號將處於未決狀態。此時專用線程調用的sigwait檢測到該訊號,就會立即返回,表示等待到該訊號,進而進行相應的處理。
這種方法可用於實現守護進程對SIG_HUP訊號的捕捉。
在多線程環境中有一個問題:如果向一個進程遞送一個訊號,那麼該進程中的哪些線程會接收到這個訊號呢?
關於這個問題,《UNIX環境進階編程》一書中是這樣說的:進程中的訊號是遞送到單個線程的。如果訊號與硬體故障或計時器逾時相關,該訊號就被發送到引起該事件的線程中去,而其他的訊號則被發送到任意一個線程。
為了理解最後一句話,做了一個簡單的測試:
1)在main函數中fork一個子進程
2)在子進程中設定SIGUSR1的處理方式為捕捉,並自訂處理函數sig_usr1。處理函數的操作是列印捕捉到訊號的線程的線程ID和所在進程ID
3)在子進程中建立一個新的線程。新的線程與主線程共用sig_usr1
4)子進程的主線程和新線程中都調用pause函數等待訊號
5)父進程中,先睡眠3秒(給子進程設定訊號處理函數和建立新線程的時間),然後向子進程發送SIGUSR1
啟動並執行結果是:總是主線程接收到該訊號,而新線程不會接收到訊號。(不曉得是不是測試次數比較少的原因。。。)
對上述程式做一點修改:
1)在子進程的主線程中屏蔽SIGUSR1,然後不調用pause等待訊號,而是睡眠10秒
a)如果新線程不做修改,仍是調用pause等待訊號,那麼主線程和新線程都不會捕捉到訊號。子進程會隨著睡眠到時正常退出。
b)如果新線程不調用pause,而是調用sigwait函數等待SIGUSR1,那麼新線程會等待到函數。但是不會調用指定的訊號處理函數sig_usr1,而是繼續執行。所以當調用sigwait函數等待訊號時,需要在等待到之後重新編寫關於訊號的相應操作