Linux hung_task故障分析:从真实日志到Root Cause的Case Study
第8章:真实Case Study——从一条hung_task日志到Root Cause
0. Case边界、版本基线与证据声明
本章分析的真实日志头部是:
1 | |
目前没有完整Call Trace、Wait Object地址、Owner/Producer栈、IRQ/I/O trace、设备寄存器和firmware日志。因此本章不会编造最终Root Cause,而是展示如何从有限事实建立可继续执行的Case Analysis。
前文的机制说明以主线Linux v6.1标签提交830b3c68c1fb1e9176028d02ef86f3cf76aa2476为基线;本案例运行的是厂商派生内核6.1.83-DR-PL5.1_V1.0.16。最终符号、调用路径和行为必须以该构建对应的源码、.config、vmlinux及模块为准,不能把主线v6.1路径直接当作现场事实。
8.1 本章不再增加理论
本章只执行前七章的方法:
1 | |
重点是展示什么时候能下结论、什么时候必须停下来补证据。
8.2 Case基本信息
| 字段 | 已知值 |
|---|---|
| Task/comm | as_mapreloc |
| PID | 3004 |
| State | 日志显示D状态背景 |
| Reported duration | 超过184秒 |
| Kernel release | 6.1.83-DR-PL5.1_V1.0.16 |
| Taint | P、O |
| 完整Call Trace | 缺失 |
| Root Cause | 未确认 |
任务名可能截断且不唯一,PID也可能在系统重启或进程退出后失效。
8.3 第一步:只写事实,不做推断
可以确认:内核hung_task Detector报告as_mapreloc:3004长时间没有观察到调度进展,运行内核带有专有和树外模块taint标志。
不能确认:它在等锁、Completion、I/O还是远端服务;不能确认厂商模块就是责任方;更不能确认死锁、IRQ丢失或硬件损坏。
8.4 从state:D能确认什么
D是用户可见的不可中断睡眠表示,说明任务当前主要处于阻塞而非持续占CPU的busy loop。分析方向应转为:它怎样睡下、等待哪个对象、谁负责推进该对象。
仅凭D状态不能知道Wait Object,也不能区分正常长延迟和异常永久停滞。
8.5 从blocked >184s能确认什么
v6.1检测器根据任务context switch计数和last_switch_time判断长期无进展,日志秒数受扫描周期和调度时机影响。因此“超过184秒”是检测尺度,不是某个业务API入口的精密秒表。
它足以证明异常值得调查,但不足以证明准确起点。
8.6 从Tainted: P O能确认什么
按相应内核文档,P通常表示加载过专有模块,O表示树外模块。它说明现场不是纯上游内核,并提示需要厂商源码、符号和模块信息。
taint是环境证据,不是责任判定。必须用Wait Object和依赖链把问题连接到具体模块,才能评价其相关性。
8.7 当前最缺的第一份证据:完整Call Trace
完整栈决定第一层分类:
1 | |
没有栈,就无法确认Wait Primitive,更无法解析Wait Object。
8.8 Case分析总路线
1 | |
每一箭头必须附证据和置信度。
8.9 如果Call Trace显示wait_for_completion
只能先写:
1 | |
回到厂商对应源码,定位类似wait_for_completion(&req->done)的直接调用点,记录req实例、生命周期和请求ID。随后搜索本对象的complete()、complete_all()及reinit_completion()路径。
8.10 Completion路径下一步怎么查
建立候选Producer列表:IRQ handler、threaded IRQ、worker、timer、DMA callback或另一个task。对每个Producer回答:是否被调度/触发、是否处理同一对象、是否走到complete分支、是否存在错误早退。
静态源码证明“应该完成”,trace或现场状态才能证明“实际执行过”。
8.11 如果Producer是IRQ
依赖图初版:
1 | |
补充/proc/interrupts前后计数、irq_handler_entry/exit、ISR状态位和driver complete事件。共享IRQ的entry不能证明目标设备确实完成。
8.12 如果Call Trace显示mutex_lock
第一层只确认:
1 | |
需要从调用实参确认具体M,并利用vmcore、锁trace、lockdep或其他task stack找现场Owner。静态搜索只能列出可能持锁路径。
8.13 Mutex场景的完整链
可能形成:
1 | |
此时Lock只是阻塞传播节点,最终Root Cause可能位于Task B的下游。找到Owner后必须继续执行同一SOP。
8.14 如果Call Trace显示io_schedule
这说明睡眠具有I/O语义,但io_schedule()仍是框架。下一步从Business Wait Point定位page/folio、bio、request、journal、RPC或具体设备对象。
直接写“block layer bug”属于越过证据。
8.15 IO场景继续怎么查
本地存储链可能是:
1 | |
用block issue/complete、queue状态、timeout/reset、IRQ和设备日志识别请求在哪一层停滞。需要request ID或设备/扇区等字段防止错误配对。
8.16 如果Call Trace显示NFS或RPC
依赖跨越本机和远端:
1 | |
收集RPC状态与重传、挂载参数、网络收发、服务端和远端存储证据。客户端D状态可能只是远端故障的表现。
8.17 如果Call Trace显示Memory或Reclaim
try_to_free_pages()、dirty throttling、writeback或folio wait不能直接证明内存管理Bug。继续追:
1 | |
记录GFP/order、zone水位、dirty/writeback、vmscan和块完成事件。
8.18 当前Case最重要的原则
无论完整栈落入哪类,都必须继续:
1 | |
第一个阻塞函数通常不是最终Root Cause。
8.19 Case Analysis Template
1 | |
8.20 怎样判断Confidence
本文采用四级:
1 | |
当前案例Root Cause等级是Unknown。
8.21 Confirmed需要什么
至少应明确Wait Object、Owner/Producer和First Non-progress Node;依赖边有对象级现场或trace证据;替代假设得到检查;修复改变对应机制;旧版本复现而修复版本通过回归。
“改完后偶尔没再出现”不等于Confirmed。
8.22 Strongly Supported
例如源码证明Completion C只在IRQ X完成,trace确认命令提交后无IRQ X且buffer窗口完整,但缺少设备寄存器。可写:完成链在IRQ交付前停滞得到强支持,但无法区分mask、firmware或hardware。
8.23 Suspected
若只有一条wait_for_completion()栈和驱动函数名,只能怀疑Producer未推进。没有具体Completion、Producer和timeline时,不能升级为IRQ、firmware或硬件结论。
8.24 Evidence Table模板
| Hop | Claim | Evidence | Confidence | Missing Evidence |
|---|---|---|---|---|
| 1 | as_mapreloc等待对象X |
完整栈+调用点 | 待补 | 对象实例 |
| 2 | X由Producer P推进 | 对应版本源码 | 待补 | 本次请求关联 |
| 3 | P未推进 | trace/runtime | 待补 | 触发与状态证据 |
| 4 | 底层原因是Y | 寄存器/FW/复现 | 待补 | 替代假设排除 |
当前只有日志头,因此表中任何具体X、P、Y都不能预填为事实。
8.25 Dependency Graph模板
Completion候选图:
1 | |
图中的C、P、E在证据出现前标为占位符,不能把教学示例当案例事实。
8.26 如果是Lock场景
1 | |
需要证明M实例、B确实持有M、B的当前栈以及下一条依赖。若链最终回到as_mapreloc或已有节点,才调查Cycle。
8.27 如果是IO场景
1 | |
每一层用对象ID、issue/complete、timeout、网络/设备状态证明。不能仅凭函数前缀合并不同请求。
8.28 真正要找的是First Non-progress Point
它是沿依赖方向第一个“应发生的进展没有发生”的边界。例如已确认submit和DMA start,而目标IRQ在完整窗口中缺失,则停止区间位于DMA完成、硬件/firmware和IRQ交付之间。
这仍是区间而非最终根因,需要更多底层状态继续缩小。
8.29 Flight Recorder应该怎么配置
在尚不知分类时,从低开销基础事件开始:
1 | |
拿到完整栈后按分类添加block、workqueue、vmscan/writeback、nfs/rpc或driver事件。必须实测buffer历史能覆盖预期触发间隔。
8.30 Driver最好增加哪些Tracepoint
若Business Wait Point落在自有驱动,建议围绕状态机增加:
1 | |
只添加能回答依赖链问题的结构化事件,避免用大量printk替代设计。
8.31 事件里必须带ID
并发请求必须通过command/request ID贯穿host、firmware和hardware tag:
1 | |
没有ID时,相邻事件不一定属于同一请求。
8.32 真实时间线应该长什么样
正常候选:
1 | |
这只是期望模型,需要用对应驱动源码确认真实事件顺序。
8.33 异常时间线
1 | |
“没有事件”只有在event已启用、filter正确、window完整且无drop时才有证明力。
8.34 为什么不要只靠dmesg
dmesg善于保存告警和离散状态,但console输出延迟、多CPU交错、ring覆盖及printk扰动使其难以还原细粒度时序。它通常告诉你最终症状,不一定保存最后一次submit、IRQ和wake。
8.35 为什么不要只靠ftrace
trace说明事件发生顺序,源码解释这些事件的契约和对象关系,运行时状态补充未被trace覆盖的内部值,复现验证因果稳定性。
1 | |
四者相互印证才构成强证据。
8.36 真实Case分析阶段1:Static Analysis
拿到完整栈和对应厂商源码后:标记框架帧、Wait Primitive、Business Point;定位调用实参;记录Wait Object生命周期;搜索Owner/Producer、完成、唤醒、取消和错误路径;画Expected Dependency Chain。
主线v6.1只用于理解机制,不能替代厂商补丁源码。
8.37 真实Case分析阶段2:Runtime Analysis
现场仍在时采集:
1 | |
记录采集时间,避免把不同时间点状态错误拼成同时态。
8.38 真实Case分析阶段3:Temporal Analysis
Flight Recorder回答:Waiter何时切出、是否被wake、Producer是否运行、IRQ是否到达、request是否issue/complete、driver状态机最后停在哪个事件。
输出是带ID的Actual Timeline,而不是一堆未经关联的trace文本。
8.39 真实Case分析阶段4:Reproduction
记录触发条件、频率、负载、硬件和环境。多次复现是否停在同一对象和First Non-progress区间,决定假设可信度。
复现不解释机制,但能检验证据是否稳定以及修复是否真正相关。
8.40 真实Case分析阶段5:Fault Injection
在测试环境模拟怀疑的异常:延迟或跳过Completion、屏蔽测试IRQ、让firmware不回复、注入I/O错误或持锁延迟。比较生成的stack、timeline和恢复行为是否与现场同构。
异常注入必须具备超时、reset和安全退出,不能在生产机制造不可恢复D状态。
8.41 如何避免错误修复
把wait_for_completion()改成timeout后告警消失,只证明永久等待被限制,不证明Producer故障修复。还要检查迟到Completion、对象回收、重复完成、reset并发和错误返回传播。
修复验收应针对First Non-progress机制,而不是仅针对告警文本。
8.42 应该同时记录三类Fix
1 | |
三者可以同时需要,但报告中不能互相替代。
8.43 Root Cause Fix例子
根据最终证据可能是:补正确的IRQ使能/路由、修正DMA descriptor或IOMMU映射、补齐错误路径complete()、统一锁顺序、修复firmware状态机。
在当前案例尚未获得完整栈前,以上都只是类别示例,不是修复建议。
8.44 Fault Containment例子
带超时等待、命令取消、设备reset、有限重试、fail-fast和功能降级可以防止单个外部故障无限传播。必须定义超时后的对象所有权和迟到回调处理。
8.45 Observability Improvement例子
增加command ID、最后提交/完成ID、IRQ计数、timeout状态快照、driver tracepoint、pstore或可导出的Flight Recorder。观测改进应有开销预算和敏感信息审查。
8.46 Regression Test怎么设计
至少包含原场景、长稳、高并发、资源压力、错误注入、reset/recovery、设备remove以及性能回归。每个测试验证:任务不会永久D、错误正确返回、对象生命周期安全、依赖链能恢复推进。
8.47 对于Driver类问题
覆盖正常完成、IRQ延迟/缺失、DMA timeout、firmware不回复、设备reset、迟到IRQ和并发命令。检查重复完成、UAF、资源泄漏和错误码。
8.48 对于Lock类问题
覆盖高竞争、不同锁顺序、异常早退、取消、嵌套锁和Owner下游阻塞。启用合适调试配置时结合lockdep,但不能只以“无lockdep告警”判定通过。
8.49 对于NFS或IO类问题
覆盖网络延迟/断链、服务端不可用、存储慢/timeout、队列freeze、重连和恢复。验证挂载/重试策略符合业务期望,并能保留诊断证据。
8.50 最终Bug Report推荐结构
1 | |
8.51 Case Summary模板
当前可以严谨写成:
1 | |
8.52 为什么这段结论合理
它区分了已确认事实与缺失证据,没有把D状态等同于deadlock,也没有把taint等同于厂商模块责任。它还给出了能改变调查状态的下一步,而不是泛泛地说“继续看日志”。
8.53 完整Call Trace拿到后怎么继续
1 | |
8.54 如果只有一次现场怎么办
立即保存完整dmesg/串口、内核构建信息、模块列表、taint、all task stacks、/proc/interrupts、目标PID stack/status、trace buffer、设备寄存器和firmware日志。原始文件只读归档,分析时制作副本。
采集动作本身可能改变现场,应按价值和风险排序。
8.55 如果系统很快恢复
恢复后的/proc只表示当前状态,不能代替故障时状态。下一版应部署常驻Flight Recorder,在driver timeout或更早错误点snapshot/traceoff,并持久化关键状态。
8.56 如果系统完全卡死
可评估hung_task_panic配合kdump、ramoops/pstore、硬件watchdog dump或平台崩溃采集。启用panic会把局部阻塞升级为系统重启,必须评估服务可用性、dump可靠性和存储容量。
8.57 为什么Case Study最终必须可复现
完整闭环是:旧版本在受控条件复现、证据稳定指向同一停止点、修复直接改变机制、新版本通过原场景和异常注入、性能与恢复能力无回归。
无法稳定复现时仍可修复高置信度问题,但报告必须保留不确定性与后续观测计划。
8.58 最终Case分析总图
1 | |
8.59 本章最重要的设计思想
- 真实案例先分Fact和Hypothesis。
- D状态与184秒只证明长期不可中断等待,不证明具体子系统责任。
- 厂商6.1.83派生内核必须使用匹配源码、配置和符号。
- 完整Call Trace是确定Wait Primitive的第一关键证据。
- Wait Primitive之后继续找对象、Owner/Producer和下一跳。
- First Non-progress Point比最外层Waiter更接近根因。
- 缺失事件只有在采集完整性得到证明后才有意义。
- Source、Runtime、Trace和Reproduction应相互验证。
- 修复分为Root Cause、Containment和Observability三类。
- 没有证据闭环时,明确写Unknown比虚构Root Cause更有价值。
8.60 博客总结
as_mapreloc:3004 blocked for more than 184 seconds目前只能证明该任务被hung_task Detector判定为长期无调度进展的不可中断等待者,并且运行在带专有和树外模块taint的厂商6.1.83派生内核上。缺少完整Call Trace意味着连Wait Primitive都尚未确认,因此Root Cause必须保持Unknown。
正确的下一步是取得匹配的vendor源码、.config、vmlinux和模块符号,完整符号化栈;从Business Wait Point的实参确定Wait Object;对锁找Owner,对Completion、condition和I/O找Producer;递归构建Dependency Graph,再用运行时状态和Flight Recorder Timeline确定First Non-progress Point。最终通过Claim-Evidence-Confidence表区分直接事实、强支持推断和未知项。
这正是成熟调试和“看见D状态就猜Driver/IO/死锁”的区别:结论的强度永远不能超过证据。
8.61 学完检查题
- 当前案例已经确认的事实有哪些?
- 为什么主线v6.1不能替代厂商6.1.83源码?
state:D不能告诉你哪些信息?- 184秒为何不是业务等待入口的精确时间?
P Otaint应怎样使用,不能怎样使用?- 当前Root Cause的置信度为什么是Unknown?
- 完整Call Trace能把调查推进到哪一步?
- Completion、mutex和I/O栈分别选择什么调查方向?
- 如何确认具体Wait Object实例?
- 为什么静态源码不能证明Producer现场执行过?
- IRQ entry为何不证明目标设备完成?
- 找到mutex Owner后为什么不能停止?
io_schedule()为什么不是根因终点?- NFS等待的Producer可能位于哪里?
- reclaim栈如何继续依赖存储?
- Evidence Table为何必须包含Missing Evidence?
- Confirmed和Strongly Supported差在哪里?
- 只有单条栈时通常应使用什么置信度?
- Dependency Graph中的占位节点应怎样标记?
- First Non-progress Point与最终物理根因有何区别?
- 基础Flight Recorder应先启用哪些事件?
- 为什么拿到栈后再按分类增加事件?
- 自定义Driver事件为何需要request ID?
- “没有IRQ事件”成为证据前要检查什么?
- dmesg和ftrace各自缺少什么?
- Static Analysis的最终输出是什么?
- Runtime采集为什么必须记录时间?
- Temporal Analysis如何识别停止区间?
- Reproduction提高哪一类可信度?
- Fault Injection应具备哪些安全机制?
- timeout修复为何可能引入迟到回调问题?
- 三类Fix分别解决什么问题?
- Driver类回归应覆盖哪些异常?
- Lock类回归为何不能只看lockdep?
- NFS/IO类回归需要验证哪些恢复路径?
- 只有一次现场时应优先保存什么?
- 系统恢复后为何不能用当前stack替代故障stack?
hung_task_panic的收益和风险分别是什么?- 什么条件构成完整复现—修复—回归闭环?
- 你能否在不虚构任何证据的前提下,为本案例写出下一轮采集方案?
8.62 整个hung_task专题总结
1 | |
整个专题最终训练的不是识别某个函数名,而是持续回答三个问题:
这个Task具体在等谁?那一方为什么没有继续推进?支持这条因果边的证据是什么?