一、軟鎖定:內核里的“無限循環”什么樣
Linux 把 CPU 時間分成兩類:用戶上下文、中斷上下文。內核線程、系統調用,都屬于“可搶占”的用戶上下文;而硬中斷、軟中斷、NMI,則是“不可被普通搶占”的中斷上下文。軟鎖定(soft lockup)指的是:某顆核在“可搶占”上下文里,持續占有 CPU 超過閾值(默認 20s),卻一次都不讓出調度器。換句話說,內核自己跑死循環了,而且自愿關掉搶占。結果是:
-
該核不響應任何普通中斷,定時器、調度、RCU、負載均衡統統卡住;
-
其他核仍可運行,系統看似“活”,但負載飆高、響應遲鈍;
-
由于搶占被關,內核線程無法遷移,top 里會看到某線程 100% 且跑滿整個閾值。
觸發條件通常是:自旋鎖死循環、preempt_disable 后忘記 enable、內核模塊在回調里做重度計算。軟鎖定不是 panic,而是“內核還有口氣,但已無法自救”。
二、看門狗:給 CPU 綁一根“脈搏繩”
看門狗(watchdog)的核心思想:預期 CPU 每隔固定時間“報一次心跳”,若錯過,就認為系統失去響應,于是拉閘復位。Linux 存在兩層看門狗:
-
軟看門狗(software watchdog):內核在高精度定時器里每 4s 寫一次“喂狗”計數器;
-
硬看門狗(hardware watchdog):主板或 SoC 里的獨立外設,一旦錯過心跳,直接斷言 reset 引腳。
軟鎖定檢測正是依附軟看門狗:每 CPU 都有一個“時鐘戳”變量,時鐘中斷里更新;而看門狗線程(watchdog/x)每秒檢查一次,若發現某核時鐘戳超過 20s 未變,就打印 soft lockup 日志,并視配置觸發 panic 或僅告警。硬看門狗則“冷眼旁觀”:若軟鎖定導致整個系統都不再喂狗,它就出手復位。于是形成“軟鎖定→日志→硬狗超時→強制重啟”的階梯式守護鏈。
三、日志解讀:那一行 soft lockup 到底在說什么
典型信息:
CPU#3 stuck for 22s ... [module: foo+0x1234]
字段拆解:
CPU#3 stuck for 22s ... [module: foo+0x1234]
字段拆解:
-
CPU#3:受害者核,注意不一定是肇事線程當前核,而是“時鐘戳卡住的核”;
-
22s:持續關搶占時間,大于閾值才觸發打印;
-
module: foo+0x1234:看門狗采樣到的 PC 指針,往往落在死循環函數;
-
堆棧跟蹤:軟狗檢測線程會打印受害核的 backtrace,包含調用鏈、鎖信息、搶占計數。
若看到 preempt_count = 1,說明有人關搶占后沒開;若 rcu_state 顯示 expired,說明 RCU 也受害。日志是“尸體照片”,卻足夠讓你定位到函數名,甚至源碼行。
四、誘發場景:那些“日常”操作如何踩雷
-
自旋鎖嵌套:外層鎖 A,內層鎖 B,又回調回 A,形成 ABBA 死鎖,但自旋鎖不睡眠,只會死循環;
-
中斷風暴:網卡驅動在高負載下頻繁進入硬中斷,軟中斷里調用 local_bh_disable 后陷入重計算,導致軟中斷線程長時間關搶占;
-
內核模塊 bug:ioctl 回調里 for(;;) 忘記 cond_resched(),看似無害,卻在搶占關閉路徑里跑飛;
-
實時進程失控:FIFO 調度策略的實時任務死循環,且優先級高于看門狗線程,軟鎖定檢測自身都被餓死。
這些場景的共同特征:關搶占、不睡眠、不自愿調度。寫內核代碼時,只要牢記“禁止搶占 → 必須短 → 必須可調度”,就能規避 90% 的軟鎖定。
五、調試利器:從 sysrq 到 ftrace,給死循環“拍 X 光”
系統還活著,只是某核卡住,可用魔法鍵:
echo l > /proc/sysrq-trigger
立即打印所有 CPU 的堆棧,比軟鎖定日志更全;
echo t > /proc/sysrq-trigger
打印任務狀態,可看鎖持有信息。
若問題可復現,提前打開 ftrace:
echo function > current_tracer
echo preempt_disable > set_ftrace_filter
再跑負載,跟蹤關搶占與開搶占的配對情況,找出“只關不開”的函數。對于動態加載的模塊,可用 kprobe 在 local_irq_disable 處插樁,記錄調用鏈。 traces 是“動態心電圖”,能讓你看到“脈搏”何時消失、消失前最后一次心跳在哪里。
echo l > /proc/sysrq-trigger
立即打印所有 CPU 的堆棧,比軟鎖定日志更全;
echo t > /proc/sysrq-trigger
打印任務狀態,可看鎖持有信息。
若問題可復現,提前打開 ftrace:
echo function > current_tracer
echo preempt_disable > set_ftrace_filter
再跑負載,跟蹤關搶占與開搶占的配對情況,找出“只關不開”的函數。對于動態加載的模塊,可用 kprobe 在 local_irq_disable 處插樁,記錄調用鏈。 traces 是“動態心電圖”,能讓你看到“脈搏”何時消失、消失前最后一次心跳在哪里。
六、預防之道:鎖、搶占、信號量的“三駕馬車”
-
鎖策略:能用互斥鎖就不用自旋鎖;必須自旋時,確保臨界區無循環、無睡眠、無調度點;
-
搶占模型:關搶占后立即設定“最大停留時間”,可用 preempt_count_add() 配對,或用 local_irq_save() 縮短關中斷窗口;
-
信號量與完成量:長時間等待外部事件,用 wait_for_completion_timeout(),讓出 CPU,避免“忙等”。
此外,打開 CONFIG_LOCKDEP 與 CONFIG_PREEMPT_RT,讓內核在調試期幫你跟蹤鎖依賴與實時瓶頸。記住:軟鎖定不是“性能問題”,而是“正確性問題”,預防成本遠低于線上救火。
七、硬看門狗實戰:選型、喂狗、與軟狗協同
硬件看門狗形式多樣:SuperIO 芯片、SoC 內部定時器、IPMI BMC 控制器。Linux 統一抽象為 /dev/watchdog 字符設備。用戶空間可通過 ioctl 設置超時、喂狗、或觸發“魔法關閉”。典型流程:
-
系統啟動時,systemd 自動打開設備,記錄超時時間;
-
內核軟狗每 4s 喂一次;
-
若軟鎖定導致喂狗停止,倒計時歸零,硬件狗斷言 reset;
-
重啟后,BMC 日志里留下“Watchdog reset”字段,供運維定位。
若擔心“誤殺”,可調長超時,或啟用“預超時中斷”——在還剩一半時間時,內核先收到 NMI,有機會打印堆棧,再決定是否繼續等待。硬狗是“最后的大錘”,但錘子也有重量,調得太短會誤傷正常高負載,調得太長又失去守護意義。經驗值:30s 預超時 + 60s 復位,適合大多數生產環境。
八、容器與虛擬化:軟鎖定在云時代的“新面具”
在宿主機超售場景,vCPU 可能被調度器搶出物理核,導致“時鐘中斷”延遲,進而觸發“虛假”軟鎖定。內核日志里會出現“clocksource watchdog: Marking clocksource ‘tsc’ as unstable”,緊接著是 soft lockup。解決路徑:
-
改用高精度時鐘源 hpet 或 acpi_pm;
-
在虛擬機配置里開啟 hv_stimer 或 arch_timer,讓半虛擬化時鐘接管;
-
給容器綁核,避免 vCPU 被頻繁遷移;
-
關閉宿主機的 C-State 深度節能,減少核心喚醒延遲。
云環境下的軟鎖定,往往是“時鐘”而非“死循環”作祟,需要結合時鐘源、調度延遲、steal time 綜合判斷,避免盲目重啟。
九、用戶空間看門狗:systemd、watchdogd 與自定義守護
若硬件狗不存在,可用 systemd 的 systemd-watchdog 模塊:服務單元里聲明 WatchdogSec=30s,進程每 30s 調用 sd_notify(0, "WATCHDOG=1"),否則 systemd 重啟該服務。對于 Node.js、Java、Go 應用,可通過 libsystemd 包裝 “喂狗”調用,把“業務死循環”轉化為“服務重啟”,而非整系統復位。自定義看門狗守護則更進一步:監控進程池、線程池、GC 停頓,若發現“邏輯死鎖”但內核依舊活著,可選擇性重啟進程,保留系統其余部分。分層看門狗策略,讓“復位”從“核按鈕”降級為“服務重啟”,最大限度減少用戶感知。
十、故障復盤:一次真實軟鎖定的“解剖室”記錄
生產環境凌晨 3 點,監控系統報“ CPU usage 100% 持續 5min”,但 SSH 可登錄。查看日志:
soft lockup: CPU#7 stuck for 120s, module: iptable_nat
堆棧指向 nf_nat_packet 里的 while (skb) 循環。進一步用 ftrace 發現:某條連接跟蹤項因 NAT 端口復用導致鏈表成環,iptables 在關中斷路徑里遍歷鏈表,永不結束。修復:升級內核,補丁已在 5.15 中解決循環引用。復盤啟示:
soft lockup: CPU#7 stuck for 120s, module: iptable_nat
堆棧指向 nf_nat_packet 里的 while (skb) 循環。進一步用 ftrace 發現:某條連接跟蹤項因 NAT 端口復用導致鏈表成環,iptables 在關中斷路徑里遍歷鏈表,永不結束。修復:升級內核,補丁已在 5.15 中解決循環引用。復盤啟示:
-
軟鎖定不一定是“自己寫的模塊”,主線內核也可能踩坑;
-
堆棧里若出現第三方模塊,先禁用再觀察;
-
保持內核在穩定維護版本,比任何“調參”都有效。
記錄完整時間線、保留 vmcore、對比升級前后軌跡,才能讓“下一次”不再重演。
十一、心理建設:軟鎖定不是“失敗”,而是“暴露”
很多團隊把軟鎖定當成“偶發噪聲”,重啟掩蓋,日積月累,最終在某次促銷活動中集體爆發。正確心態是:每一次軟鎖定都是內核給出的“免費體檢報告”。它告訴你:
-
鎖設計不合理;
-
實時策略濫用;
-
模塊缺少 cond_resched;
-
看門狗閾值過寬。
把報告納入迭代,把“重啟”改為“修復”,才能讓系統從“看似穩定”走向“真正健壯”。記住:看門狗不是敵人,它是內核的“良心”,在系統失去心跳時,替你喊停。
尾聲:讓心跳持續,讓復位稀少
軟鎖定與看門狗,是一對“生死雙生”的機制:前者負責“吶喊”,后者負責“拉閘”。理解它們的原理、日志、調試與預防,就像給系統裝上“心電圖”(軟狗)與“除顫器”(硬狗)。你無需祈禱系統永不死鎖,只需確保:
-
鎖盡可能短;
-
搶占盡可能開;
-
看門狗閾值盡可能貼合業務;
-
每一次復位都有跡可循、有據可查。
如此,當下一次凌晨電話響起,你可以從容地打開日志,指著那一行 soft lockup 說:“別怕,只是內核在提醒我們——該修心了。”