0/13 已展开

LLM 分析

sched/fair: Only apply cpufreq pressure where frequency is invariant

系列概况

  • 标题: [PATCH] sched/fair: Only apply cpufreq pressure where frequency is invariant
  • 作者: Jianyong Wu(wujianyong@hygon.cn,后续通过 jianyong.wu@outlook.comjong wu 继续回复)
  • 版本: v1(单 patch;thread 中作者重写了 commit message 的解释,未发布 v2 文本)
  • 规模: 1 个 patch,13 封邮件(1 patch + 12 review/reply)
  • 修改文件: kernel/sched/fair.cget_actual_cpu_capacity()
  • 代码统计: 1 file changed, 9 insertions(+), 2 deletions(-)
  • Message-ID: 20260821073927.455475-1-wujianyong@hygon.cn
  • 完整性: 完整(patch → Vincent/Hongyan 质疑 → 作者承认 v1 解释错误、改写根因 → 多人追问 boost 与 policy->max 关系 → K Prateek Nayak 给出 cpufreq 层替代修复方案)

补丁目的

get_actual_cpu_capacity() 一直无条件把 cpufreq pressure 折进 capacity:

capacity -= max(hw_load_avg(cpu_rq(cpu)), cpufreq_get_pressure(cpu));

这种扣减只在架构声明 frequency invariant(utilization 已按当前频率同步缩放)时才正确。

在非 invariant 平台上,commit d2d5c129d07e("cpufreq: Make cpufreq_update_pressure() fall back to cpuinfo.max_freq")让 cpufreq_update_pressure() 即使没有真正硬件限频也输出正压力。结果 capacity 被持续压低,utilization 却仍按满频累计,繁忙 CPU 的 util 反超 capacity,调度器误判。

旧流程的问题

static inline unsigned long get_actual_cpu_capacity(int cpu)
{
    unsigned long capacity = arch_scale_cpu_capacity(cpu);
    capacity -= max(hw_load_avg(cpu_rq(cpu)), cpufreq_get_pressure(cpu));
    return capacity;
}
  • 默认语义"capacity 与 utilization 同基准缩放"只在 arch_scale_freq_invariant() 为真时成立。
  • d2d5c129d07ecpuinfo.max_freq 作为 arch_scale_freq_ref() 的回退,让非 invariant 系统也开始出现正压力。
  • 作者在第 4 封回复里给出真正根因:在 x86 + acpi-cpufreq 上,cpuinfo.max_freq 含 autonomous boost 频率,policy->max 解析为 _PSS 中最高可选项;开启 boost 时实测频率可超 policy->max,因此 policy->max < cpuinfo.max_freq 并不代表存在有效硬件限频,回退假设失效。

新流程

static inline unsigned long get_actual_cpu_capacity(int cpu)
{
    unsigned long capacity = arch_scale_cpu_capacity(cpu);
    unsigned long pressure = hw_load_avg(cpu_rq(cpu));

    /*
     * Utilization only follows frequency where the architecture is
     * frequency invariant. Elsewhere, lowering the capacity would
     * scale one side of the comparison and not the other.
     */
    if (arch_scale_freq_invariant())
        pressure = max(pressure, cpufreq_get_pressure(cpu));

    return capacity - pressure;
}

只在架构声明 frequency invariant 时才把 cpufreq pressure 合并进来;否则只用 hw_load_avg

Patch 概览

文件函数关键改动
kernel/sched/fair.cget_actual_cpu_capacity()引入 pressure 局部变量,用 arch_scale_freq_invariant() 守卫 cpufreq_get_pressure(cpu) 的合并

关键实现

  • 入口点:get_actual_cpu_capacity(int cpu),被 select_task_rq_fair()find_energy_efficient_cpu()util_fits_cpu() 等选核/能量路径调用。
  • 关键判断:arch_scale_freq_invariant()(arm64 默认 true,多数 x86 也 true,用于"util 已按当前 freq 缩放"语义)。
  • 物理含义:util 与 capacity 必须用同一缩放基准;util 若始终以 SCHED_CAPACITY_SCALE 表示满频工作量,就不能拿"当前 freq / max freq"反推 capacity。

ASCII 流程图

+---------------------------------------------------------------+
| OLD FLOW (non-invariant, e.g. x86 + acpi-cpufreq)             |
+---------------------------------------------------------------+
| policy->max      <- _PSS P-state cap                          |
| cpuinfo.max_freq <- includes boost frequency                  |
|        |                                                      |
|        v                                                      |
| cpufreq_update_pressure()                                     |
|   max_freq    = cpuinfo.max_freq  [after d2d5c129d07e]        |
|   capped_freq = policy->max                                   |
|   if (max_freq <= capped_freq) -> skip                        |
|                                 -> else positive pressure    |
|        |                                                      |
|        v                                                      |
| positive pressure  (NO real HW cap, yet non-zero)             |
|        |                                                      |
|        v                                                      |
| get_actual_cpu_capacity():                                    |
|   capacity -= max(hw_load_avg, cpufreq_pressure)              |
|        |                                                      |
|        v                                                      |
| util NOT scaled, capacity SHRUNK -> util > capacity -> BUG    |
+---------------------------------------------------------------+

