Linux死锁检测与排障实战 从Lockdep到ftrace与crash
Linux死锁检测与排障实战_从Lockdep到ftrace与crash
面向内核与系统排障的 Linux 死锁实战指南:先讲死锁成立条件与锁语义,再给开发期与线上期的工具选型、最小排障路径、典型案例和预防规范,目标是“能定位、能复现、能预防”。
目录
- 什么是 Linux 死锁
- 内核常见锁与死锁风险点
- 工具选型决策表(先看这个)
- 开发期:Lockdep 预防性检测
- 线上期:从现象到根因的排障路径
- ftrace / gdb / pstack 的定位方法
- 最小复现思路(用户态与内核态)
- 生产环境补充:kdump 与 crash 离线分析
- 预防优先:锁层级与编码规范
- 案例复盘模板
- 一页纸排障速查图(值班版)
- 附录A:常见死锁/卡死日志速查
- 附录B:用户态 vs 内核态死锁差异
- 常见误区
- 免责声明
什么是 Linux 死锁
死锁通常指多个执行体互相等待对方持有的资源,且在无外力介入下无法推进。
成立通常需同时满足四个条件:
| 条件 | 说明 |
|---|---|
| 互斥 | 资源同一时刻仅允许一个持有者 |
| 请求并保持 | 持有已有资源同时请求新资源 |
| 不可剥夺 | 资源不能被强制回收 |
| 循环等待 | 存在环状等待链 |
内核常见锁与死锁风险点
| 锁类型 | 场景 | 风险 |
|---|---|---|
| spinlock | 短临界区、不可睡眠路径 | 锁顺序错误导致忙等互卡 |
| mutex | 可睡眠路径、长临界区 | ABBA 顺序导致阻塞死锁 |
| rwlock/rwsem | 读多写少 | 升级/降级与写锁争用复杂 |
高风险场景:
- 中断上下文与进程上下文争抢同一锁
- 不同子系统对同一资源采用不一致加锁顺序
- 低内存与回收路径叠加锁竞争
工具选型决策表(先看这个)
| 场景 | 首选工具 | 目标 | 预期输出/现象 |
|---|---|---|---|
| 开发阶段、预防潜在死锁 | Lockdep | 检测锁依赖环,提前报警 | INFO: possible circular locking dependency detected |
| 线上“卡住但还活着” | top/ps/lslocks+ftrace | 找阻塞点与时序 | STAT中D增多、锁等待路径可复现 |
| 用户态多线程互锁 | gdb/pstack | 看线程栈和等待关系 | 多线程长期卡在pthread_mutex_lock |
| 系统已基本挂死 | kdump + crash | 离线分析内核转储 | vmcore 中可见阻塞任务栈与锁持有关系 |
开发期:Lockdep 预防性检测
Lockdep 通过锁依赖图检测“可能形成环”的加锁顺序。
常见内核配置(按版本与发行版调整):
CONFIG_PROVE_LOCKINGCONFIG_LOCKDEP
工作方式(简化):
- 记录“先拿 A 再拿 B”依赖边
- 新增边若形成环,输出警告
- 在“死锁真正发生前”暴露风险
Lockdep 典型日志(简化示意)
[ INFO: possible circular locking dependency detected ] CPU0 CPU1 lock(&A) lock(&B) lock(&B) lock(&A) -> deadlock potential如何读:
possible circular:潜在循环等待,不代表此刻已完全死锁CPU0/CPU1:展示两条并发路径A -> B与B -> A:锁顺序反转(ABBA)
Lockdep 的边界与局限
- 调试开销较高,不建议线上长期全量开启
- 某些复杂上下文(中断/软中断交错)可能出现难以解读的告警
- 它擅长发现“锁顺序问题”,对非锁资源循环等待需结合其它证据
线上期:从现象到根因的排障路径
基础命令:
topps-eopid,ppid,stat,comm,wchan--sort=stat lslocksdmesg|rg-i"lock|deadlock|hung"ftrace / gdb / pstack 的定位方法
ftrace(内核时序)
- 追函数进入/退出与延迟
- 还原“谁先拿锁、谁后等待”
最小命令流(示例):
cd/sys/kernel/debug/tracingecho0>tracing_onechofunction_graph>current_tracerechomutex_*>set_graph_functionecho1>tracing_on# 触发问题路径...echo0>tracing_oncattrace>/tmp/trace.txt关注点:
- 同一把锁是否被反序获取
- 某线程是否长时间停在锁相关函数
gdb / pstack(用户态线程)
info threads看线程列表thread apply all bt看全线程栈- 多次抓栈对比“长期卡在同一锁函数”
lslocks输出怎么读
| 字段 | 作用 |
|---|---|
PID/COMMAND | 谁持有或等待锁 |
TYPE | 锁类型(如 POSIX/FLOCK) |
MODE | 读锁/写锁(READ/WRITE) |
PATH | 锁关联文件路径 |
最小复现思路(用户态与内核态)
用户态 ABBA 复现
- 线程 A:先锁
L1再锁L2 - 线程 B:先锁
L2再锁L1 - 运行并用
gdb/pstack捕捉栈
可直接编译的最小示例:
#include<pthread.h>#include<stdio.h>#include<unistd.h>staticpthread_mutex_tm1=PTHREAD_MUTEX_INITIALIZER;staticpthread_mutex_tm2=PTHREAD_MUTEX_INITIALIZER;void*t1(void*arg){pthread_mutex_lock(&m1);usleep(100000);pthread_mutex_lock(&m2);returnNULL;}void*t2(void*arg){pthread_mutex_lock(&m2);usleep(100000);pthread_mutex_lock(&m1);returnNULL;}intmain(void){pthread_ta,b;pthread_create(&a,NULL,t1,NULL);pthread_create(&b,NULL,t2,NULL);pthread_join(a,NULL);pthread_join(b,NULL);return0;}gcc-O0-gdeadlock_abba.c-lpthread-odeadlock_abba ./deadlock_abba&pstack$!内核态复现(测试环境)
- 准备测试模块,构造两条路径反序加锁
- 开启 Lockdep
- 触发路径并分析 lockdep 报告
简化伪代码:
spin_lock(&A);/* path 1 */spin_lock(&B);spin_lock(&B);/* path 2 */spin_lock(&A);仅建议在测试内核与隔离环境操作。
生产环境补充:kdump 与 crash 离线分析
当系统几乎不可交互时,可用 kdump 捕获 vmcore,再用crash离线分析:
| 步骤 | 目的 |
|---|---|
| 启用 kdump | 故障时保留内核现场 |
| 获取 vmcore | 保留死锁现场上下文 |
crash分析任务与栈 | 还原等待链与锁持有关系 |
这是“最后一道证据链”。
crash常用命令速查:
ps -u # 看任务状态(含不可中断任务) bt <pid> # 指定任务栈回溯 foreach bt # 批量看栈(慎用,输出大) log # 查看内核日志缓冲实践提醒:线上机器需提前预留crashkernel=内存,否则故障时可能抓不到 vmcore。
预防优先:锁层级与编码规范
建议把以下规则写入团队规范:
- 全局锁顺序表(Lock Hierarchy):严格按层级拿锁
- 短临界区原则:减少持锁时长
- 可中断/超时策略:对非关键路径使用 try/timed lock
- 代码评审必查项:是否出现 ABBA、是否跨上下文复用锁
- 持续集成检查:调试内核定期跑 lockdep 覆盖
锁层级示例(示意):
L1: global_lock L2: inode_lock L3: dentry_lock 规则:只能从 L1 -> L2 -> L3 方向获取,禁止逆序。try-lock 回退示例(用户态):
if(pthread_mutex_trylock(&m2)!=0){pthread_mutex_unlock(&m1);/* 退避重试或走降级路径 */}案例复盘模板
| 维度 | 记录内容 |
|---|---|
| 现象 | 负载、告警、业务影响范围 |
| 证据 | 日志、栈、ftrace、vmcore |
| 根因 | 锁顺序、上下文冲突、资源路径 |
| 修复 | 锁顺序统一、超时回退、流程重构 |
| 预防 | 规范、测试、监控项与阈值 |
一页纸排障速查图(值班版)
值班执行口诀(文字版):
- 先定性:
R / D / Z谁异常最多。 - 再取证:栈、等待点、锁持有关系必须成链。
- 后修复:统一锁顺序,补超时/回退与监控。
- 最后固化:把本次根因写入锁层级规范与测试用例。
附录A:常见死锁/卡死日志速查
| 报错片段(示意) | 典型来源 | 含义 |
|---|---|---|
possible circular locking dependency detected | Lockdep | 潜在锁顺序环 |
task ... blocked for more than 120 seconds | hung task detector | 任务长期阻塞 |
BUG: soft lockup | watchdog | CPU 长时间未调度 |
hard LOCKUP | NMI watchdog | 更严重的 CPU 卡死迹象 |
附录B:用户态 vs 内核态死锁差异
| 维度 | 用户态死锁 | 内核态死锁 |
|---|---|---|
| 常用工具 | gdb/pstack | lockdep / ftrace / crash |
| 可恢复性 | 往往可重启进程恢复 | 可能影响整机稳定 |
| 证据采集 | 线程栈 + 日志 | 内核栈 + trace + vmcore |
| 处理优先级 | 服务级 | 系统级(更高) |
常见误区
| 误区 | 修正 |
|---|---|
| 只靠日志就能定位死锁 | 死锁定位通常必须结合栈与时序 |
kill -9能解所有卡死 | 内核态等待与死锁常不能直接“杀掉”解决 |
| Lockdep 线上常开更安全 | Lockdep 有开销,通常开发/测试内核使用 |
| 只修一次锁顺序就完事 | 需建立长期锁层级规范与回归测试 |
免责声明
- 工具与内核配置项会随发行版和内核版本变化,具体以现场环境与官方文档为准。
- 文中流程用于工程排障框架,不替代你所在系统的安全与运维规范。
