本页目录

同步调试

覆盖: lockdep (锁依赖图) → lockstat (竞争统计) → KCSAN (数据竞争检测) → hung task detector → NMI watchdog → CONFIG_DEBUG_ATOMIC_SLEEP 内核版本: 2.6 ~ 6.x

概述

并发 bug 是内核中最难调试的问题之一:死锁、数据竞争、原子上下文睡眠。这些 bug 往往非确定性——只在特定时序下触发。幸运的是,内核内置了一套强大的同步调试基础设施,能在 bug 发生时(甚至发生前)捕获它。


Lockdep: 锁依赖验证器

// kernel/locking/lockdep.c
// 核心思想: 构建运行时锁依赖图,检查环路 (死锁)
//   每次 lock: 在图里加一条 A→B 的边
//   如果 B→A 的边已存在 → 发出死锁警告!
//
//   不需要真的发生死锁! Lockdep 能"预测"潜在死锁

// Lockdep 跟踪的"锁类":
//   - 同一类型的锁 (如所有 inode→i_lock) → 同一个 class
//   - 不同函数中拿的锁 → 不同 class (即使类型相同)

// /proc/lockdep: 完整的锁依赖图
// /proc/lockdep_stats: 依赖图统计

Lockdep 报告解读

典型的 lockdep 输出:
  ======================================================
  WARNING: possible circular locking dependency detected
  ======================================================
  CPU 0: lock(A); ... lock(B);
  CPU 1: lock(B); ... lock(A);
  → 潜在死锁! 即使它还没在实际执行中发生

// 还包括:
//   - 每个锁是在哪个函数、哪个文件中被拿的
//   - 从 boot 以来每个锁的持有历史

Lockstat: 竞争统计

// CONFIG_LOCK_STAT
// 记录每个锁的:
//   - wait time: 等锁花了多久
//   - hold time: 持锁持了多久 (需要 CONFIG_LOCK_STAT_HOLDLOCK)

// /proc/lock_stat:
//   格式: class_name    contended   total_wait_ns   max_wait_ns   min_wait_ns

KCSAN: Kernel Concurrency Sanitizer

// kernel/kcsan/
// 类似用户态的 ThreadSanitizer (TSan)
// 运行时检测数据竞争: 两个 CPU 同时访问同一内存地址,至少一个是写

// 工作原理:
//   1. 对每次内存访问: 记录地址 + 大小 + 时间戳 (watchpoint)
//   2. 如果另一个 CPU 在未同步的情况下访问同一地址 → 报告

// 报告解读:
//   BUG: KCSAN: data-race in function_name (read/write of 4 bytes at addr)
//     CPU 0: write to x in func_a()
//     CPU 1: read from x in func_b()
//     → x 的访问没有共同锁保护 → 数据竞争!

// 编译选项: CONFIG_KCSAN=y
//   带 KCSAN 的内核显著变慢 (~10x),仅用于调试

Hung Task Detector

检测: 进程在 TASK_UNINTERRUPTIBLE (D) 状态超过 120 秒
  → 通常是死锁、NFS 不可达、或驱动 bug

配置:
  /proc/sys/kernel/hung_task_timeout_secs  (默认 120)
  /proc/sys/kernel/hung_task_panic         (0=warn, 1=panic)

输出: 打印卡住进程的内核栈 (关键!)
  → 看到在 mutex_lock() 等什么 → 找哪个进程持有该锁

NMI Watchdog

基于 NMI (不可屏蔽中断) 的死锁检测:
  每个 CPU 周期性地收 NMI → 重置 per-CPU 计数器
  如果某个 CPU 长时间没收到 NMI:
    → 可能因为关中断太久 (spin_lock_irqsave 死循环)
    → 打印卡住 CPU 的栈回溯

工作原理:
  perf event 驱动 → 溢出 → NMI → watchdog overflow handler
  → 检查 hrtimer 自上次中断以来的时间 → 如果超时 → 报警

其他调试工具

// CONFIG_DEBUG_ATOMIC_SLEEP:
//   在原子上下文中调用 schedule() (或任何可能睡眠的函数)
//   → 立即 BUG()

// CONFIG_PROVE_LOCKING (lockdep 的增强):
//   每个锁操作都经过 lockdep 检查 → 更大的运行时开销
//   但能捕获更多不正确的锁使用

// CONFIG_DEBUG_SPINLOCK:
//   检查 spinlock 未初始化使用、double unlock 等

// CONFIG_PROVE_RCU:
//   检查 RCU reader 在临界区外调用 rcu_dereference、睡眠等

调试命令速查

# lockdep: 依赖图 + 统计
cat /proc/lockdep        # 完整的锁依赖图 (文本, 很大)
cat /proc/lockdep_stats  # 统计摘要

# lockstat: 竞争
cat /proc/lock_stat | sort -k2 -n -r | head -20

# hung task: 当前卡住的进程
echo t > /proc/sysrq-trigger   # 打印所有进程的内核栈 (sysrq)
cat /proc/<pid>/stack           # 单进程内核栈 (5.14+)

# 数据竞争 (KCSAN, 需要编译时启用)
dmesg | grep KCSAN

# 关中断检查
dmesg | grep -i "sleeping function called from invalid context"

参考

  • 内核文档⁠: Documentation/locking/lockdep-design.rst, Documentation/dev-tools/kcsan.rst
  • 源码⁠: kernel/locking/lockdep.c, kernel/kcsan/, kernel/watchdog.c, kernel/hung_task.c
  • LWN: "Lockdep", "The Kernel Concurrency Sanitizer"

关键词: lockdep, lockstat, KCSAN, data race, hung task, NMI watchdog, DEBUG_ATOMIC_SLEEP, PROVE_LOCKING