+---------------------------------------------------------------+
| NEW FLOW (this patch)                                         |
+---------------------------------------------------------------+
| if (arch_scale_freq_invariant())                              |
|    true  -> pressure = max(hw_load_avg, cpufreq_pressure)     |
|              [both sides scaled by freq -> OK]                |
|    false -> pressure = hw_load_avg                            |
|              [drop cpufreq_pressure -> no asymmetric shrink]  |
+---------------------------------------------------------------+

+---------------------------------------------------------------+
| REVIEW TIMELINE                                               |
+---------------------------------------------------------------+
| m1  Jianyong     : v1 patch (frequency-invariant explanation) |
| m2  Vincent      : question / ask for clarification            |
| m3  Hongyan      : question / ask for clarification            |
| m4  Jianyong     : admits v1 message wrong;                    |
|                    root cause = cpuinfo.max_freq includes     |
|                    boost frequency, != effective HW cap       |
| m5-m11  reviews  : discussion on cpuinfo.max_freq vs          |
|                    policy->max semantics                      |
| m12-m13 Prateek  : propose cpufreq-layer fix using            |
|                    __resolve_freq() instead of                |
|                    cpuinfo.max_freq fallback                  |
+---------------------------------------------------------------+

类比

调度器记两本账:util = 这辆车实际跑了多远;capacity = 这辆车油箱里的油

旧逻辑说:"如果限速 70(cpufreq pressure),就把油量按 100/70 打折。"这只有当里程本身也是按"实际能跑的上限 70"来记的时候才自洽。

d2d5c129d07e 把"理论最大速度"(含 boost)当成了"限速 70"塞了进来——其实根本没限速,却被扣了油。结果里程 > 油量,明明跑了那么多路油却不够,调度器以为 CPU 已跑满。

补丁的做法:只有里程本来就是按当前频率记的(frequency invariant),才同步扣油;否则不要被这个假压力扣掉。

Highlight:风险与注意点

  • 修在 sched 层只是"止血":真正不变量是 cpuinfo.max_freq 的语义。Prateek 提议在 cpufreq_update_pressure() 把 fallback 从 cpuinfo.max_freq 换成 __resolve_freq(policy, policy->cpuinfo.max_freq, policy->max, policy->min, ...),从源头消除伪压力;sched 层与 cpufreq 层两种思路需要 maintainer 取舍。
  • commit message 误导:作者在第 4 封承认 v1 的"frequency invariant 不对称"叙述错误,真正根因是 boost 让 cpuinfo.max_freq 不等于"硬件有效上限",存在 merging 时被要求重写提交说明的风险。
  • arch_scale_freq_invariant() 覆盖面:arm64 默认 true、多数 x86 也 true,所以这个改动只在真正非 invariant 的少量平台上生效;非 invariant 平台把 cpufreq pressure 完全丢掉,会不会反过来低估 capacity,仍需实测。
  • 可能回归d2d5c129d07e 的回退本意是给没有 arch_scale_freq_ref() 的平台一个合理估计;本 patch 在 sched 侧关闭它,相当于把问题推到 cpufreq 侧解决,需要 cpufreq 维护者(Viresh/Rafael)意见。
  • 验证点:x86 + acpi-cpufreq、intel_pstate、arm64 + schedutil 上 util_fits_cpu() 与 EAS 选核行为是否出现可见差异。

版本变化

  • v1 提交说明(被作者自我否定):根因为 frequency invariant 不对称。代码仅 9 行新增。
  • v1 修订说明(thread 内改写):根因改为 cpuinfo.max_freq 含 boost 频率、与 policy->max 不可比。代码未变。
  • 截至 thread 末尾未见 v2 patch 文本,但 Prateek 的 __resolve_freq(...) 提议暗示 maintainer 倾向在 cpufreq 层修,作者下一步可能发 v2 把 fallback 替换。

与其他相关 patch 系列的关联

  • d2d5c129d07e("cpufreq: Make cpufreq_update_pressure() fall back to cpuinfo.max_freq"):本 patch Fixes: 指向的引入 commit。
  • cpufreq_update_pressure()arch_scale_freq_ref()arch_scale_freq_invariant() 的语义需要 cpufreq + sched 双方共同维护;Prateek 提议的 __resolve_freq(...) 改动落在 drivers/cpufreq/cpufreq.c,跨子系统讨论。
  • find_energy_efficient_cpu()select_task_rq_fair()util_fits_cpu() 都间接消费 get_actual_cpu_capacity(),是回归测试要覆盖的入口。

一句话总结

cpufreq 回退到 cpuinfo.max_freq 后在非 frequency-invariant 平台上引入虚假压力,让 capacity 被扣而 util 未缩放;本 patch 用 arch_scale_freq_invariant() 守卫 cpufreq_get_pressure() 的合并,恢复 capacity/utilization 的对称缩放。