0/3 已展开

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.h
    • include/linux/sched.h
    • kernel/locking/spinlock_rt.c
    • kernel/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_trwlock_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 概览

四处改动:

  1. kernel/sched/core.c__might_resched() 函数签名增加 rt_sleeping_lock,判定条件放宽为 OR。
  2. include/linux/sched.hcond_resched() / cond_resched_lock() / cond_resched_rwlock_read() / cond_resched_rwlock_write() 四处宏在调用 __might_resched() 时统一传 false
  3. kernel/locking/spinlock_rt.crtlock_might_resched() 处把 true 传入。
  4. 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:风险与注意点

  1. 全量更新调用点__might_resched() 是内核内部 ABI,所有非 RT 调用点必须同步把新形参补上 false,否则声明 vs 调用不一致会编译失败或警告。patch 已覆盖 sched.h 四处宏和 __might_sleep(),但其他子树(驱动、arch)若直接调用 __might_resched() 也需跟随,否则 PREEMPT_RT 构建会破坏。
  2. 未来扩展synchronize_srcu() 暂未纳入。David Woodhouse 提到希望也允许,但 SRCU 的 MIGHT_RESCHED_RCU_SHIFT 嵌套检查更复杂,需要单独讨论。
  3. stable 回移植:打了 Fixes: 312364f3534c 标签,会进 stable 队列,PREEMPT_RT 用户需关注下游 backport。
  4. non_block_count 语义边界:补丁只把特例严格限制在 rtlock_might_resched();未来若有新的「可睡眠但非 spinlock」原语(比如某类 completion),需要再次评估是否也允许走 true 路径,否则会重新触发类似 splat。
  5. 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() 路径打开特例放行。