printk死結是指在使用Linux核心中的printk
函數列印日誌時,由於某種原因導致系統中兩個或多個線程相互等待對方釋放資源而無法繼續執行的情況。printk死結會影響系統應用程式和業務的正常運行,最終觸發核心宕機,所以預防和及時解決printk死結對於維持系統穩定性和可靠性至關重要。本文介紹Alibaba Cloud Linux系統出現printk死結導致系統宕機的原因及解決方案。
問題描述
在使用Alibaba Cloud Linux發生核心宕機時,分析vmcore檔案存在以下現象:
當系統發生宕機時,會產生轉儲檔案vmcore,您可以通過查看該檔案擷取宕機時的核心日誌,並根據其中的calltrace資訊(通常以"Call Trace:"開頭)來定位問題的發生位置,分析宕機原因,從而進行修複和調試。
核心日誌(dmesg)存在一些
warning
層級的列印日誌,通常與調度(scheduling)、工作隊列(work_queue)有關。分析vmcore檔案會發現某個進程的calltrace資訊有如下特徵:
最近的函數是關於擷取自旋鎖的調用,比如
_raw_spin_lock
、queued_spin_lock_slowpath
、raw_spin_rq_lock_netsted
等。函數調用路徑是從
printk
到console_unlock
,再到上述擷取自旋鎖的調用。
問題原因
系統宕機是由Linux社區的printk機制長期存在死結的問題導致的,且是已知問題,該問題出現機率較低,但在Alibaba Cloud Linux 3的5.10.134-16.3
核心版本出現該問題的機率偏高。
為什麼會出現printk死結?
核心在持有work_queue(工作隊列)或runqueue(運行隊列,簡稱rq)的自旋鎖後,如果調用printk函數列印核心日誌,printk的底層實現會去調用drm驅動程式,而drm驅動程式由於自身的實現原因又會去嘗試對work_queue或rq進行加鎖,導致printk死結,最終導致系統宕機。
說明drm驅動嘗試加鎖的詳細資料,請參考drm/fb-helper: Add fb_deferred_io support補丁。
為什麼調度(scheduling)和工作隊列(work_queue)會出現
warning
日誌?由於核心持有work_queue或rq的自旋鎖並使用printk函數列印日誌的情境,又通常發生在調度(scheduling)和工作隊列(work_queue)實現代碼的warning路徑上,所以可以觀察到核心的dmesg資訊中出現調度(scheduling)和工作隊列(work_queue)相關的警示資訊。
為什麼Alibaba Cloud Linux 3的
5.10.134-16.3
核心版本出現該問題的機率偏高?事實上,調度(scheduling)和工作隊列(work_queue)列印
warning
日誌的情況並不多,但是在Alibaba Cloud Linux 3的5.10.134-16.3
核心版本中,從Linux上遊社區回合的非同步unthrottle特性存在迴歸缺陷,增大了觸發列印warning
日誌的機率,從而導致Alibaba Cloud Linux 3中出現該問題的機率偏高。
影響範圍
該問題目前是Linux社區共性問題,且觸發該問題的補丁drm/fb-helper: Add fb_deferred_io support是在Linux上遊社區的v4.10版本引入。
在Alibaba Cloud Linux的
4.19
和5.10
核心版本存在該問題。在Alibaba Cloud Linux 3的
5.10.134-16.3
核心版本中出現該問題的機率更高。
解決方案
當您發現此類問題時,可以通過以下命令調整printk的列印等級,阻止warning
層級的日誌列印到串口的方式避開該問題。
如果您的日誌系統是依賴捕獲串口輸出而非核心日誌(dmesg)資訊,請謹慎評估後使用。
該方法會導致串口中的
warning
日誌缺失,但不會影響核心日誌(dmesg)中的warning
日誌。
sysctl -w kernel.printk="<console_loglevel> <default_message_loglevel> <minimum_console_loglevel> <default_console_loglevel>" >> /etc/sysctl.conf
其中kernel.printk
的四個值說明如下:
console_loglevel
:表示高於(不含)此優先順序的資訊會被列印至串口。default_message_loglevel
:如果在printk時未指定列印等級,則預設使用該等級。minimum_console_loglevel
:表示console_loglevel
可以被設定的最小值。default_console_loglevel
:表示console_loglevel
的預設值。
由於Linux將核心的日誌列印等級分為8個,等級越低,優先順序越高。因此需要阻止warning
層級的日誌列印到串口,則console_loglevel
最大設定為4才合理。命令樣本如下:
sysctl -w kernel.printk="4 4 1 7" >> /etc/sysctl.conf
日誌列印等級說明:
#define LOGLEVEL_EMERG 0 /* system is unusable */
#define LOGLEVEL_ALERT 1 /* action must be taken immediately */
#define LOGLEVEL_CRIT 2 /* critical conditions */
#define LOGLEVEL_ERR 3 /* error conditions */
#define LOGLEVEL_WARNING 4 /* warning conditions */
#define LOGLEVEL_NOTICE 5 /* normal but significant condition */
#define LOGLEVEL_INFO 6 /* informational */
#define LOGLEVEL_DEBUG 7 /* debug-level messages */