0/1 已展开

LLM 分析

sched/core:修复 task_sched_runtime() 在 proxy 执行场景下的 stale 读取

系列概况

  • 标题:[PATCH] sched/core: fix task_sched_runtime() for proxy execution
  • 作者:Hui Su <sh_def@163.com>
  • 版本:单封 v1 patch,无 series 编号(version: nullindex: nulltotal: null
  • 规模:1 个文件,2 处修改,2 处新增
  • 修改文件:kernel/sched/core.c
  • 代码统计:1 file changed, 2 insertions(+), 2 deletions(-)
  • Message-ID:20260902112539.879979-1-sh_def@163.com
  • 完整性:单封 patch,无后续回复;携带 Fixes: 7de9d4f94638 指向引入 proxy execution 的 commit

补丁目的

在 proxy execution(代理执行)机制下,rq->donor(调度上下文:名义上的"持有 CPU 槽位"者)与 rq->curr(执行上下文:真正在跑指令的 task)可以是不同的 task。运行时记账把 sum_exec_runtime 挂在 rq->curr 上,而 scheduler 侧的 update_curr 是从 rq->donor 的 sched_class 发起。函数 task_sched_runtime()CPUCLOCK_SCHED 读路径,原先只对 rq->donor == p 的情况 flush pending runtime。当一个 mutex owner 被代理执行时,该 owner 实际是 rq->curr 而非 rq->donor,于是其 pending sum_exec_runtime 在返回前没有被 flush,clock_gettime(CLOCK_SCHED) 等接口观察到 stale 值,直到下一次 scheduler accounting 事件才更新。

旧流程的问题

  • 判定条件 task_current_donor(rq, p) 只在 p == rq->donor 时触发 flush pending runtime
  • proxy场景下 rq->donor != rq->curr,被代理执行的 owner(p == rq->curr)不满足判断 → 不 flush
  • CPUCLOCK_SCHED 因此返回陈旧 sum_exec_runtime
  • stale 持续到下一次 scheduler tick / accounting 事件为止
  • 对 realtime / proxy-mutex 路径上是 user-visible 的正确性问题
  • 旧调用 p->sched_class->update_curr(rq) 还隐含把 scheduler 侧记账入口绑到 p 的 class,而不是 donor 的 class,进一步放大了跨 class 场景的语义错误

新流程

  • 把"是否需要 flush"的判定从 task_current_donor(rq, p) 换成 task_current(rq, p):判断 p 是否是当前正在执行的 task(即 rq->curr),覆盖 proxy execution 中 owner 即 curr 的情况
  • update_curr 的入口换成 rq->donor->sched_class->update_curr(rq):scheduling state 仍然属于 donor,记账入口必须由 donor 的 sched_class 触发
  • rq->donor == rq->curr(即非 proxy 普通场景)时行为与旧逻辑完全一致
  • task_on_rq_queued(p) 这条限定保持不变,避免对迁移中或睡眠中的 task误触发

Patch 概览

只有1 个 patch,单 hunk:

--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -5706,10 +5706,10 @@ unsigned long long task_sched_runtime(struct task_struct *p)
- if (task_current_donor(rq, p) && task_on_rq_queued(p)) {
+ if (task_current(rq, p) && task_on_rq_queued(p)) {
-   p->sched_class->update_curr(rq);
+   rq->donor->sched_class->update_curr(rq);

关键实现

判断条件与 update入口的语义解耦:

  1. task_current(rq, p) 用来回答"现在跑指令的是不是 p"——这是 flush pending runtime 的触发条件,因为 pending sum_exec_runtime 挂在 rq->curr
  2. rq->donor->sched_class->update_curr(rq) 用来把更新动作交给调度上下文的 sched_class,因为调度记账实体是 donor
  3. 两端解耦后,donor 和 curr 同属一个 sched_class(普通场景)行为不变;RT donor + fair curr 这种跨 class 场景,scheduler 侧记账也由 RT class 正常推进测试覆盖(作者声明手工验证):
  • RT donor proxy-execute fair mutex owner
  • fair donor proxy-execute fair mutex owner
  • 非 proxy 对照组行为保持不变

流程图

proxy execution 下 rq 内 donor/curr 分离与 flush 触发逻辑:

+---------------- runqueue (rq) ----------------+
|                                                |
|   donor = scheduling context (who "owns" the   |
|           CPU slot; scheduler-side accounting) |
|   curr  = execution context (who runs code;    |
|           sum_exec_runtime counter owner)      |
|                                                |
|   Normal case (no proxy):                      |
|     donor == curr == task A                    |
|     task_sched_runtime(A) flush + return OK    |
|                                                |
|   Proxy execution case:                        |
|     donor = task A (mutex owner, blocked)      |
|     curr  = task B (proxy, running) |
|     task_sched_runtime(B): B == curr           |
|       OLD: not donor -> not flushed -> STALE   |
|       NEW: == curr   -> flushed     -> FRESH   |
|     task_sched_runtime(A): A == donor          |
|       update_curr still via donor's class |
|       so RT-donor + fair-curr gets RT update |
+------------------------------------------------+

旧 vs 新 flush 路径对照:

 [caller: clock_gettime(CLOCK_SCHED)]
 |
                    v
 task_sched_runtime(p, rq, ...)
 |
       +-----------+-----------+
       |                       |
       v v
   OLD logic NEW logic
   task_current_donor?     task_current?
       |                       |
       | no (p==curr, not | yes (p==curr)
       | donor in proxy)     |
       v                       v
   skip update_curr         rq->donor->sched_class return p->se.sum_exec ->update_curr(rq)
 (STALE)                 return p->se.sum_exec (FRESH)

类比

把 CPU 想象成一台汽车的发动机舱:donor 是「这辆车的登记车主」(即名义上的"占有这辆车"),curr 是「此刻实际坐在驾驶座踩油门的人」。proxy execution 就像「借车」——朋友借你的车去办事,你是车主(donor),但方向盘后面坐的是朋友(curr)。task_sched_runtime() 就像读里程表(sum_exec_runtime)。

旧代码问"车主本人是不是在开自己的车"——借车场景下车主根本不在驾驶位(task_current_donor 不成立),所以里程表不刷新,读到的还是上次的数字。新代码改成问"有没有人正在开车"——只要驾驶座上有人(task_current),里程就必须刷新;但里程该记到"车主"名下,而不是记到临时驾驶员头上——所以 update_curr 的入口还是要走 donor 的 sched_class(rq->donor->sched_class->update_curr),就像里程还是记在你的行驶本上。

Highlight:风险与注意点

  1. 跨 sched_class 的 update_curr 路径:RT donor + fair curr场景下,rq->donor->sched_class->update_curr(rq) 是 RT class 的入口去更新 RT 的调度状态,需确认不会对 curr 侧的 fair 实体产生误记账或空指针
  2. Fixes tag 链与 stable 回溯Fixes: 7de9d4f94638 指向 "sched: Start blocked_on chain processing in find_proxy_task()",属于 proxy execution 近期引入的回归;后续可能需要 Cc: stable@vger.kernel.org 触发 stable 回溯
  3. stale 窗口长度依赖调度频率:stale 持续到下一次 scheduler accounting 事件,对高 tick latency 的低优先级 task 可见性更差,建议加 perf/rt-tests 自动化用例
  4. rq lock 持有假设task_currenttask_current_donor 的实现都依赖 rq lock;本 patch 未改 lock 逻辑,需确认现有 task_sched_runtime() 上下文中 rq lock 仍然持有
  5. 瞬态 race:mutex owner 进入睡眠的瞬间,donor / curr 关系切换,task_current 在该瞬态点是否仍能正确返回,需要进一步 review
  6. 测试形式:作者仅口头描述手工验证,未提供 selftest / perf 脚本;建议把 RT + fair 代理执行场景沉淀到 tools/testing/selftests/sched/

一句话总结

task_sched_runtime() 中"是否 flush pending"的判断从 donor 改成 curr,但 update_curr 入口仍走 donor 的 sched_class,从而修掉 proxy execution 下 CPUCLOCK_SCHED 读到 stale runtime 的回归 bug。

分类依据

primary_category = bugfixFixes: tag 指向具体回归 commit,"stale runtime"、"reads can therefore observe stale runtime until the next scheduler accounting event" 属于典型的 correctness / regression 修复语义;同时提交者明确给出复现窗口与修复后验证。is_important = true:proxy execution 是调度器近年关键特性,CPUCLOCK_SCHED 陈旧读取是 user-visible 的正确性问题,且 Fixes 链清晰,影响面涉及 RT + fair 跨 class 路径。