sched discussion
[PATCH v2] sched/core: Skip rq->avg_idle update without a valid idle_stamp
LLM 分析
sched/core:补回 idle_stamp 守卫,避免 rq->avg_idle 被推满 clamp
系列概况
- 标题:[PATCH v2] sched/core: Skip rq->avg_idle update without a valid idle_stamp
- 作者:Shubhang Kaushik (Ampere) sh@gentwo.org
- 版本:v2,2026-08-06
- 规模:1 file changed, 8 insertions(+), 2 deletions(-)
- 修改文件:
kernel/sched/core.c - 代码统计:
update_rq_avg_idle()入口加idle_stamp守卫,delta/max仅在 stamp 非零时计算 - Message-ID(首封):
20260806-master-v2-1-e1f3a1a0c903@gentwo.org - 完整性:thread 共 4 封邮件;index 2(John Stultz)与 index 4(Shubhang)的正文在 lore 抓取中被截断,但 index 3(Zhan Xusheng)给出完整技术评论,thread 立场足以还原。
补丁目的
修复 commit 4b603f1551a73 ("sched: Update rq->avg_idle when a task is moved to an idle CPU") 把 rq->avg_idle 记账搬出 wakeup 路径后丢失的 rq->idle_stamp 合法性检查。
新 helper update_rq_avg_idle() 无条件执行 rq_clock(rq) - rq->idle_stamp;当 stamp 为 0 时,样本退化为 rq_clock(rq) 这种极大值,avg_idle 立刻被推到 2 * max_idle_balance_cost 的 clamp,让 load balancer 误判"这台 CPU 可以睡很久"。
旧流程的问题
- 旧
ttwu_do_activate()在更新前用if (rq->idle_stamp)守住:stamp 为 0 直接跳过更新,结尾的rq->idle_stamp = 0是 no-op。 4b603f1551a73改造后,put_prev_task_idle()→update_rq_avg_idle(rq)链路不再有 stamp 校验。- 多条路径会让 rq 进入 idle 时
idle_stamp仍为 0:find_proxy_task()、force-idling(sched_core_enabled(rq)提前 return)、sched_balance_newidle()中ttwu_pending早返回。
新流程
在 update_rq_avg_idle() 入口先把 idle_stamp 读出来,为 0 直接返回,跳过 delta 计算、平均值更新与 clamp;末尾的 rq->idle_stamp = 0 自然也走不到。
Patch 概览
单文件 diff,集中在 ttwu_do_wakeup() 里对 update_rq_avg_idle(rq) 调用前的局部变量重排,无跨函数改动。
关键实现
@@ static inline void ttwu_do_wakeup(struct task_struct *p)
- 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;
调用关系与 stamp=0 入口:
put_prev_task_idle()
|
v
update_rq_avg_idle(rq) <-- now has idle_stamp guard
|
v
update_avg(); clamp; rq->idle_stamp = 0
stamp=0 入口与真正高频触发点:
+----------------------------+
| pick_next_task_fair() |
| idle: |
+-------------+--------------+
|
+---------------+----------------+
| |
sched_core_enabled(rq) sched_balance_newidle()
return (force-idle) |
| +------------+------------+
v | |
rq goes idle, ttwu_pending? this_rq->idle_stamp
idle_stamp not refreshed | = rq_clock(this_rq)
| early return 0 (before ttwu_pending
| | early return)
v v
put_prev_task_idle() -> update_rq_avg_idle(rq)
needs guard for stamp==0
类比
把 rq->avg_idle 想成一块"待机时长统计表",它依赖上一次进入 idle 时写入的时间戳 idle_stamp。
- 正常路径:CPU 每次进 idle,管家
newidle_balance()都会先在表上盖章,记下进入时刻;出 idle 时用"现在时刻 − 盖章"得到真实待机时长。 - 异常路径(stamp=0):全新上电、或被
ttwu_pending早返回这种"还没盖章就被叫醒"的回路跳过盖章;老电表原本有if (timestamp)这条"没戳就不算"的安全带,新电表把安全带摘了,于是第一次上电就会算出"今天 − 0"这种天文数字,骗负载均衡器说"这户可以睡很久"。 - 本 patch 就是把那条
if (timestamp)安全带重新系回update_rq_avg_idle(),与原始ttwu_do_activate()里的if (rq->idle_stamp)是同一招。
Highlight:风险与注意点
- 真正高频触发路径:Zhan 指出
sched_balance_newidle()中if (this_rq->ttwu_pending) return 0;早返回位于this_rq->idle_stamp = rq_clock(this_rq);之前,这才是 hackbench 这种 wakeup-heavy 负载必踩的入口;而 changelog 里举例的find_proxy_task()、force-idling需要 proxy exec 或CONFIG_SCHED_CORE,不应被当作 root cause。建议 v3 在 changelog 中点名ttwu_pending,让"avg_idle clamp"问题描述变成与配置无关。 - 单调用点影响面:
update_rq_avg_idle()只有一个调用方put_prev_task_idle(),补完守卫后行为空间收回到4b603f1551a73之前,副作用面小。 - 回归覆盖:建议补
TRACE_EVENT(sched_update_rq_avg_idle_skip)或 sched selftest 校验 stamp=0 时确实被跳过,避免再次被重构"优化"掉。 - 审阅链:v2 已加 K Prateek Nayak 的 Reviewed-by,并 Cc John Stultz;v1 → v2 实质差异在 commit message 文案与 trigger 点名。
版本变化
- v1 → v2:加入 Reviewed-by:K Prateek Nayak;commit message 显式举例
find_proxy_task()与force-idling;Cc John Stultz;明示是 earlier proposal 的 narrower variant,避免触碰set_next_task_idle()的 stamp 行为。 - review 反馈(Zhan Xusheng):把 stamp=0 真正高频触发点定位到
sched_balance_newidle()的ttwu_pending早返回,建议 v3 在 changelog 中补一句;并指出 fix 本身与4b603f1551a7移除的旧if (rq->idle_stamp)守卫等价,省略结尾rq->idle_stamp = 0是 no-op。
一句话总结
在 commit 4b603f1551a73 把 rq->avg_idle 记账搬到 put_prev_task_idle() 之后,把丢失的 idle_stamp 合法性检查补回 update_rq_avg_idle(),避免 stamp=0 时把 avg_idle 一脚踩满 clamp。