遇到 scheduling while atomic 或 double unlock 错误,说明在原子上下文中调用了可能睡眠的函数或重复释放锁;需结合 panic 日志分析上下文、调用栈及锁类型匹配性,并启用 CONFIG_DEBUG_ATOMIC_SLEEP、CONFIG_DEBUG_SPINLOCK 等选项辅助定位。

遇到 BUG: scheduling while atomic 或 double unlock 这类内核错误,通常说明驱动或内核模块在原子上下文(atomic context)中做了不该做的事,比如调用了可能睡眠的函数,或者重复释放了同一把锁。这类问题隐蔽性强、复现不稳定,但只要抓住关键线索,就能快速定位。
看清楚错误发生的位置和上下文
内核 panic 日志里会明确指出出错的函数、调用栈(call trace)、CPU、抢占/中断状态等信息。重点关注:
-
出错时是否处于中断上下文(in_interrupt() == true)或软中断(in_softirq()) —— 这决定了能否调用
msleep、wait_event、mutex_lock等可能调度的函数; -
调用栈中是否有明显“睡眠型”函数,如
wait_event_timeout、kmalloc(GFP_KERNEL)、copy_to_user、flush_work等; - 是否在 spinlock 持有期间调用了可能阻塞的函数 —— 即使没直接 sleep,某些函数内部也可能触发调度(例如某些设备驱动中的 wait_event 变体)。
检查锁的使用是否匹配上下文
Linux 提供多种锁机制,选错类型是常见根源:
- spinlock / rwlock:只适用于短临界区,且必须在原子上下文中使用(不能 sleep),释放必须与获取严格配对;
- mutex / semaphore:允许睡眠,只能在进程上下文使用,不能在中断、softirq 或持有 spinlock 时调用;
-
rcu_read_lock():不可阻塞,但也不能嵌套写侧操作,且不能在 spinlock 保护区内调用
synchronize_rcu; - 特别注意:
mutex_unlock在中断上下文中调用会静默失败,而spin_unlock被重复调用则直接触发double unlockpanic。
警惕隐式睡眠和跨上下文调用
有些函数看似“安全”,实则暗藏调度点:
-
kmalloc(GFP_KERNEL)在内存紧张时可能等待页回收,绝对禁止在中断/softirq 中使用,应改用GFP_ATOMIC; -
printk()一般安全,但若启用了console_lock且日志量大,也可能间接导致调度(尤其在早期内核); - 自定义 workqueue 函数(如
schedule_work的 handler)运行在进程上下文,但如果它被误从 atomic 上下文直接调用(而非 schedule),就会出问题; - 驱动中常见的陷阱:在
->probe()或->remove()中调用wait_event是允许的,但在->irq_handler()或NAPI poll中调用就是致命错误。
用工具辅助验证和复现
静态检查 + 动态检测能大幅缩短排查周期:
-
打开内核配置:
CONFIG_DEBUG_ATOMIC_SLEEP=y(捕获 scheduling while atomic)、CONFIG_DEBUG_SPINLOCK=y和CONFIG_DEBUG_MUTEXES=y(捕获 double unlock / unlock without lock); -
启用 lockdep:
CONFIG_LOCKDEP=y,配合lockdep_assert_held()在关键路径加断言; -
使用
dump_stack()插桩:在可疑函数入口/出口打印栈,确认调用来源是否符合预期上下文; - 在模拟环境中注入延迟或强制抢占(如用
cond_resched()触发调度点),可让原本偶发的问题稳定复现。










