0/4 已展开

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_qmap 9 行变化(+6/-3),其余两 patch 各 1-2 行实质改动
  • 修改文件tools/sched_ext/scx_central.bpf.ctools/sched_ext/scx_qmap.bpf.ctools/sched_ext/scx_flatcg.bpf.c,不动内核代码
  • 代码统计:合计约 11 行实质改动
  • Message-ID20260827080738.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:

  1. scx_centralcentral_timerfn() 硬编码 BPF_F_TIMER_CPU_PIN,并且忽略 bpf_timer_start() 的返回值,使内核 <6.7 不支持该 flag 时 -EINVAL fallback 路径无效,timer 在第一次 tick 后永久死亡且无任何诊断。
  2. scx_qmap 中三个 timer 回调(monitor_timerfn / lowpri_timerfn / round_robin_timerfn)同样忽略 bpf_timer_start() 返回值;re-arm 失败会让周期心跳静默停摆,饿死 LOWPRI_DSQ 中的任务并冻结 cid 轮转。
  3. scx_flatcgfcg_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/3scx_central.bpf.c1回归修复PIN 改为条件 + 检查 ret
2/3scx_qmap.bpf.c3健壮性三处 timer fn 都加 if(ret) check
3/3scx_flatcg.bpf.c1语义修复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:风险与注意点

  1. 示例代码也是 ABI 一部分:这些是 tools/sched_ext/ 下的 reference scheduler,社区常把它们当模板拷贝。PIN 标志硬编码 / 忽略返回值这种 bug 会被下游学走,补丁同时承担"教学样本"的修正职责。
  2. time_delta() 不可与 vtime 混用:Fixes tag 提示这是 62addc6dbf36 机械转换引入的回归。下次再写"统一时间助手"的重构时,要先区分 wall-clock 与 vtime 两个语义域。
  3. Tejun 改写 commit message:Tejun Heo 在 for-7.3-fixes 应用时改写了 Patch 1 的描述,指出 -EINVAL fallback 实际存在于 start_central_timer()d6edb15ad92c)而非 central_init()——作者对历史归属描述有误,需核对后续回溯。
  4. Sashiko 自动评审定位一致:bot 独立指出 central_timerfn 的同类问题,恰好与 Patch 1 覆盖;说明这三处 bug 属于同类模式(timer re-arm 错误处理不完整),存在继续扩大审计到 scx_pairscx_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 微调。