0/5 已展开

LLM 分析

Linux Scheduler / MM:OOM reaper 在持有 mmap_lock 时命中 sleeping-in-invalid-context

系列概况

  • 标题[syzbot] [mm?] BUG: sleeping function called from invalid context in kvm_mmu_notifier_invalidate_range_start
  • 作者:syzbot(首报与转报)、Sean Christopherson(KVM 维护者)、David Woodhouse(dwmw2,邮件归档/转发者)
  • 版本:本线程没有正式的 patch series 版本号,但配套的代码改动在 kernel.h / sched.h 里给 __might_resched() 增加了一个 bool rt_sleeping_lock 参数
  • 规模:5 封邮件,1 份未签名的 diff 片段(围绕 __might_resched 签名扩展);非典型 patch series
  • 修改文件:从邮件 4 的 diff 片段可见,主要是 include/linux/kernel.hinclude/linux/sched.h,并影响所有 cond_resched*()
  • 代码统计:diff 中可见约 6 处函数/宏签名变更,每处只增减一两行,纯签名扩展,无新增逻辑分支
  • Message-ID:首封 6936812a.a70a0220.38f243.0090.GAE@google.com;最后一封 6a8d8cc9.a5a502ce.31d34.0067.GAE@google.com
  • 完整性:缺正式 patch cover letter、缺 Signed-off-by、缺 Fixes tag;最后一封只是 #syz test: seb-nonblock-rt-test 的测试请求,不能算完整闭环

补丁目的

目的是修复 OOM reaper 在持有 mmap_lock(reader 锁,标 ++++-{4:4})时调用 cond_resched() / __might_resched() 被锁检测标记为"非法上下文休眠"的问题。dump 里的栈关键路径是:

oom_reaper/39
  -> mmap_read_trylock
  -> oom_reap_task_mm (mm/oom_kill.c)
  -> ... -> cond_resched()
  -> __might_resched() triggers BUG at spinlock_rt.c:48

症结不在 mmap_lock 本身,而在 oom_reaper 路径上同时使用 RT-mutex 化的 spinlock(rt_spin_lock),所以 sched/RT 锁检测把这次 might_sleep 当成了非法的"在原子上下文里 sleep"。补丁想给 __might_resched() 引入一个 rt_sleeping_lock 形参,让调用方明确告诉锁检测"这次睡眠发生在 RT sleeping lock 上,不是普通的 sleeping-in-atomic"。

旧流程的问题

  • 在 PREEMPT_RT 配置下,mmap_lock 之类的 reader lock 被 rtmutex 化,进入临界区后依然可能触发 cond_resched()
  • 旧的 __might_resched() 只看 preempt_count 与 RCU 嵌套深度,无法区分"持普通 spinlock"和"持 RT sleeping lock",一看到 preempt_count=0 就直接 BUG。
  • 这让 oom_reaper 在 RT 配置下不可用:它必须 cond_resched() 让出 CPU,但又会被锁检测误判。

新流程

__might_resched() 增加一个 bool rt_sleeping_lock 形参:

extern void __might_resched(const char *file, int line,
                            unsigned int offsets,
                            bool rt_sleeping_lock);

所有 cond_resched*() 宏调用点都传入 false

#define cond_resched() ({ \
    __might_resched(__FILE__, __LINE__, 0, false); \
    _cond_resched(); \
})

未来由 KVM / MM 路径在持 RT sleeping lock 的地方传入 true,让锁检测放过这一次 might_sleep,从而允许 oom_reap_task_mm() 在持有 reader mmap_lock 时正常触发调度。

Patch 概览

邮件 4 给出的核心 diff 形态:

include/linux/kernel.h
  __might_resched()              new 4th parameter rt_sleeping_lock
  __might_resched_no_domains()   same new parameter

include/linux/sched.h
  cond_resched()                 pass false
  cond_resched_lock(lock)        pass false
  cond_resched_rwlock_read()     pass false
  cond_resched_rwlock_write()    pass false
  ... other cond_resched*() macros updated likewise

这个 patch 是签名扩展 + 默认 false 的"准备阶段",真正的修要靠后续由 KVM 路径在合适调用点传入 true 完成。

