0/5 已展开

LLM 分析

sched_ext: Sparse annotation cleanups

系列基线信息

字段内容
标题[PATCHSET sched_ext/for-7.3] sched_ext: Sparse annotation cleanups
作者Tejun Heo
版本/规模3 patches,5 files,77 insertions / 32 deletions
基线分支sched_ext/for-7.3 (94ca9591108a)
Message-ID20260724012914.107823-1-tj@kernel.org
来源sched-ext 频道
状态已被 maintainer 合入 for-7.3

明确目的

Sparse 是 Linux 内核的静态分析工具,会对 __rcu 标注的指针做合规检查——凡是 __rcu 指针的读取,必须通过 rcu_dereference() 系列宏来声明保护方式;直接用普通 load 读取会被 Sparse 报警告。

本 patchset 对 kernel/sched/ext 下的三类裸 __rcu 读取做标注清理:

  1. cgrp->scx_sched:cgroup 与 scx_sched 的关联字段,写入在多锁保护之下,读取也应声明对应锁。
  2. scx_root:根调度器的全局指针,裸读取是多调度器过渡期遗留的临时标记,现在逐步收拢。
  3. dsq->first_task:dispatch queue 首任务指针的身份比较,应使用 rcu_access_pointer()

整个系列不改变任何运行时行为,纯注释/标注层面的改动。


遍历代码

Patch 1/3 — scx_cgroup_sched() 访问器

新增内联函数 scx_cgroup_sched(),封装 cgrp->scx_sched 的读取:

static inline struct scx_sched *scx_cgroup_sched(struct cgroup *cgrp)
{
    return rcu_dereference_check(cgrp->scx_sched,
        lockdep_is_held(&cgroup_mutex) ||
        percpu_rwsem_is_held(&scx_fork_rwsem) ||
        lockdep_is_held(&scx_enable_mutex));
}

保护逻辑:写入路径在 scx_enable_mutex + scx_fork_rwsem + cgroup_mutex 三把锁全持的情况下修改;新 cgroup 继承父 sched 只持 cgroup_mutex(此时新 cgroup 尚不可达其他两锁持有者)。因此持有任意一把锁即可稳定读取。

ext.csub.c 中 9 处 cgrp->scx_sched 直接读取全部替换为 scx_cgroup_sched()

Patch 2/3 — scx_root 访问器与层级导航

新增两个访问器:

  • scx_root_protected_live():调度器在线(live)期间 scx_root 不会变化,因此用 rcu_dereference_protected(scx_root, true) 做普通 load,声明"上下文已保证安全"。适用于 dispatch 入口、class switch、idle 通知、fork init 等只在线窗口内执行的路径。
  • scx_root_protected():需要排他写入,用 rcu_dereference_protected(scx_root, lockdep_is_cpus_held() || lockdep_is_held(&scx_enable_mutex))。适用于 hotplug 路径。

此外,pos == scx_root 的"是否根"判断改为 !pos->level(level 为 0 即根),避免依赖全局指针做身份比对。sch->ancestors[0] 提供了一条通过当前 sched 稳定抵达根的路径。

保留一处裸读取touch_core_sched_dispatch() 暂不转换,后续单独处理。

Patch 3/3 — dsq->first_task 身份比较

task_unlink_from_dsq() 在 dsq lock 保护下比较 dsq->first_task == p,判断要离开的任务是否正是队头。dsq->first_task__rcu(给 lockless 的 scx_bpf_dsq_peek() 使用),但此处只需身份判断、不解引用,因此用 rcu_access_pointer() 替代普通 load——它只读指针值不做 RCU 宽限期依赖,但向 Sparse 声明了合规。


ASCII 流程图

+-------------------+     +-------------------+     +-------------------+
| cgrp->scx_sched   |     | scx_root          |     | dsq->first_task   |
| (__rcu pointer)   |     | (__rcu pointer)   |     | (__rcu pointer)   |
+-------------------+     +-------------------+     +-------------------+
        |                         |                         |
   READ before:              READ before:              READ before:
   plain load                plain load                plain load
        |                         |                         |
   READ after:               READ after:               READ after:
   scx_cgroup_sched()       scx_root_protected_live() rcu_access_pointer()
        |                    scx_root_protected()           |
        v                    ancestors[0] / !level          v
   rcu_dereference_check     rcu_dereference_protected   read pointer only
   (any 1 of 3 locks)        (live invariant | locks)    (no deref, dsq lock)
         Lock protection hierarchy for cgrp->scx_sched writes:

         scx_enable_mutex ─┐
         scx_fork_rwsem   ─┤── all 3 held together for writes
         cgroup_mutex     ─┘

         Any single lock ───── stabilizes the read (scx_cgroup_sched)

概念类比

__rcu 标注想象成银行保险柜上的"双人签字"标签:保险柜(__rcu 指针)规定取东西必须走正式流程(rcu_dereference 系列宏),但有些柜员手里已经拿了钥匙(持有锁或处于不可变窗口),他们确实可以直接开柜,只是以前没在记录本上签字——Sparse 相当于审计员发现"签字栏空着"就报警。本 patchset 不是给柜员新钥匙,而是让他们在签字栏填上正确理由:"我持了 cgroup_mutex"、"调度器在线期间值不变"、"我只看了一眼标签没取内容"。


Highlight 突出问题

  1. touch_core_sched_dispatch() 仍是裸读取:本系列刻意留了一处未转换,需要后续 patch 单独处理;读者不应以为所有 scx_root 裸访问已清除。
  2. rcu_dereference_protected(scx_root, true)true 条件scx_root_protected_live()true 意味"我相信当前上下文不可能并发更新"——这依赖于调度器 live 窗口的语义不变性,而不是锁。如果未来有人在此路径中引入了非 live 上下文调用,Sparse 不会报警但会出现真实 race。
  3. !pos->level 替代 pos == scx_root:从全局指针身份比较改为层级属性判断,语义等价但更局部化;需确认 level == 0 在所有子调度器场景下严格对应根。
  4. Sparse 输出仍有三类残留噪声:BPF kfunc 声明噪声(全局共享)、rq->curr/rq->donor(核心调度器共享)、touch_core_sched_dispatch()(待后续),本 patchset 只解决了 sched_ext 专有部分。

版本演进

本系列为首次发布,无前序版本。Maintainer 已直接合入 for-7.3。


与其他相关 patch 系列的关联

  • 这是 sched_ext 多调度器(sub-scheduler)重构过渡期的标注收尾工作。裸 scx_root 访问最初是过渡标记,随着多数路径已改为"从当前 sched 推导根",现在只剩下语义上确实需要全局根的路径需要正式标注。
  • touch_core_sched_dispatch() 的裸访问将由后续独立 patch 解决。

一句话总结

为 sched_ext 三类 __rcu 指针的裸读取添加合规访问器(scx_cgroup_sched()scx_root_protected_live/protected()rcu_access_pointer()),消除 Sparse 警告,无功能变更。