sched discussion
[PATCH] sched/core: fix task_sched_runtime() for proxy execution
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: null,index: null,total: 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入口的语义解耦:
task_current(rq, p)用来回答"现在跑指令的是不是 p"——这是 flush pending runtime 的触发条件,因为 pendingsum_exec_runtime挂在rq->curr上rq->donor->sched_class->update_curr(rq)用来把更新动作交给调度上下文的 sched_class,因为调度记账实体是 donor- 两端解耦后,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:风险与注意点
- 跨 sched_class 的 update_curr 路径:RT donor + fair curr场景下,
rq->donor->sched_class->update_curr(rq)是 RT class 的入口去更新 RT 的调度状态,需确认不会对 curr 侧的 fair 实体产生误记账或空指针 - Fixes tag 链与 stable 回溯:
Fixes: 7de9d4f94638指向 "sched: Start blocked_on chain processing in find_proxy_task()",属于 proxy execution 近期引入的回归;后续可能需要Cc: stable@vger.kernel.org触发 stable 回溯 - stale 窗口长度依赖调度频率:stale 持续到下一次 scheduler accounting 事件,对高 tick latency 的低优先级 task 可见性更差,建议加 perf/rt-tests 自动化用例
- rq lock 持有假设:
task_current与task_current_donor的实现都依赖 rq lock;本 patch 未改 lock 逻辑,需确认现有task_sched_runtime()上下文中 rq lock 仍然持有 - 瞬态 race:mutex owner 进入睡眠的瞬间,donor / curr 关系切换,
task_current在该瞬态点是否仍能正确返回,需要进一步 review - 测试形式:作者仅口头描述手工验证,未提供 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 = bugfix:Fixes: 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 路径。