关键实现

调用链解读:

cond_resched_lock(lock)  -- reader mmap_lock path -->
   __might_resched(..., false)
   --> lockdep / RT sleeping_lock detection
       rt=false -> behavior equals the old implementation
       rt=true  -> skip the invalid-context warning
   _cond_resched()         -- real yielding point

OOM reaper 在 RT 下需要进入 oom_reap_task_mm() 时既可能 spin 也可能 schedule,所以必须让 cond_resched_lock(lock)rt_sleeping_lock=true 的语义下被允许。

整体对象关系:

+----------------------+         +----------------------------+
|   cond_resched*()    |  call   |     __might_resched()      |
|   macros in sched.h  |-------->|  + new param:              |
|  pass rt=false       |         |    bool rt_sleeping_lock   |
+----------------------+         +----------------------------+
                                        |
                                        | false
                                        v
                                 +-------------+
                                 | warn / BUG  |  (legacy path)
                                 +-------------+
                                        ^
                                        | true
                                 +-------------+
                                 |  skip BUG   |  (RT sleeping lock path)
                                 +-------------+

类比

想象银行柜台:reader lock 相当于"取号等待",多个客户可以同时在大厅里坐着等叫号;普通 spinlock 相当于"柜台员工锁住工位不让换人",员工绝不能跑去洗手间。而 oom_reaper 是银行大堂经理,他要处理"长时间挂号的客户"必须让自己也能去趟洗手间。旧锁检测只要看到"员工还在工位上"就报警,根本不问这次是不是"大堂经理去洗手间"。新参数相当于给大堂经理配了一张特殊工牌,锁检测认到这张工牌就知道"这次睡眠是合理的"。

再换一个比喻:红绿灯原本只判断"有没有车在路口",现在新增一个"是不是应急车辆"的标志,应急车辆闯灯会被放行。rt_sleeping_lock=true 就是那张应急标志。

Highlight:风险与注意点

  • 签名 ABI 风险__might_resched() 是内核里被各模块广泛调用的内部 API,扩展参数会影响所有调用点。邮件 4 的 diff 已经把已知宏都同步成 false,但 out-of-tree 模块会编译失败。
  • 过度放行的可能:一旦 rt_sleeping_lock=true 的调用面扩大,要确保传入 true 的位置真的只发生在 RT sleeping lock 临界区,否则又把原本应当告警的真实 bug 吞掉。
  • oom_reaper 死锁风险:oom_reaper 在持 mmap_lock 时让出调度,若持锁时间过长,可能拖慢其它需要 mmap_lock writer 的路径;补丁只解决"被误报 BUG",不解决"持锁太久"。
  • 复现路径需要持续跟踪:邮件 2 给出 repro.syz,dwmw2 在邮件 5 发起 #syz test: seb-nonblock-rt-test,说明问题需要在 RT 内核 + 非阻塞 mmap 路径上验证;后续是否合入 mainline 取决于该测试结果。

版本变化

本线程本身没有 patch series vN→vN+1 的演进,可观察到的版本变化是:

  • 首报(2025-12-08):仅发现 BUG,无 reproducer,HEAD 37bb2e7217b0(6.19 合并窗口)。
  • 邮件 2(2026-05-04):syzbot 在 linux-next(b9303e6bff70,2026-04-30 snapshot)上找到 reproducer。
  • 邮件 3(2026-05-06):Sean Christopherson 回复(KVM 角度介入)。
  • 邮件 4(2026-08-21):转发了一份扩展 __might_resched 签名的 diff,是修复的前置准备。
  • 邮件 5(2026-08-25):dwmw2 转发 #syz test: seb-nonblock-rt-test 请求,测试补丁后的行为。

一句话总结

OOM reaper 在 RT 配置下因为持有 rtmutex 化的 reader lock 又调用 cond_resched(),被 __might_resched() 误判为在非法上下文睡眠;补丁给 __might_resched() 增加 rt_sleeping_lock 形参,让合法路径显式声明自己处于 RT sleeping lock 上,从而避免误报并保持 oom_reaper 的让出调度能力。