sched-ext discussion
[PATCHSET sched_ext/for-7.3] sched_ext: Sparse annotation cleanups
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-ID | 20260724012914.107823-1-tj@kernel.org |
| 来源 | sched-ext 频道 |
| 状态 | 已被 maintainer 合入 for-7.3 |
明确目的
Sparse 是 Linux 内核的静态分析工具,会对 __rcu 标注的指针做合规检查——凡是 __rcu 指针的读取,必须通过 rcu_dereference() 系列宏来声明保护方式;直接用普通 load 读取会被 Sparse 报警告。
本 patchset 对 kernel/sched/ext 下的三类裸 __rcu 读取做标注清理:
cgrp->scx_sched:cgroup 与 scx_sched 的关联字段,写入在多锁保护之下,读取也应声明对应锁。scx_root:根调度器的全局指针,裸读取是多调度器过渡期遗留的临时标记,现在逐步收拢。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.c 和 sub.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 突出问题
touch_core_sched_dispatch()仍是裸读取:本系列刻意留了一处未转换,需要后续 patch 单独处理;读者不应以为所有scx_root裸访问已清除。rcu_dereference_protected(scx_root, true)的true条件:scx_root_protected_live()传true意味"我相信当前上下文不可能并发更新"——这依赖于调度器 live 窗口的语义不变性,而不是锁。如果未来有人在此路径中引入了非 live 上下文调用,Sparse 不会报警但会出现真实 race。!pos->level替代pos == scx_root:从全局指针身份比较改为层级属性判断,语义等价但更局部化;需确认level == 0在所有子调度器场景下严格对应根。- 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 警告,无功能变更。