sched-ext discussion
[PATCH] sched_ext: Fix timer pinning and return value in scx_central
LLM 分析
sched_ext 工具调度器:修复 timer pinning、vtime 偏移丢失与返回值忽略
系列概况
- 标题:
sched_ext: Fix timer pinning and return value in scx_central/sched_ext: Check bpf_timer_start return values in scx_qmap/sched_ext: Fix vtime delta loss in scx_flatcg cgroup migration - 作者:Wanwu Li
<liwanwu@kylinos.cn> - 版本:单版本 v1(3 个独立 patch 同时发出,Message-ID 后缀
-1/-2/-3) - 规模:3 个 patch,影响
tools/sched_ext/下三个示例 BPF scheduler 文件;scx_qmap9 行变化(+6/-3),其余两 patch 各 1-2 行实质改动 - 修改文件:
tools/sched_ext/scx_central.bpf.c、tools/sched_ext/scx_qmap.bpf.c、tools/sched_ext/scx_flatcg.bpf.c,不动内核代码 - 代码统计:合计约 11 行实质改动
- Message-ID:
20260827080738.829103-{1,2,3}-liwanwu@kylinos.cn,外加sashiko-bot自动评审回复与 Tejun Heo 应用邮件- 完整性:完整。Tejun Heo 已Applied the following three patches to sched_ext/for-7.3-fixes,并微调了 scx_central 的 commit message 描述
补丁目的
这一组 patch 集中修复 tools/sched_ext/ 下三个示例 BPF 调度器由近期重构引入的回归 bug:
scx_central的central_timerfn()硬编码BPF_F_TIMER_CPU_PIN,并且忽略bpf_timer_start()的返回值,使内核 <6.7 不支持该 flag 时-EINVALfallback 路径无效,timer 在第一次 tick 后永久死亡且无任何诊断。scx_qmap中三个 timer 回调(monitor_timerfn/lowpri_timerfn/round_robin_timerfn)同样忽略bpf_timer_start()返回值;re-arm 失败会让周期心跳静默停摆,饿死LOWPRI_DSQ中的任务并冻结 cid 轮转。scx_flatcg的fcg_cgroup_move()在机械迁移到time_*()助手时丢掉了有符号 vtime 偏移:time_delta()把负值截断到 0,导致被迁移任务落到目的 cgroup 的 frontier 上、丢失已积累的虚拟时间信用。
三个 bug 的共同特征是:示例代码层面的细枝末节,但会让 sched_ext 用户在生产场景遇到"调度器静默失效、行为不符合预期"——属于工具链而非内核逻辑的修复。
旧流程的问题
Old flow: ignore return value / lose signed semantics in timer re-arm
timer tick
|
v
bpf_timer_start(...., BPF_F_TIMER_CPU_PIN) <- scx_central hardcoded PIN
| scx_qmap passes0 (no PIN)
| (return value ret ignored)
|
v
Failure treated as success:
- Kernel <6.7: timer dies permanently after first tick
- LOWPRI_DSQ never reenqueued - stats heartbeat silent
- cid stops rotating
- user sees "scheduler stuck" with no scx_bpf_error diagnostic
Old flow: cgroup migration (scx_flatcg)
--------------------------------------
delta = time_delta(p->scx.dsq_vtime, from_cgc->tvtime_now)
|
v
If dsq_vtime < tvtime_now (queued task behind frontier),
time_delta() clamps the negative delta to 0
|
v
Task moved to dest cgroup with zero vtime credit,
loses relative position, repeated migrations reset to frontier.
新流程
New flow: honor timer_pinned / check return / restore signed subtraction
timer tick
|
v
ret = bpf_timer_start(timer, interval,
timer_pinned ? BPF_F_TIMER_CPU_PIN : 0)
|
v
if (ret) scx_bpf_error("bpf_timer_start failed (%d)", ret);
|
v
- Old kernels auto fall back to non-PIN path
- On failure, error channel activated, cause visible in trace
New flow: cgroup migration (scx_flatcg)
--------------------------------------
delta = (s64)(p->scx.dsq_vtime - from_cgc->tvtime_now)
|
v
Wrapping subtraction preserves signed semantics.
Negative offset of queued tasks is carried over to the destination cgroup, so relative position matches the source cgroup exactly.
Patch 概览
| # | 文件 | 改动处数 | 类型 | 关键改动 |
|---|---|---|---|---|
| 1/3 | scx_central.bpf.c | 1 | 回归修复 | PIN 改为条件 + 检查 ret |
| 2/3 | scx_qmap.bpf.c | 3 | 健壮性 | 三处 timer fn 都加 if(ret) check |
| 3/3 | scx_flatcg.bpf.c | 1 | 语义修复 | time_delta -> 有符号 (s64) 减法 |
关键实现
Patch 1/3 - scx_central.bpf.c::central_timerfn
@@ central_timerfn()
+ int ret;
- bpf_timer_start(timer, TIMER_INTERVAL_NS, BPF_F_TIMER_CPU_PIN);
+ ret = bpf_timer_start(timer, TIMER_INTERVAL_NS,
+ timer_pinned ? BPF_F_TIMER_CPU_PIN : 0);
+ if (ret)
+ scx_bpf_error("bpf_timer_start failed (%d)", ret);
逻辑要点:把 PIN 标志从硬编码改成 timer_pinned 三元选择,与 central_init() / start_central_timer() 已有的探测路径保持一致;同时捕获返回值并通过 scx_bpf_error() 上报。Fixes tag 指向 22a920209ab6("Implement tickless support"),Tejun 在应用时把 -EINVAL fallback 的归属从 central_init() 修正为 start_central_timer()(d6edb15ad92c)。
Patch 2/3 - scx_qmap.bpf.c 三处 timer
@@ monitor_timerfn()
- bpf_timer_start(timer, ONE_SEC_IN_NS, 0);
+ if (bpf_timer_start(timer, ONE_SEC_IN_NS, 0))
+ scx_bpf_error("failed to re-arm stats timer");
@@ lowpri_timerfn()
- bpf_timer_start(timer, LOWPRI_INTV_NS, 0);
+ if (bpf_timer_start(timer, LOWPRI_INTV_NS, 0))
+ scx_bpf_error("failed to re-arm lowpri timer");
@@ round_robin_timerfn()
- bpf_timer_start(timer, round_robin_ns, 0);
+ if (bpf_timer_start(timer, round_robin_ns, 0))
+ scx_bpf_error("failed to re-arm round-robin timer");
逻辑要点:套用 init 路径已经使用的 if (...) scx_bpf_error(...) 模板;stats timer、lowpri timer、round-robin timer 三处失败都可观测,避免静默停摆。9 行变化(+6/-3),仅一个文件。
Patch 3/3 - scx_flatcg.bpf.c::fcg_cgroup_move
@@ BPF_STRUCT_OPS(fcg_cgroup_move, struct task_struct *p,
- delta = time_delta(p->scx.dsq_vtime, from_cgc->tvtime_now);
+ delta = (s64)(p->scx.dsq_vtime - from_cgc->tvtime_now);
逻辑要点:time_delta() 是把 wall-clock 时间差截到非负区间的助手,不能直接用于可能为负的 dsq_vtime 偏移;显式 (s64) 包装减法保留相对位置。Fixes tag 指向 62addc6dbf36("Use time helpers in BPF schedulers")。
类比
把这三个 bug 类比成"厨房里的三个隐患":
scx_central像一个只会用高压锅但从来不试锅盖漏不漏气的厨师:高压锅(BPF_F_TIMER_CPU_PIN)在部分老炉子上根本不能用,他不试就直接用,结果锅炸了都不知道。scx_qmap像餐厅把三个闹钟的电源插头都拔了却依然贴着"营业中"标签:顾客看不到任何提示,厨房也以为自己在按时出餐,实际上菜早就不动了。scx_flatcg像搬家公司的搬运工按"实际楼层差"算搬家费,但合同里允许负数抵扣("楼上搬楼下"应给补偿)。行政把规则改成了"差额小于 0 按0 算",于是客户搬下楼永远付全价,搬家工永远不补偿。修法就是把规则改回"差额可负"。
Highlight:风险与注意点
- 示例代码也是 ABI 一部分:这些是
tools/sched_ext/下的 reference scheduler,社区常把它们当模板拷贝。PIN 标志硬编码 / 忽略返回值这种 bug 会被下游学走,补丁同时承担"教学样本"的修正职责。 time_delta()不可与 vtime 混用:Fixes tag 提示这是62addc6dbf36机械转换引入的回归。下次再写"统一时间助手"的重构时,要先区分 wall-clock 与 vtime 两个语义域。- Tejun 改写 commit message:Tejun Heo 在
for-7.3-fixes应用时改写了 Patch 1 的描述,指出-EINVALfallback 实际存在于start_central_timer()(d6edb15ad92c)而非central_init()——作者对历史归属描述有误,需核对后续回溯。 - Sashiko 自动评审定位一致:bot 独立指出
central_timerfn的同类问题,恰好与 Patch 1 覆盖;说明这三处 bug 属于同类模式(timer re-arm 错误处理不完整),存在继续扩大审计到scx_pair、scx_userland等其他示例的可能。
版本变化
本系列仅 v1,无版本演进。但 Patch 1 在合入 for-7.3-fixes 时由 Tejun Heo 修改了 commit message 中的 Fixes 归属描述(central_init() -> start_central_timer(),并补充 d6edb15ad92c 作为参考)。
一句话总结
Wanwu Li 一次提交 3 个 patch,把 tools/sched_ext/ 下三个示例 BPF 调度器里 timer re-arm 错误处理与 cgroup 迁移 vtime 语义两类回归一并修掉,被 Tejun Heo 收进 sched_ext/for-7.3-fixes;其中 -EINVAL fallback 的归属描述由 maintainer 微调。