Linux-信號機制詳解(二)

       前面的詳解(一)已經詳細討論了信號的種類,信號產生的條件和信號的阻塞信號,基本上已經對信號的處理方式有了一定的瞭解。今天我們繼續瞭解信號的其他內容。

四。捕捉信號

之前的信號的處理中有提到過捕捉信號,就是通過調用自定義的函數處理信號


信號捕捉舉例:

1. ⽤戶程序註冊了SIGQUIT信號的處理函數sighandler。

2. 當前正在執⾏main函數,這時發⽣中斷或異常切換到內核態。

3. 在中斷處理完畢後要返回⽤戶態的main函數之前檢查到有信號SIGQUIT遞達。

4. 內核決定返回⽤戶態後不是恢復main函數的上下⽂繼續執⾏,⽽是執⾏sighandler函

數,sighandler和main函數使⽤不同的堆棧空間,它們之間不存在調⽤和被調⽤的關係,

是 兩個獨⽴的控制流程。

5. sighandler函數返回後⾃動執⾏特殊的系統調⽤sigreturn再次進⼊內核態。

6. 如果沒有新的信號要遞達,這次再返回⽤戶態就是恢復main函數的上下⽂繼續執⾏了。

下面是一些信號處理的函數使用方法

(一)sigaction函數

<span style="font-family:Microsoft YaHei;font-size:14px;">#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct
sigaction *oact);</span>

      sigaction函數可以讀取和修改與指定信號相關聯的處理動作。調⽤成功則返回0,出錯則返回- 1。 signo是指定信號的編號。若act指針⾮空,則根據act修改該信號的處理動作。
若oact指針⾮ 空,則通過oact傳出該信號原來的處理動作。 

       當某個信號的處理函數被調⽤時,內核⾃動將當前信號加⼊進程的信號屏蔽字,當信號處理函數返回時⾃動恢復原來的信號屏蔽字,這樣就保證了在處理某個信號時,如果這種信號再次產⽣,那麼 它會被阻塞到當前處理結束爲⽌。如果在調⽤信號處理函數時,除了當前信號被⾃動屏蔽之外,還希望⾃動屏蔽另外⼀些信號,則⽤sa_mask字段說明這些需要額外屏蔽的信號,當信號處理函數返回時⾃動恢復原來的信號屏蔽字。

(二)pause函數

<span style="font-family:Microsoft YaHei;font-size:14px;">#include <unistd.h>
int pause(void);</span>

       pause函數使調⽤進程掛起直到有信號遞達。如果信號的處理動作是終⽌進程,則進程終⽌,pause函數沒有機會返回;如果信號的處理動作是忽略,則進程繼續處於掛起狀態,pause不返回;如果信號的處理動作是捕捉,則調⽤了信號處理函數之後pause返回-1,errno設置爲EINTR, 所以pause只有出錯的返回值(想想以前還學過什麼函數只有出錯返回值?)。錯誤碼EINTR表 ⽰“被信號中斷”。

(三)可重入函數

       當捕捉到信號時,不論進程的主控制流程當前執⾏到哪⼉,都會先跳到信號處理函數中執⾏,從信號處理函數返回後再繼續執⾏主控制流程。信號處理函數是⼀個單獨的控制流程,因爲它和主控制流程是異步的,⼆者不存在調⽤和被調⽤的關係,並且使⽤不同的堆棧空間。引⼊了信號處理函數使得⼀個進程具有多個控制流程,如果這些控制流程訪問相同的全局資源(全局變量、硬件資源等),就有可能出現衝突。

如圖:


       main函數調⽤insert函數向⼀個鏈表head中插⼊節點node1,插⼊操作分爲兩步,剛做完第⼀步的 時候,因爲硬件中斷使進程切換到內核,再次回⽤戶態之前檢查到有信號待處理,於
是切換 到sighandler函數,sighandler也調⽤insert函數向同⼀個鏈表head中插⼊節點node2,插⼊操作的 兩步都做完之後從sighandler返回內核態,再次回到⽤戶態就從main函數調⽤的insert函數中繼續 往下執⾏,先前做第⼀步之後被打斷,現在繼續做完第⼆步。結果是,main函數和sighandler先後 向鏈表中插⼊兩個節點,⽽最後只有⼀個節點真正插⼊鏈表中了。

     像這樣,insert函數被不同的控制流程調⽤,有可能在第⼀次調⽤還沒返回時就再次進⼊該函 數,這稱爲重⼊,insert函數訪問⼀個全局鏈表,有可能因爲重⼊⽽造成錯亂,像這樣的函數稱爲 不可重⼊函數,反之,如果⼀個函數只訪問⾃⼰的局部變量或參數,則稱爲可重⼊(Reentrant) 函數。

(四)sig_atomic_t類型與volatile限定符

       在上⾯的例⼦中,main和sighandler都調⽤insert函數則有可能出現鏈表的錯亂,其根本原因在 於,對全局鏈表的插⼊操作要分兩步完成,不是⼀個原⼦操作,假如這兩步操作必定會⼀起做完, 中間不可能被打斷,就不會出現錯亂了。


     對於程序中存在多個執⾏流程訪問同⼀全局變量的情況,volatile限定符是必要的,此外,雖然程 序只有單⼀的執⾏流程,但是變量屬於以下情況之⼀的,也需要volatile限定:

1. 變量的內存單元中的數據不需要寫操作就可以⾃⼰發⽣變化,每次讀上來的值都可能不⼀樣

2. 即使多次向變量的內存單元中寫數據,只寫不讀,也並不是在做⽆⽤功,⽽是有特殊意義的什麼樣的內存單元會具有這樣的特性呢?肯定不是普通的內存,⽽是映射到內存地址空間的硬件寄存器,例如串⼜的接收寄存器屬於上述第⼀種情況,⽽發送寄存器屬於上述第⼆種情況。sig_atomic_t類型的變量應該總是加上volatile限定符,因爲要使⽤sig_atomic_t類型的理由也正 是要加volatile限定符的理由。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章