0/3 已展开

LLM 分析

sched/core:跳过无效 idle_stamp 的 avg_idle 更新

系列概况

  • 标题:[PATCH v3] sched/core: Skip rq->avg_idle update without a valid idle_stamp
  • 作者:Shubhang Kaushik (Ampere) sh@gentwo.org
  • 版本:v3(关联 v1 2026-07-28、v2 2026-08-06,本封 v3 2026-08-07)
  • 规模:单文件改动,kernel/sched/core.c,净增 6 行(8 insertions / 2 deletions)
  • 修改文件:kernel/sched/core.c(函数 update_rq_avg_idle()
  • Message-ID:20260807-master-v3-1-c328354efed3@gentwo.org
  • 完整性:6封邮件齐全(1 封 patch 主送 + 2 封作者 ping + 1 封 Vincent Guittot 引用 + 1 封 Zhan Xusheng 技术分析 + 1 封 tip-bot2 入树通告);最终 commit c6dcd97c8be75f052a1ca52cf79b03e7292962f1 于 2026-09-02 由 Peter Zijlstra 经 tip:sched/urgent 合入。

补丁目的

修复 commit 4b603f1551a73("sched: Update rq->avg_idle when a task is moved to an idle CPU")引入的回归。该提交把 rq->avg_idle 的统计点从 wakeup 路径搬到了 put_prev_task_idle(),但搬的过程中漏掉了 idle_stamp == 0 的有效性检查:

u64 delta = rq_clock(rq) - rq->idle_stamp;   // 没保护,直接算

idle_stamp 为 0 时,delta 实际上等于 rq_clock(rq)(一个非常大的值),第一次进 update_avg() 就把 avg_idle 推到 clamp,后续两个使用点(fair.c:1450414584)再也压不住,就会倾向于一直发起 newidle balancing,造成额外调度开销。

旧流程的问题

旧代码(被 4b603f1551a73 替换前):

if (rq->idle_stamp) {
    u64 delta = rq_clock(rq) - rq->idle_stamp;
    update_rq_avg_idle(...);
}

校验和计算绑在一起,stamp 为 0 时根本不会进入统计。

新代码(被本次 patch 修复的目标):

void update_rq_avg_idle(struct rq *rq)
{
    u64 delta = rq_clock(rq) - rq->idle_stamp;   // 无校验
    update_avg(&rq->avg_idle, delta);
    if (rq->avg_idle > max)
        rq->avg_idle = max;
    rq->idle_stamp = 0;
}

把统计函数独立出来后,guard 丢了。

新流程

void update_rq_avg_idle(struct rq *rq)
{
    u64 idle_stamp = rq->idle_stamp;
    u64 delta, max;

    if (!idle_stamp)
        return; /* stamp不可信,跳过整次统计 */

    delta = rq_clock(rq) - idle_stamp;
    max   = 2 * rq->max_idle_balance_cost;
    update_avg(&rq->avg_idle, delta);
    if (rq->avg_idle > max)
        rq->avg_idle = max;
    rq->idle_stamp = 0;
}

行为变化只是 stamp 为 0 时早退——保留原本的 newidle 记账思路,不去 set_next_task_idle() 里强行 stamp,避免触动 force-idle / proxy-exec 记账路径。

Patch 概览

唯一 hunk 在 kernel/sched/core.cupdate_rq_avg_idle()

  • 新增 u64 idle_stamp = rq->idle_stamp; 局部缓存;
  • 新增 if (!idle_stamp) return; 早退分支(v3 删掉了 unlikely());
  • delta / max 改成函数顶部先声明、guard 通过后再赋值。

关键实现

-触发路径:当 sched_balance_newidle() 因为 this_rq->ttwu_pending 提前返回时,idle_stamp 没被设置,rq 进入 idle;随后 wakeup 走 put_prev_task_idle()update_rq_avg_idle(),stamp 还是 0。

  • 类似路径:newidle_balance()find_proxy_task() 或 force-idling 切到 idle,也漏写 stamp。
  • 验收:Prateek Nayak (AMD) Reviewed-by;John Stultz Acked-by;hackbench 多轮跑分相对 v7.2-rc5 平均差约 1.5%,与基线抖动同档。

类比

idle_stamp 想成「电表读数」:贴封条时记一下底数,拆封条时算差值就是这一段的用电量。4b603f1551a73 把「贴封条」和「算电费」分到了两个不同的柜台,但搬动时把「检查封条有没有贴」这一步忘在了旧柜台前,于是新柜台经常拿「今天0 点」当底数算电费,每一笔都飙到上限。update_rq_avg_idle() 的 stamp guard 就是把「没贴封条就退单,不收钱」写回到新柜台。

+------------------+ +------------------------+
| wakeup path |         |  put_prev_task_idle()  |
|  (old accounting)| |  (new accounting path) |
+------------------+         +------------------------+
        |                              |
        v v
  guard idle_stamp? guard idle_stamp?
 |                              |
        Y (kept)                  N <-- BUG: 被搬走时丢了
        |                              |
        v                              v
  delta = clock - stamp delta = clock - 0  (~1e12)
        |                              |
        v                              v
 update_avg(...) update_avg(...) ---> clamp 立刻命中 ---> newidle balancing永远打开

Highlight:风险与注意点

  • idle_stamp 在整个内核只有一处赋值(fair.c:14563),位置在 ttwu_pending bail-out 之"下";任何绕开这一行进入 idle 的路径都会复现这个 bug。Zhan Xusheng 在评审中指出,作者 changelog 列举的"三种情形"是窄了,实际可达集合更大。
  • 第一次命中即饱和:update_avg()diff/8,几秒钟启动后 rq_clock() 已达1e12 量级,单次就把 avg_idle 推到 2*max_idle_balance_cost(1e4~1e5范围)的 clamp 上。
  • 后果是"只多不少":两个消费点(fair.c:1450414584)据此判断是否值得做 newidle balancing,stamp=0 让它们从不抑制,newidle 路径被常态打开。
  • 设计选择:本 patch 选择"在统计入口 guard",而不是"在 set_next_task_idle() 里强制 stamp"——后者会污染 force-idle / proxy-exec 的现有记账模型,作者主动回避。
  • 验证:hackbench 单负载、多轮平均1.5% 抖动,未见回归;建议补 perf stat -e sched:* 抓 newidle 触发率确认收敛。

版本变化

  • v1(2026-07-28):首版补丁,引入 idle_stamp 早退。
  • v2(2026-08-06):新增 Prateek Reviewed-by;在描述中补充 find_proxy_task() 与 force-idling 作为无 stamp 进入 idle 的示例;抄送 John Stultz。
  • v3(2026-08-07):在描述中把 sched_balance_newidle()/ttwu_pending 明确为触发示例;去掉 unlikely();新增 John Stultz Acked-by。
  • 入树(2026-09-02):经 tip:sched/urgent 合入,commit c6dcd97c8be75f052a1ca52cf79b03e7292962f1,提交者 Peter Zijlstra。

一句话总结

idle_stamp==0 这道闸门装回到搬动后的 update_rq_avg_idle() 入口,避免 rq_clock() 被当成空闲时长、一次性把 avg_idle 推到 clamp、让 newidle balancing 一直开着。