淺談 Linux 的死鎖檢測

發表於2016-10-17

死鎖:就是多個程式(≥2)因為爭奪資源而相互等待的一種現象,若無外力推動,將無法繼續執行下去。注意,只有在多程式或者多執行緒之間或者他們與中斷之間相互通訊或者共享資源才有可能發生死鎖,單執行緒或者程式之間沒有聯絡的話,一般不會發生死鎖。鎖的種類比較多,這裡主要說自旋鎖和訊號量。兩者的差別就在於前者獲得不到資源時的動作是不斷的資源(即忙轉浪費cpu的cycles)而後者則表現為睡眠等待。

死鎖的基本情況如下:

(一)、自旋鎖

1 遞迴使用:同一個程式或執行緒中,申請自旋鎖,但沒有釋放之前又再次申請,一定產生死鎖。
2 程式得到自旋鎖後阻塞,睡眠:在獲得自旋鎖之後呼叫copy_from_user()、copy_to_ser()、和kmalloc()等有可能引起阻塞的函式。
3 中斷中沒有關中斷,或著因為申請未釋放的自旋鎖:在中斷中使用自旋鎖是可以的,應該在進入中斷的時候關閉中斷,不然中斷再次進入的時候,中斷處理函式會自旋等待自旋鎖可以再次使用。或者在程式中申請了自旋鎖,釋放前進入中斷處理函式,中斷處理函式又申請同樣的自旋鎖,這將導致死鎖。
4 中斷與中斷下半部共享資源和中斷與程式共享資源死鎖出現的情況類似。
5 中斷下半部與程式共享資源和中斷與程式共享資源死鎖出現的情況類似。

自旋鎖三種狀態:
自旋鎖保持期間是搶佔失效的(核心不允許被搶佔)。
1 單CPU且核心不可搶佔:
自旋鎖的所有操作都是空。不會引起死鎖,核心程式間不存在併發操作程式,程式與中斷仍然可能共享資料,存在併發操作,此時核心自旋鎖已經失去效果。
2 單CPU且核心可搶佔:
當獲得自旋鎖的時候,禁止核心搶佔直到釋放鎖為止。此時可能存在死鎖的情況是參考自旋鎖可能死鎖的一般情況。
禁止核心搶佔並不代表不會進行核心排程,如果在獲得自旋鎖後阻塞或者主動排程,核心會排程其他程式執行,被排程的核心程式返回使用者空間時,會進行使用者搶佔,此時呼叫的程式再次申請上次未釋放的自旋鎖時,會一直自旋。但是核心被禁止搶佔,從而造成死鎖。
核心被禁止搶佔,但此時中斷並沒被禁止,核心程式可能因為中斷申請自旋鎖而死鎖。
3 多CPU且核心可搶佔:
這才是是真正的SMP的情況。當獲得自旋鎖的時候,禁止核心搶佔直到釋放鎖為止。

(二)訊號量

1 遞迴使用: 同理,在同一個程式或執行緒中,申請了訊號量,但沒有釋放之前又再次申請,程式會一直睡眠,這種情況一定死鎖。
2 程式得到訊號量後阻塞,睡眠:由於獲取到訊號量的程式阻塞或者隨眠,其他在獲取不到後訊號量也會進入睡眠等待,這種情況可能造成死鎖。
3 中斷中申請訊號量:由於訊號量在獲取不到自旋鎖後會進入睡眠等待,中斷處理函式不允許睡眠,如果睡眠,中斷將無法返回。
4 中斷下半部申請訊號量:中斷下半部允許睡眠,這種情況不會造成死鎖。
5 倆個程式相互等待資源:程式1獲得訊號量A,需要訊號量B,在程式1需要訊號量B之前程式2獲得訊號量B,需要訊號量A。程式1、2因相互等待資源而死鎖。

上面是死鎖的基本情況和型別。linux本身就提供了檢測死鎖的機制,如下:

(一)D狀態死鎖檢測

所謂D狀態死鎖:程式長時間(系統預設配置120秒)處於TASK_UNINTERRUPTIBLE 睡眠狀態,這種狀態下程式不響應非同步訊號。如:程式與外設硬體的互動(如read),通常使用這種狀態來保證程式與裝置的互動過程不被打斷,否則裝置可能處於不可控的狀態。
對於這種死鎖的檢測linux提供的是hungtask機制,主要內容集中在Hung_task.c檔案中。具體實現原理如下:
1)、系統建立normal級別的khungtaskd核心執行緒,核心執行緒每120秒檢查一次,檢查的內容:遍歷所有的執行緒連結串列,發現D狀態的任務,就判斷自最近一次切換以來是否還有切換髮生,若是有,則返回。若沒有發生切換,則把任務的所有呼叫棧等資訊列印出來。
2)、具體實現過程如下:
首先,hung_task_init建立一個名為khungtaskd的核心執行緒,核心執行緒的工作由watchdog來完成。

其次,我們看watchdog的實現:

最後,我們分析一下hungtask的核心實現check_hung_uninterruptible_tasks,如下:

怎麼樣,經過上面的分析,可以發現其實原理很簡單。

(二)R狀態死鎖檢測

所謂R狀態死鎖:程式長時間(系統預設配置60秒)處於TASK_RUNNING 狀態壟斷cpu而不發生切換,一般情況下是程式關搶佔後長時候幹活,有時候可能程式關搶佔後處於死迴圈或者睡眠後,這樣就造成系統異常。
對於這種死鎖的檢測機制linux提供的機制是softlockup。主要集中在softlockup.c檔案中。
1)、系統建立一個fifo的程式,此程式週期性的清一下時間戳(per cpu),而系統的時鐘中斷中會被softlockup掛入一個鉤子(softlockup_tick),這個鉤子就是每個時鐘中斷到來的時候都檢查是否每cpu的時間戳被touch了,若在閥值60秒內都沒有被touch,系統就列印除錯資訊。
2)、讓我們分析一下具體的實現:
首先,系統初始化的時候為每個cpu建立一個watchdog執行緒,這個執行緒是fifo的。具體實現如下:

其次,我們看一下核心執行緒watchdog的實現,如下:

最後,softlockup在時鐘中斷中掛上一個鉤子softlockup_tick,每個時鐘中斷都來檢查watchdog這個fifo程式是否有touch過時間戳,若是60秒都沒有touch過,就向系統上報異常資訊了,如下:

怎麼樣,說白了,也不難,就是兩條線並行工作:一條線是fifo級別的核心執行緒負責清時間戳,另一條線是時鐘中斷定期檢查時間戳是否有被清過,若是到了閥值都沒有被請過,則列印softlockup的資訊。

(三)長時間關中斷檢測

長時間關中斷檢測可以有幾種實現機制,而利用nmi watchdog來檢查這種長時間關中斷情況,是比較簡單的。其原理是需要軟硬體配合,硬體通常提供一個計數器(可以遞增也可以遞減),當記數到某個值得時候,系統就硬體復位。而nmi watchdog就定期(小於這個計數到達系統復位的時間)的去清一下系統的計數,若是某個程式長時間關中斷,則可能導致nmi watchdog得不到清,最終系統復位。

相關文章