0/2 已展开

LLM 分析

sched/core:恢复 update_rq_avg_idle 对 idle_stamp 的有效性检查

系列概况

  • 标题:[PATCH] sched/core: Skip rq->avg_idle update without a valid idle_stamp
  • 作者:Shubhang Kaushik (Ampere) sh@gentwo.org
  • 版本:v1(thread_key 中 master-v1-1 表示首版)
  • 规模:单 patch,1 个文件,8 insertions(+) / 2 deletions(-)
  • 修改文件kernel/sched/core.c
  • 代码统计:在 update_rq_avg_idle() 内 cache rq->idle_stamp,加 validity 守卫
  • Message-ID20260728-master-v1-1-f95d9b0147d2@gentwo.org
  • 完整性:包含 diff、Fixes: tag、签名行、hackbench 备注、相关讨论说明与 base-commit
  • 后续回复39ff2c76-d533-4b6c-9449-de19ed18e44a@amd.com(K Prateek Nayak, AMD),正文只回显前一封 commit message,没有独立评审意见

补丁目的

修复 commit 4b603f1551a73rq->avg_idle 记账从 wakeup 路径搬到 put_prev_task_idle() 时丢失的 idle_stamp 合法性检查。原 wakeup 侧代码只在 rq->idle_stamp 非零时才更新 avg_idle;新 helper 失去该守卫后,当 idle_stamp == 0 时会无脑用 rq_clock(rq) 当作 delta,瞬时把 rq->avg_idle 推到 clamp 上限,污染 idle load balancing 的输入样本。

旧流程的问题

put_prev_task_idle()
        |
        v
update_rq_avg_idle(rq)
        |
        +-- delta = rq_clock(rq) - rq->idle_stamp
        |       (when idle_stamp == 0,
        |        delta == rq_clock(rq), a huge bogus value)
        |
        v
update_avg(&rq->avg_idle, delta)   -->  saturates to clamp on first call

旧 wakeup 路径只在 idle_stamp 非零时记账;搬到 put_prev_task_idle() 后这条合法性守卫被丢掉,记账系统被一次虚假"巨大"采样直接饱和。

新流程

put_prev_task_idle()
        |
        v
update_rq_avg_idle(rq)
        |
        +-- cache rq->idle_stamp into local var
        +-- if (unlikely(!idle_stamp)) return;
        |
        v
delta = rq_clock(rq) - idle_stamp
update_avg(&rq->avg_idle, delta)
clamp to max

新流程在入口用 unlikely(!idle_stamp) return; 把没有真实 idle 区间的调用直接挡掉,其他行为保持不变。

Patch 概览

仅 v1,整条线索只包含一个 patch。核心改动集中在 update_rq_avg_idle():把 rq->idle_stamp 缓存到局部变量,并在计算 delta 前做 validity 检查。作者明确没有在 set_next_task_idle() 里额外打 idle stamp,刻意保留既有 newidle 记账模型以避开 forced/proxy idle accounting 争议。

关键实现

 void update_rq_avg_idle(struct rq *rq)
 {
-	u64 delta = rq_clock(rq) - rq->idle_stamp;
-	u64 max = 2*rq->max_idle_balance_cost;
+	u64 idle_stamp = rq->idle_stamp;
+	u64 delta, max;
+
+	if (unlikely(!idle_stamp))
+		return;
+
+	delta = rq_clock(rq) - idle_stamp;
+	max = 2 * rq->max_idle_balance_cost;

要点拆解:

  • rq->idle_stamp 缓存到本地局部变量,避免重复读 rq 字段。
  • unlikely(!idle_stamp) 把"非法采样"挡在记账逻辑之前。
  • delta/max 计算公式与之前一致,正常路径行为不变。
  • 函数结尾 rq->idle_stamp = 0 的清零由调用方/别处处理,未在本补丁改动。

类比

rq->avg_idle 想象成"咖啡店收银台过去几次顾客排队的平均等待时长"。

  • 上一个改动相当于把 start_time 默认设成了"店面开业时间 0",即使收银员当天没开张,关账时也会算出 elapsed = now - 0,把"平均等待时长"瞬间变成天文数字,并被一键钳到 max。
  • 本补丁相当于:关账前先确认"我今天上过班了 (idle_stamp != 0)",没上就直接关账、跳过统计,账本不会被污染。

另一层类比:idle_stamp == 0 类似"温度计还没被激活",直接当作"现在的温度"读数一记下来,会污染趋势曲线;补丁相当于"温度计未就绪就跳过本次采样"。

Highlight:风险与注意点

  • 回归面4b603f1551a73 的初衷是把记账放在 idle task 切出处;本补丁只堵住 idle_stamp==0 这一个漏点,未修复相邻的"被 fake idle 切出"等场景,理论上仍可能喂入不真实样本,需要后续 trace 验证。
  • idle_stamp 状态机:补丁只 return、不清 stamp,留下的 0 状态对下游路径是否还有副作用需要确认,例如 rq->no_balance 或 idle 重新进入路径是否依赖 idle_stamp 已被清零。
  • bench 覆盖度:备注只在 hackbench 上没观察到回归;hackbench 对 CPU idle 区间敏感度有限,SPECjvm、latency-critical workload 或 idle-heavy 服务器负载建议补充验证。
  • 与原方案分歧:作者明确写这是"earlier proposal 的更窄变体",刻意没有在 set_next_task_idle() 中打 idle stamp;该折中能否被 reviewer 接受是上游合并的关键点。
  • 回复信息缺失:第二封邮件正文只回显前一封 commit message,没有看到 K Prateek Nayak 的具体评审意见,建议后续轮次补充确认。

版本变化

仅 v1,没有可对比的 vN -> vN+1 迭代。

一句话总结

本补丁在 update_rq_avg_idle() 入口加 idle_stamp 有效性守卫,避免 idle_stamp == 0 时把 rq_clock(rq) 当成 idle duration,把 rq->avg_idle 瞬时冲到 clamp,是 commit 4b603f1551a73 迁移 avg_idle 记账后的一次最小修复。

                  wakeup task on idle CPU
                          |
                          v
                  put_prev_task_idle()
                          |
                          v
        +----------------------------------------+
        |       update_rq_avg_idle(rq)           |
        +----------------------------------------+
                          |
                    read idle_stamp
                          |
                  +-------+--------+
                  |                |
           idle_stamp == 0   idle_stamp != 0
                  |                |
                return       delta = rq_clock(rq) - idle_stamp
            (skip update)             |
                                     v
                          update_avg(&rq->avg_idle, delta)
                                     |
                                     v
                                 clamp to max