2015-12-26 44 views
6

我仍然有點困惑,爲什麼從信號處理程序中接收信號並調用非異步安全函數的確切原因是不安全的。有人可以解釋這個背後的原因,並可能嘗試給我一些參考,我可以按照自己更多的瞭解這個?爲什麼只能安全地從信號處理程序調用異步信號安全函數?

換句話說,我在問爲什麼從信號處理程序中調用printf是不安全的。是否因爲兩個可能的printf調用而導致進程內問題和可能的競爭條件,或者是因爲進程間競爭相同的資源(在本例中爲stdout)。說進程A中的線程正在調用printf,而另一個線程接收到該信號,然後調用printf。是否可能是因爲這裏的內核不知道該怎麼做,因爲它不能區分這兩個調用。

+1

簡寫形式是「因爲標準是這麼說的」,或者「因爲你的圖書館是這樣說的」,長形式是代碼不是用異步訪問能力:有很多方法可以編寫代碼,以便它不能很好地處理異步問題。幾乎所有處理異步的方法在同步運行時都會產生成本,因此具有非異步處理功能會使性能常常受到影響 – Yakk

+0

感謝!如果我以這樣的方式構造我的代碼,以便不以並行方式調用非異步安全函數,但是在非返回信號處理程序的上下文中,那沒關係嗎? – Curious

+0

'malloc'特定版本:https://stackoverflow.com/questions/3366307/why-is-malloc-not-async-signal-safe –

回答

6

說一個進程內的線程A正在調用printf,另一個線程 接收到信號,然後調用printf。是否可能是因爲這裏的內核 不知道該怎麼做,因爲它不能夠區分這兩個調用。

這不是內核會有問題。這是你的應用程序本身。 printf不是內核函數。它是C庫中的一個函數,即您的應用程序使用。 printf實際上是一個相當複雜的功能。它支持各種輸出格式。

此格式化的最終結果是寫入標準輸出的格式化輸出字符串。這個過程本身也涉及一些工作。格式化的輸出字符串被寫入內部stdout文件句柄的輸出緩衝區。只要某些定義的條件發生,即輸出緩衝區已滿,和/或每當新行字符被寫入時,輸出緩衝區就會被刷新(只有在此時內核接管並將定義的數據塊寫入文件)輸出流。

所有這些都由輸出緩衝區的內部數據結構支持,您不必擔心它,因爲它是C庫的工作。現在,信號可以在任何時候到達,而printf它可以工作。我的意思是,在任何時候。在更新輸出緩衝區內部數據結構的過程中,printf可能會很好到達,並且它們處於暫時不一致的狀態,因爲printf尚未完成更新。

示例:在現代C/C++實現中,printf可能不是信號安全的,但它是線程安全的。多個線程可以使用printf來寫入標準輸出。線程的責任是協調這個過程,確保最終的輸出真正有意義,並且不會隨機地從多個線程的輸出中混淆,但這並不重要。

問題是printf是線程安全的,這通常意味着某個地方有一個mutex參與該過程。因此,可能發生的事件序列爲:

  • printf獲取內部互斥量。

  • printf繼續其工作,格式化字符串並將其寫入stdout的輸出緩衝區。

  • printf之前完成,並且可以釋放獲取的互斥量,信號到達。

現在,內部mutex被鎖定。關於信號處理程序的一點是,通常不會指定進程中的哪個線程處理信號。給定的實現可能會隨機選擇一個線程,或者它可能總是選擇當前正在運行的線程。在任何情況下,它都可以選擇鎖定printf的線程,以處理信號。

所以,現在,您的信號處理程序運行,並且它還決定致電printf。由於printf的內部互斥鎖被鎖定,所以線程必須等待互斥鎖被解鎖。

然後等待。

然後等待。

因爲,如果您正在跟蹤事情:互斥鎖被被中斷以處理信號的線程鎖定。線程繼續運行之前,互斥鎖不會解鎖。但是直到信號處理程序終止,線程纔會恢復運行,但是信號處理程序現在正在等待互斥體被解鎖。

你被綁架了。

現在,當然,printf可能會使用C++等效的std::recursive_mutex來避免這個問題,但即使這樣也不能解決可能由信號引入的所有可能的死鎖。

總而言之,爲什麼它「不安全地接收信號並從該信號處理程序中調用非異步安全功能」是因爲根據定義它不是。從信號處理程序中調用非異步安全函數是不安全的,因爲信號是一個異步事件,並且由於它不是一個異步安全函數,所以根據定義,不能這樣做。水是溼的,因爲它是水,並且不能從異步信號處理程序調用異步不安全函數

+1

謝謝!這就說得通了。只要它是一個內部進程問題,我想我可以解決它 – Curious

+0

遞歸互斥體在某種意義上更糟糕:您正在修改數據結構,並且您現在正在同時再次修改它。 – Yakk