Linux內核發生死鎖怎么解決
時間:2023-09-09 來源:華清遠見
一、什么是Linux內核死鎖
死鎖是指多個進程(線程)因為長久等待已被其他進程占有的的資源而陷入阻塞的一種狀態。當等待的資源一直得不到釋放,死鎖會一直持續下去。死鎖一旦發生,程序本身是解決不了的,只能依靠外部力量使得程序恢復運行,例如重啟,開門狗復位等。
#Linux 提供了檢測死鎖的機制,主要分為 D 狀態死鎖和 R 狀態死鎖。
1.D 狀態死鎖:
進程等待 I/O 資源無法得到滿足,長時間(系統默認配置 120 秒)處于 TASK_UNINTERRUPTIBLE 睡眠狀態,這種狀態下進程不響應異步信號(包括 kill -9)。如:進程與外設硬件的交互(如 read),通常使用這種狀態來保證進程與設備的交互過程不被打斷,否則設備可能處于不可控的狀態。對于這種死鎖的檢測 Linux 提供的是 hung task 機制。觸發該問題成因比較復雜多樣,可能因為 synchronized_irq、mutex lock、內存不足等。D 狀態死鎖只是局部多進程間互鎖,一般來說只是 hang 機、凍屏,機器某些功能沒法使用,但不會導致沒喂狗,而被狗咬死。
2.R 狀態死鎖:
進程長時間(系統默認配置 60 秒)處于 TASK_RUNNING 狀態壟斷 CPU 而不發生切換,一般情況下是進程關搶占或關中斷后長時候執行任務、死循環,此時往往會導致多 CPU 間互鎖,整個系統無法正常調度,導致喂狗線程無法執行,無法喂狗而最終看門狗復位的重啟。該問題多為原子操作,spinlock 等 CPU 間并發操作處理不當造成。
#死鎖的產生需要滿足以下4個條件:
1》 互斥條件:指運算單元(進程、線程或協程)對所分配到的資源具有排它性,也就是說在一段時間內某個鎖資源只能被一個運算單元所占用。
2》 請求和保持條件:指運算單元已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它運算單元占有,此時請求運算單元阻塞,但又對自己已獲得的其它資源保持不放。
3》 不可剝奪條件:指運算單元已獲得的資源,在未使用完之前,不能被剝奪。
4》 環路等待條件:指在發生死鎖時,必然存在運算單元和資源的環形鏈,即運算單元正在等待另一個運算單元占用的資源,而對方又在等待自己占用的資源,從而造成環路等待的情況。
#常見的死鎖有以下4種情況:
(1)進程重復申請同一個鎖,稱為AA死鎖。例如,重復申請同一個自旋鎖;使用讀寫鎖,第一次申請讀鎖,第二次申請寫鎖。
(2)進程申請自旋鎖時沒有禁止硬中斷,進程獲取自旋鎖以后,硬中斷搶占,申請同一個自旋鎖。這種AA死鎖很隱蔽,人工審查很難發現。
(3)兩個進程都要獲取鎖L1和L2,進程1持有鎖L1,再去獲取鎖L2,如果這個時候進程2持有鎖L2并且正在嘗試獲取鎖L1,那么進程1和進程2就會死鎖,稱為AB-BA死鎖。
(4)在一個處理器上進程1持有鎖L1,再去獲取鎖L2,在另一個處理器上進程2持有鎖L2,硬中斷搶占進程2以后獲取鎖L1。這種AB-BA死鎖很隱蔽,人工審查很難發現。
二、如何解決Linux內核死鎖
死鎖的常用解決方案有以下兩個:
1.按照順序加鎖:嘗試讓所有線程按照同一順序獲取鎖,從而避免死鎖。
2.設置獲取鎖的超時時間:嘗試獲取鎖的線程在規定時間內沒有獲取到鎖,就放棄獲取鎖,避免因為長時間等待鎖而引起的死鎖。
Linux內核死鎖檢測lockdep
1.1 使用方法
死鎖檢測工具lockdep的配置宏如下:
(1)CONFIG_LOCKDEP:在配置菜單中看不到這個配置宏,打開配置宏CONFIG_PROVE_LOCKING或CONFIG_DEBUG_LOCK_ALLOC的時候會自動打開這個配置宏。
(2)CONFIG_PROVE_LOCKING:允許內核報告死鎖問題。
(3)CONFIG_DEBUG_LOCK_ALLOC:檢查內核是否錯誤地釋放被持有的鎖。
(4)CONFIG_DEBUG_LOCKING_API_SELFTESTS:內核在初始化的過程中運行一小段自我測試程序,自我測試程序檢查調試機制是否可以發現常見的鎖缺陷。
1.2 技術原理
死鎖檢測工具lockdep操作的基本對象是鎖類,例如結構體里面的鎖是一個鎖類,結構體的每個實例里面的鎖是鎖類的一個實例。
lockdep跟蹤每個鎖類的自身狀態,也跟蹤各個鎖類之間的依賴關系,通過一系列的驗證規則,確保鎖類狀態和鎖類之間的依賴總是正確的。另外,鎖類一旦在初次使用時被注冊,后續就會一直存在,它的所有具體實例都會關聯到它。
1)鎖類狀態
lockdep為鎖類定義了(4n+1)種使用歷史狀態,其中的4指代如下:
(1)該鎖曾在STATE上下文中被持有過。
(2)該鎖曾在STATE上下文中被以讀鎖形式持有過。
(3)該鎖曾在開啟STATE的情況下被持有過。
(4)該鎖曾在開啟STATE的情況下被以讀鎖形式持有過。
其中的n是STATE狀態的個數,STATE狀態包括硬中斷(hardirq)、軟中斷(softirq)和reclaim_fs(__GFP_FS分配,表示允許向下調用到文件系統。如果文件系統持有鎖以后使用標志位__GFP_FS申請內存,在內存嚴重不足的情況下,需要回收文件頁,把修改過的文件頁寫回到存儲設備,遞歸調用文件系統的函數,可能導致死鎖)。其中的1是指該鎖曾經被使用過。
如果鎖曾在硬中斷上下文中被持有過,那么鎖是硬中斷安全的(hardirq-safe);
如果鎖曾在開啟硬中斷的情況下被持有過,那么鎖是硬中斷不安全的(hardirq-unsafe)。
2)檢查規則
單鎖狀態規則如下:
(1)一個軟中斷不安全的鎖類也是硬中斷不安全的鎖類。
(2)任何一個鎖類,不可能同時是硬中斷安全的和硬中斷不安全的,也不可能同時是軟中斷安全的和軟中斷不安全的。也就是說:硬中斷安全和硬中斷不安全是互斥的,軟中斷安全和軟中斷不安全也是互斥的。
多鎖依賴規則如下:
(1)同一個鎖類不能被獲取兩次,否則可能導致遞歸死鎖(AA死鎖)。
(2)不能以不同順序獲取兩個鎖類,否則導致AB-BA死鎖。
(3)不允許在獲取硬中斷安全的鎖類之后獲取硬中斷不安全的鎖類。
硬中斷安全的鎖類可能被硬中斷獲取。假設處理器0上的進程首先獲取硬中斷安全的鎖類A,然后獲取硬中斷不安全的鎖類B;處理器1上的進程獲取鎖類B,硬中斷搶占進程,獲取鎖類A,可能導致AB-BA死鎖。
(4)不允許在獲取軟中斷安全的鎖類之后獲取軟中斷不安全的鎖類。
軟中斷安全的鎖類可能被軟中斷獲取。假設處理器0上的進程首先獲取軟中斷安全的鎖類A,然后獲取軟中斷不安全的鎖類B;處理器1上的進程獲取鎖類B,軟中斷搶占進程,獲取鎖類A,可能導致AB-BA死鎖。
當鎖類的狀態發生變化時,檢查下面的依賴規則:
(1)如果鎖類的狀態變成硬中斷安全,檢查過去是否在獲取它之后獲取硬中斷不安全的鎖。
(2)如果鎖類的狀態變成軟中斷安全,檢查過去是否在獲取它之后獲取軟中斷不安全鎖。
(3)如果鎖類的狀態變成硬中斷不安全,檢查過去是否在獲取硬中斷安全的鎖之后獲取它。
(4)如果鎖類的狀態變成軟中斷不安全,檢查過去是否在獲取軟中斷安全的鎖之后獲取它。
如何防止Linux內核死鎖
1.減少同步代碼塊嵌套操作
2.降低鎖的使用粒度,不要幾個功能共用一把鎖
3.盡量采用tryLock(timeout)的方法,可以設置超時時間,這樣超時之后,就可以主動退出,防止死鎖(關鍵)

