sched-ext discussion
[PATCHSET sched_ext/for-7.3] sched_ext: Misc fixes
LLM 分析
sched_ext:scx_root_disable 删除 unlocked 时钟失效 & scx_bpf_events 改为读取调用者计数器
系列概况
- 标题:
[PATCHSET sched_ext/for-7.3] sched_ext: Misc fixes - 作者: Tejun Heo tj@kernel.org
- 版本: v1 → v2(仅 patch 1 重发,patch 2 未变)
- 规模: 2 patch,diffstat 共
kernel/sched/ext/ext.c | 31 ++++++++++++++++++++++++++++++--,17 insertions(+)、14 deletions(-) - 修改文件:
kernel/sched/ext/ext.c - 代码统计: patch 1 = 6+/7-(v1)→ 3+/8-(v2);patch 2 = 11+/7-
- Message-ID 根:
20260815055107.115671-1-tj@kernel.org - 完整性: 完整,包含 patch 1、patch 2、sashiko bot review、作者回应、v2 重发和最终 Applied 通知
补丁目的
这个 patchset 把 for-7.3 分支里的两个小问题一并修掉:
scx_root_disable()在拿rq锁之前调用了一次无锁的scx_rq_clock_invalidate(),这条路径不仅冗余还可能在rq->scx.flags上裸做 read-modify-write,与持锁的更新者产生 race。直接删掉这个调用、并在注释里把时钟失效的真正时机说清楚。scx_bpf_events()原本固定读取 root scheduler 的事件计数器;sub-scheduler 程序调用时拿到的也是 root 的值。改成通过scx_prog_sched(aux)解析当前 BPF 程序所属的 scheduler,并顺手把格式错误的注释修成完整 kerneldoc。
旧流程的问题
Patch 1 的旧流程
scx_root_disable() 在循环里对每个 rq 都执行:
scx_rq_clock_invalidate(rq); /* 未持锁,裸 RMW */
scoped_guard(rq_lock_irqsave, rq) {
update_rq_clock(rq);
...
}
问题点:
scx_rq_clock_invalidate()只是对rq->scx.flags做一次 read-modify-write,但这条 word 上其他 writer 全都持有 rq 锁;无锁 RMW 可能和它们竞争,丢失一边写入的位。- 时钟失效其实多余:
dl_server的 rebalance 已经会再次对每个 rq 加锁,而rq_unpin_lock()在 unlock 时会清除SCX_RQ_CLK_VALID;再加上__scx_enabled要到本函数靠后才关闭,最终时钟反正会被失效。
Patch 2 的旧流程
__bpf_kfunc void scx_bpf_events(struct scx_event_stats *events,
size_t events__sz)
{
...
rcu_read_lock();
sch = rcu_dereference(scx_root); /* 永远拿 root */
if (sch)
scx_read_events(sch, &e_sys);
...
}
问题点:
- sub-scheduler 的 BPF 程序拿不到自己的事件计数,只能依赖 sysfs 的 per-scheduler
events文件,BPF 视角"自我观测"这条路被堵死。 - 上面的注释是
/* ... */加半截字段说明,kerneldoc 格式不全。
新流程
Patch 1 的新流程
scx_root_disable() 直接删掉无锁的 scx_rq_clock_invalidate(rq) 调用,并把注释改为解释 dl_server rebalance 的锁循环天然会清掉 SCX_RQ_CLK_VALID,保证下一次 enable 不会读到旧 scheduler 的时钟。v2 把"冗余"语义收紧成"本来就不必要"——scx_rq_clock_update() 在 sched_ext 关闭期间也无法重置 clock,缓存时钟只能由后续上锁路径刷新/失效。
for_each_possible_cpu(cpu) {
struct rq *rq = cpu_rq(cpu);
/* rq clock invalidation is provided by the rq lock cycle below */
scoped_guard(rq_lock_irqirqsave, rq) {
update_rq_clock(rq);
if (was_switched_all) {
...
}
}
}
Patch 2 的新流程
__bpf_kfunc void scx_bpf_events(struct scx_event_stats *events,
size_t events__sz,
const struct bpf_prog_aux *aux)
{
...
rcu_read_lock();
sch = scx_prog_sched(aux); /* 解析调用者程序 */
if (sch)
scx_read_events(sch, &e_sys);
...
}
解析失败时按 scx_prog_sched() 的既有规则 fallback:pre-sub-attach compat root 下返回 root scheduler,其他情况把 events 清零。同时把 BTF_ID_FLAGS(func, scx_bpf_events) 补上 KF_IMPLICIT_ARGS,因为新增的 aux 是 implicit arg。
Patch 概览
- 0001: 删除
scx_root_disable()中的无锁scx_rq_clock_invalidate()调用,更新注释和 Fixes tag。Fixes: 3a9910b5904d ("sched_ext: Implement scx_bpf_now()")。 - 0002: 修改
scx_bpf_events()签名使其接收bpf_prog_aux *,使用scx_prog_sched(aux)解析目标 scheduler,并修复为完整 kerneldoc。
关键实现
Patch 1:删调用 + 修注释
/* Re-balance the dl_server bandwidth reservations: detach ext_server
* (no more sched_ext tasks) and reinstate fair_server if it was
* previously detached because we were running in full mode.
*
* The rq lock cycle also invalidates each rq's clock (rq_unpin_lock()
* clears SCX_RQ_CLK_VALID on unlock), preventing the next enable from
* seeing outdated rq clocks from this scheduler.
*/
static void scx_root_disable(struct scx_sched *sch)
{
...
for_each_possible_cpu(cpu) {
struct rq *rq = cpu_rq(cpu);
scoped_guard(rq_lock_irqsave, rq) {
update_rq_clock(rq);
...
}
}
}
Patch 2:BPF kfunc 签名扩展
/**
* scx_bpf_events - Read the event counters of the calling scheduler
* @events: output buffer from a BPF program
* @events__sz: @events len, must end in '__sz' for the verifier
* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs
*
* Read the event counters of the scheduler associated with the calling program.
* @events is zeroed when no scheduler can be resolved.
*/
__bpf_kfunc void scx_bpf_events(struct scx_event_stats *events,
size_t events__sz,
const struct bpf_prog_aux *aux)
{
struct scx_sched *sch;
struct scx_event_stats e_sys;
rcu_read_lock();
sch = scx_prog_sched(aux);
if (sch)
scx_read_events(sch, &e_sys);
...
}
BTF_ID_FLAGS(func, scx_bpf_events, KF_IMPLICIT_ARGS) 也要补上,让 BPF 验证器把 aux 当 implicit arg 注入。
类比
- Patch 1 像公共洗手间:旧流程是「我先把水龙头拧上『暂停使用』的标签,再排队进隔间」,但排队进门本身就会换锁,标签毫无意义,还可能和别人同时贴标签时把对方那面盖掉。删掉贴标签的动作,让门口换锁自己说话。
- Patch 2 像员工打卡机:以前所有人刷卡都打到公司总台,没法看到自己项目组的加班数;现在刷卡机能识别持卡人属于哪个项目组,每队只能看自己的考勤表,没归属关系就留空。
scx_root_disable() (old) scx_root_disable() (new)
----------------------- -----------------------
for each rq: for each rq:
invalidate_clock() <-- unlocked take rq lock <-- clock invalidates
take rq lock update_rq_clock()
update_rq_clock() dl_server rebal...
dl_server rebal... (one source of truth)
scx_bpf_events() (old) scx_bpf_events() (new)
---------------------- ----------------------
sch = scx_root sch = scx_prog_sched(aux)
(always root counters) (calling scheduler's counters,
fallback to root or zeros)
Highlight:风险与注意点
- Race 风险(patch 1 v1 已修):v1 的注释把"冗余"挂在
rq_unpin_lock()之后,但实际上即便没有该 RMW,clock 也会因为后续加锁/失效被刷新,逻辑上属于"本就不必要"。v2 的描述更准确,也避免读者把它误读成"靠下面的锁循环兜底"。 - 签名兼容性(patch 2):在 BPF 子系统里改
__bpf_kfunc的签名是 ABI 级别的事件,依赖KF_IMPLICIT_ARGS才能让旧调用者无感。如果未来再增加 implicit arg,BTF flag 必须同步更新,否则验证器会因为参数顺序错位拒绝加载。 - Type confusion 漏洞(sashiko bot 指出):把一个
BPF_PROG_TYPE_TRACING/SYSCALL程序关联到非 sched_ext 的 struct_ops map 后,再调用 SCX kfunc,scx_prog_sched()会把外层指针盲转struct sched_ext_ops *,越界读ops->priv引发 kernel panic。Tejun 指出关联是 privileged 操作且显式发生,但这是结构性漏洞;正确做法是在关联路径拒绝"使用 SCX kfunc 的程序挂到非 sched_ext map"。本次只 follow-up,后续修。 KF_IMPLICIT_ARGS与 RCU 标注:patch 2 没有显式标注KF_RCU_PROTECTED,但内部仍然rcu_read_lock();下游若要再扩展,需要保证读锁覆盖范围与 implicit arg 的生命周期一致。
版本变化
- v1 → v2(patch 1):
- commit message 从"subsumed by the rq lock cycle below"改为"doesn't matter in the first place",强调 clock 在
sched_ext禁用期间不可能被重置; - 注释里关于"dl_server rebalance 失效 clock"那段被删,避免双重说法;
- diff 行数从 6+/7- 缩到 3+/8-,纯文本注释改动减少。
- commit message 从"subsumed by the rq lock cycle below"改为"doesn't matter in the first place",强调 clock 在
- patch 2:v1 → v2 之间没有重发,签名/逻辑保持一致。
一句话总结
sched_ext/for-7.3 一并清掉了 scx_root_disable() 里的无锁 race 与冗余时钟失效,并把 scx_bpf_events() 改成读取调用 BPF 程序所属 scheduler 的事件计数(sub-scheduler 终于能看见自己),同时把 implicit arg、kerneldoc 和注释一并补齐。