信號量以及可重入與線程安全

信號量內部實現原理:

舉個例子,前臺運行某個程序的時候,我們在鍵盤輸入 Ctrl+C,就會終止這個進程,這是因爲前臺進程捕捉了這個有鍵盤傳送過來的信號,並執行了相應的處理程序,那麼什麼又是信號捕捉呢?

如果信號的處理動作是用戶自定義函數,在信號遞達時就調用這個函數,這稱爲捕捉信號。由於信號處理函數的代碼是在用戶空間的,處理過程比較複雜,舉例如下:
1. 用戶程序註冊了SIGQUIT信號的處理函數sighandler。
2. 當前正在執行main函數,這時發生中斷或異常切換到內核態。
3. 在中斷處理完畢後要返回用戶態的main函數之前檢查到有信SIGQUIT遞達。
4. 內核決定返回用戶態後不是恢復main函數的上下文繼續執行,而是執行sighandler函數,sighandler和main函數使用不同的堆棧空間,它們之間不存在調用和被調用的關係,
是 兩個獨立的控制流程。
5. sighandler函數返回後自動執行特殊的系統調用sigreturn再次進入內核態。
6. 如果沒有新的信號要遞達,這次再返回用戶態就是恢復main函數的上下文繼續執行了。
這裏寫圖片描述
所以我們可以看到一次信號捕捉有四次狀態切換:用戶->內核->用戶->內核->用戶,這裏我們需要注意的點是:上述的第四點,在此強點一下,執行處理信號的函數的時候,並不是回到main函數的堆棧中執行,而是是直接到這個程序的代碼處執行,並且是以用戶態的權限。
所以相當於在信號發生時,該進程的控制權被轉交給信號處理函數,只有等到信號處理函數完成後,纔會將控制權再交還給main函數。

可重入函數

  1. 單進程多次執行,即信號那種方式,加入有如下代碼:
void sum(int a,int b){
    return a+b;
}
void catch_signal(int sign)
{
    sum(1,2);              //1

}

int main(int arg, char *args[])
{
    //註冊終端中斷信號
    signal(SIGINT, catch_signal);
    while(1){
        sum(3,4);      //2
    }
    return 0;
}

當main執行到2時,按下ctrl+c,信號發生,進入catch_signal函數,而catch_signal接下來執行到1,又進入sum函數內。這種就叫單進程重複執行。如果sum爲可重入函數,這種情況下要保證結果不受上次影響。
2. 多線程或者多進程重複執行,不受上次影響。

線程安全

線程安全只要滿足上面可重入的第2個要求即可。

可重入與線程安全的關係

所以說可重入的一定是線程安全,但線程安全不一定是可重入,它可以通過加鎖實現的,而這種加鎖實現的線程安全不是可重入的。
例如malloc,printf等等函數就是這種通過加鎖實現的線程安全函數,但卻不是可重入的。
例如malloc在執行時,會訪問一個全局狀態,而爲了能夠實現線程安全,所以malloc在訪問之前都會加鎖。
printf也是類似的。
將上面的代碼的1,2處改爲malloc,大家就知道爲什麼malloc是線程安全但卻不是可重入的,因爲它不滿足可重入的第一個要求,單進程多次執行會出錯。
例如執行到2處malloc時,按下ctrl+c,進程調到catch_signal函數中執行,執行到1處,又是malloc函數,但是由於2處執行malloc時,已經對全局狀態加鎖了,所以在1處時,就會發生死鎖。

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