sched discussion
[PATCH] sched: Allow sleeping spinlocks on PREEMPT_RT within non_block_start()/end block.
LLM 分析
sched/PREEMPT_RT:允许 rtlock 在 non_block 区段内合法调度
系列概况
- 标题:
[PATCH] sched: Allow sleeping spinlocks on PREEMPT_RT within non_block_start()/end block. - 作者: Sebastian Andrzej Siewior bigeasy@linutronix.de
- 版本: 单封 v1 patch,无 vN 编号
- 规模: 4 files changed, 20 insertions(+), 18 deletions(-)
- 修改文件:
include/linux/kernel.hinclude/linux/sched.hkernel/locking/spinlock_rt.ckernel/sched/core.c
- 代码统计: 上面给出的 +/- 行数
- Message-ID:
20260821095755.am1-Segb@linutronix.de - 完整性: 完整,共 1 封 patch + 2 封回复。Michal Hocko 仅引用 patch,没有新增评论;David Woodhouse 给出 Acked-by 并补上
Fixes/Reported-by/相关链接。
补丁目的
修 PREEMPT_RT 上 non_block_start()/end() 区间内使用 spinlock_t / rwlock_t 时触发的 might_sleep() 警告(splat)。
commit 312364f3534c 引入 non_block区间时,假定 spinlock 不会有间接 page allocator 依赖,因此把 spinlock 排除在「禁止调度」检查外。但 PREEMPT_RT 把 spinlock_t、rwlock_t 实现为基于 rtmutex 的「可睡眠锁」,锁竞争时会 schedule(),于是 non_block_count > 0 期间被 __might_resched() 拦下并 WARN。
旧流程的问题
__might_resched() 的核心判定只看 !current->non_block_count:
!is_idle_task(current) && !current->non_block_count
这条规则对 rtlock_might_resched() 也一视同仁地禁止。但 rtlock 设计上就是「可以睡」的锁,它在 contended 时必须 schedule,否则就违背了 PREEMPT_RT 的语义。
新流程
给 __might_resched() 增加 bool rt_sleeping_lock 形参。当且仅当调用方明确声明「这是 sleeping lock 的合法调度点」时,绕过 non_block_count 限制。
!is_idle_task(current) && (rt_sleeping_lock || !current->non_block_count)
true 仅由 PREEMPT_RT 路径 rtlock_might_resched() 传入;其他 cond_resched* 宏继续传 false,语义不变。
Patch 概览
四处改动:
kernel/sched/core.c—__might_resched()函数签名增加rt_sleeping_lock,判定条件放宽为 OR。include/linux/sched.h—cond_resched()/cond_resched_lock()/cond_resched_rwlock_read()/cond_resched_rwlock_write()四处宏在调用__might_resched()时统一传false。kernel/locking/spinlock_rt.c—rtlock_might_resched()处把true传入。include/linux/kernel.h— 函数声明同步更新rt_sleeping_lock形参;空实现(!CONFIG_PREEMPTION)也补齐形参。
关键实现
核心改动在 kernel/sched/core.c::__might_resched():
void __might_resched(const char *file, int line,
unsigned int offsets,
bool rt_sleeping_lock)
{
...
if ((resched_offsets_ok(offsets) &&
!irqs_disabled() &&
system_state == SYSTEM_BOOTING || ...) {
...
}
...
!is_idle_task(current) && (rt_sleeping_lock || !current->non_block_count)) ||
...
}
调用侧分工:
/* 非 RT 路径:cond_resched() / cond_resched_lock() / cond_resched_rwlock_*() */
__might_resched(__FILE__, __LINE__, PREEMPT_LOCK_RESCHED_OFFSETS, false);
__might_resched(__FILE__, __LINE__, 0, false);
/* PREEMPT_RT 路径:rtlock_might_resched() */
__might_resched(__FILE__, __LINE__, RTLOCK_RESCHED_OFFSETS, true);
类比
non_block_start()/end() 像仓库门口挂一块「禁打盹」牌子,所有 cond_resched 调度点进门都必须醒着。PREEMPT_RT 的 rtlock 就像「按规定可以小憩的搬运工」,他偶尔停下来眯 5 分钟并不违规——但门口的检查员只认「禁止打盹」一条规则,搬运工一闭眼就被吹哨警告。补丁相当于给检查员加一条新特例:识别搬运工身份(rt_sleeping_lock=true),允许他在规定位置眯眼;其他外来人员(false)依然要被拦下。
ASCII 流程图
cond_resched() / cond_resched_lock()
cond_resched_rwlock_read() / _write()
|
v
__might_resched(..., rt_sleeping_lock = false)
|
v
!is_idle_task() && !non_block_count ?
| |
YES NO -> WARN (might_sleep splat)
|
v
OK to schedule
rtlock_might_resched() (PREEMPT_RT only)
|
v
__might_resched(..., rt_sleeping_lock = true)
|
v
!is_idle_task() && (true || ...) ?
|
YES -> OK to schedule
non_block_start()
|
+-- cond_resched_lock(spin) -- rt_sleeping_lock=false
| ->旧: WARN;新: 仍 WARN
|
+-- rtlock_might_resched() -- rt_sleeping_lock=true
| -> 旧: WARN;新: 放行
|
non_block_end()
Highlight:风险与注意点
- 全量更新调用点:
__might_resched()是内核内部 ABI,所有非 RT 调用点必须同步把新形参补上false,否则声明 vs 调用不一致会编译失败或警告。patch 已覆盖sched.h四处宏和__might_sleep(),但其他子树(驱动、arch)若直接调用__might_resched()也需跟随,否则 PREEMPT_RT 构建会破坏。 - 未来扩展:
synchronize_srcu()暂未纳入。David Woodhouse 提到希望也允许,但 SRCU 的MIGHT_RESCHED_RCU_SHIFT嵌套检查更复杂,需要单独讨论。 - stable 回移植:打了
Fixes: 312364f3534c标签,会进 stable 队列,PREEMPT_RT 用户需关注下游 backport。 - non_block_count 语义边界:补丁只把特例严格限制在
rtlock_might_resched();未来若有新的「可睡眠但非 spinlock」原语(比如某类 completion),需要再次评估是否也允许走true路径,否则会重新触发类似 splat。 - mm / pwm 现状:当前
non_block_start()的真实用户只有 mm 和 pwm。pwm 路径上的 hrtimer 在 RT 上取spinlock_t是该补丁的主要触发场景;mm 路径同样受益。
版本变化
单封 v1 patch,无 vN→vN+1 演进。
一句话总结
PREEMPT_RT 下 spinlock 实质是 rtlock 可睡眠锁,原 non_block 区间一刀切禁止调度会触发 might_sleep 警告;补丁通过给 __might_resched() 加 rt_sleeping_lock 形参,仅为 rtlock_might_resched() 路径打开特例放行。