0/2 已展开

LLM 分析

sched/fair:让 root cfs_rq 的剩余调用者也走 update_curr_eevdf()

系列概况

字段内容
标题[PATCH] sched/fair: Use update_curr_eevdf() for the remaining root cfs_rq callers
作者Zhan Xusheng zhanxusheng@xiaomi.com
版本单封 PATCH(v1),最终被 tip: sched/urgent 接收
规模1 个文件,2 处修改,+2/-2
修改文件kernel/sched/fair.c
代码统计2 insertions(+), 2 deletions(-)
Message-ID20260822105930.2352761-1-zhanxusheng1024@gmail.com
完整性含 commit message(含 Fixes/Signed-off-by)、patch diff、tip-bot merge 通知;Vincent Guittot 仅引用正文做背书

补丁目的

修复一个回归:在带 cgroup 的 root cfs_rq 上,pick_task_fair()yield_task_fair() 调用的 update_curr(cfs_rq) 实际上不会更新 task 的 vruntime / deadline。

根因是 update_curr()走的是 cfs_rq->h_curr(group entity),在 !entity_is_task() 处直接 return;但调用方后面读的是 cfs_rq->curr(真正的 task entity),二者身份不一致,导致 EEVDF 的 entity_eligible() 等判断读到陈旧的 vruntime。把这两个调用点换成直接作用于 task 的 update_curr_eevdf(cfs_rq),让 cgroup 场景也能在 pick/yield 之前把 curr 刷新到最新。

旧流程的问题

pick_task_fair() / yield_task_fair()
        |
        v
update_curr(&rq->cfs)            [OLD]
        |
        +-- read cfs_rq->h_curr (top-level group entity)
        +-- if (!entity_is_task(curr)) -> return early   [BUG]
        |
        v
read cfs_rq->curr (task entity) <-- NOT the same object |
        v
entity_eligible() / pick_next_entity() use STALE vruntime

新流程

pick_task_fair() / yield_task_fair()
        |
        v
update_curr_eevdf(&rq->cfs)      [NEW]
        |
        +-- locate cfs_rq->curr (task entity) directly
        +-- __update_curr_eevdf(): vruntime += delta / weight
        +-- update_deadline() / dl_server_update()
        +-- resched_curr_lazy()
        |
        v
entity_eligible() / pick_next_entity() use FRESH vruntime

Patch 概览

仅两行替换,无新增函数、无结构性改动:

  • pick_task_fair()update_curr(cfs_rq) -> update_curr_eevdf(cfs_rq)
  • yield_task_fair()update_curr(cfs_rq) -> update_curr_eevdf(cfs_rq)

关键实现

修改位置(kernel/sched/fair.c):

@@ -10052,7 +10052,7 @@ struct task_struct *pick_task_fair(struct rq *rq, struct rq_flags *rf)
-       update_curr(cfs_rq);
+       update_curr_eevdf(cfs_rq);

@@ -10155,7 +10155,7 @@ static void yield_task_fair(struct rq *rq)
-       update_curr(cfs_rq);
+       update_curr_eevdf(cfs_rq);

两个函数的语义差别:

  • update_curr():面向 hierarchy,取 cfs_rq->h_curr;若为 group entity 则直接 return。
  • update_curr_eevdf():直接作用在 cfs_rq->curr(task),强制走 __update_curr_eevdf(),刷新 vruntime / deadline / dl_server 等。

作者在 commit message 中给出定量证据(10s、三 busy 任务 + 一个 200us 周期任务,单核 pick_task_fair() 统计):

+-----------------------------+-----------+----------------+
| Scenario | calls     | h_curr==curr   |
+-----------------------------+-----------+----------------+
| all tasks in root cgroup    |   43321   |       0        |
| busy in G0, periodic in G1  |   45211   |   45193 (noop) |
+-----------------------------+-----------+----------------+

也即在 cgroup 场景下,绝大多数 pick 实际上都错过了 update_curr() 的执行。

为什么平时看不出来?自 68e37487810a("Fix flat hierarchy")起,tick 与 enqueue / dequeue 已经能正确更新 curr,所以正常 reschedule 路径只缺 pick 前的那几微秒,作者测不到延迟差异。但仍有三条路径前面"没人帮我更新过":

  • core scheduling 下兄弟核的 pick_task()kernel/sched/core.c,那里显式先 update clock 正是为此)
  • fair_server_pick_task()
  • yield_task_fair():陈旧值直接喂给 entity_eligible(),用于判断是否交还剩余 vruntime

这三条路径上 curr 可能落后整整一个 tick,相当于回退到 68e37487810a 之前的行为。

类比

update_curr() 想象成公司前台,她手里拿的是部门考勤汇总表(h_curr)。前台只更新这份汇总;如果部门本身就是 leaf,她就不再去更新每位员工的真实打卡时间。update_curr_eevdf() 则是直接走到员工本人(cfs_rq->curr)面前核对工时,把每分钟该计入 vruntime 的活儿都补上。原来的 pick_task_fair() 把这份工作交给了前台,所以一开 cgroup,员工考勤就漏更;换成 update_curr_eevdf() 后,无论员工挂在哪个部门,考勤都按他本人补齐,部门经理才能正确判断"这位员工手上的活儿干完没有"。

Highlight:风险与注意点

  • cgroup 开启后,root cfs_rq 上 update_curr() 长期是 no-op,但调用方都没察觉,因为它们读 cfs_rq->currh_curr 默默与之不一致——典型的"对的对象、对的字段读错"型 bug。
  • 影响最大的是 yield_task_fair():stale vruntime 会直接喂给 entity_eligible(),可能让 yield 提前放弃剩余时间片,恢复到 68e37487810a 之前的旧行为。
  • core scheduling 兄弟核的 pick_task()fair_server_pick_task() 都属于"前面没人帮我更新"的路径,是重点回归对象。
  • 改动标了 Fixes: 85570f10a4c6("sched/eevdf: Move to a single runqueue"),属于行为修复而非性能优化。常规 scheduler benchmark 可能看不到差异,建议专门压测 yield、core-scheduling、DL server 这几条路径。
  • 不带 cgroup 时行为完全等价:h_curr 就是 task,原调用点本来就走完整 update_curr()。本次只是补齐 cgroup 场景下的"漏更新",不会引入新行为。

版本变化

单封 PATCH,无 v2/v3。Vincent Guittot 仅引用正文做背书,未提出修改。最终被 tip: sched/urgent 接收(commit 1719d035a6fa90b7467b6daf45a573f5180013b2,committer Peter Zijlstra,2026-09-02)。

一句话总结

pick_task_fair() / yield_task_fair() 中错误引用 h_curr 而提前 return 的 update_curr() 替换为直接作用于 task entity 的 update_curr_eevdf(),让 cgroup 场景下 root cfs_rq 的 curr 在 pick / yield 之前被正确刷新,修复85570f10a4c6(EEVDF 单 runqueue 化)引入的回归。