0/2 已展开

LLM 分析

cpufreq schedutil:修复 rate_limit_us 32 位溢出

系列概况

  • 标题:[PATCH v3] cpufreq: schedutil: Fix rate limit overflow
  • 作者:Hui Su sh_def@163.com
  • 版本:v3(演进自 v1/v2)
  • 规模:1 个文件,新增 13 行,删除 2 行
  • 修改文件:kernel/sched/cpufreq_schedutil.c
  • 代码统计:1 file changed, 13 insertions(+), 2 deletions(-)
  • Message-ID:20260806142304.1761454-1-sh_def@163.com
  • 完整性:含 commit message、Fixes/Reviewed-by/Signed-off-by、v3 changelog 与前置 v1/v2 链接
  • Reviewer:Zhongqiu Han(已 Reviewed-by)
  • Cc:stable@vger.kernel.org,需要回溯到 stable 分支
  • 跟进邮件:Rafael J. Wysocki(cpufreq maintainer)的回复(正文基本只是引用 v3 patch)

补丁目的

schedutil governor 用 rate_limit_us * NSEC_PER_USEC 计算 freq_update_delay_ns
rate_limit_usunsigned intNSEC_PER_USEC1000L
在 32 位平台上两操作数都按 32 位 unsigned long 做乘法,再把结果赋给 s64 freq_update_delay_ns

写入 rate_limit_us = 4294968 时,期望 4294968000 ns,但 32 位乘法溢出为 704 ns
schedutil 频率更新频率远高于配置。补丁抽出 helper,在乘法前显式把 rate_limit_us 扩到 s64,消除溢出。

旧流程的问题

sg_policy->freq_update_delay_ns = rate_limit_us * NSEC_PER_USEC;
sg_policy->freq_update_delay_ns = sg_policy->tunables->rate_limit_us * NSEC_PER_USEC;
rate_limit_us (u32)        NSEC_PER_USEC (long)
        |                          |
        +------- 32-bit mul -------+
                    |
                    v
        freq_update_delay_ns (s64)  <-- low 32 bits only
                    |
                    v
        4294968000 ns  -->  704 ns
        schedutil freq updates run wild

新流程

新增 helper,并在 sysfs 写入与 governor 启动两条路径上都改用它:

    rate_limit_us (u32) --(s64)--> s64 value
                                        |
                                        v
                       s64 * NSEC_PER_USEC  (64-bit mul)
                                        |
                                        v
                       freq_update_delay_ns (s64)
                                        |
                                        v
                  real delay = 4294968000 ns (no wrap)

Patch 概览

  • 新增静态函数 sugov_update_rate_limit_us(),含说明性注释
  • rate_limit_us_store()(sysfs 写入路径)改用 helper
  • sugov_start()(governor 启动路径)改用 helper
  • Fixes 指向引入该问题的 commit 9bdcb44e391d
  • Cc stable@vger.kernel.org

关键实现

static void sugov_update_rate_limit_us(struct sugov_policy *sg_policy)
{
    /*
     * Cast rate_limit_us before multiplication to force 64-bit arithmetic.
     * Otherwise, on 32-bit platforms, both operands are converted to
     * 32-bit unsigned long and the multiplication may overflow.
     */
    sg_policy->freq_update_delay_ns =
        (s64)sg_policy->tunables->rate_limit_us * NSEC_PER_USEC;
}
/* rate_limit_us_store() */
- sg_policy->freq_update_delay_ns = rate_limit_us * NSEC_PER_USEC;
+ sugov_update_rate_limit_us(sg_policy);

/* sugov_start() */
- sg_policy->freq_update_delay_ns = sg_policy->tunables->rate_limit_us * NSEC_PER_USEC;
+ sugov_update_rate_limit_us(sg_policy);

类比

rate_limit_us 想象成一个最多装 4 L 的小水桶,NSEC_PER_USEC 是一杯 1000 mL 的水。
本想倒 4.29 L 进大盆,水桶(32 位)只能装 4 L,多出来的水被截断,
大盆里只剩 704 mL,倒水动作(频率更新)就变得又急又频繁。

补丁相当于先把水倒进能装 4.29 L 的大桶(s64),桶能装下再做乘法,水就不会再溢出。

Highlight:风险与注意点

  • 触发面:32 位平台 + rate_limit_us > ~4.29s(毫秒级大延迟配置)。64 位平台不受影响,
    但补丁统一了 sysfs 与启动两条路径,风格更安全。
  • 类型基础:freq_update_delay_nss64,但 tunables->rate_limit_usunsigned int
    类型不匹配本身就是隐患;建议在 tunable 结构里同步检查类型。
  • 验证点:在 32 位 ARM/i386 构建上,用 sysfs 写 rate_limit_us=4294968 后应读到接近
    4294968000 ns,而不是 704 ns
  • stable 回溯:Fixes 指向原始 commit,Cc stable 表示要 cherry-pick 到现有 stable 分支。
  • 跟进缺口:Rafael 回复正文几乎是清一色引用块,没看到他实际给的 Acked-by/Reviewed-by
    或合并决定,需关注后续正式回信。

版本变化

  • v1 -> v2:澄清 32 位无符号算术的原因;将 rate_limit_us 转为 s64 以匹配
    freq_update_delay_ns;增加 Zhongqiu 的 Reviewed-by。
  • v2 -> v3:仅为 sugov_update_rate_limit_us() 添加说明性注释,解释为什么乘法前必须
    做强制类型转换,没有逻辑变更。

一句话总结

schedutil 在 32 位平台上把 rate_limit_us * 1000 按 32 位算术算出后再赋给 s64
导致大延迟溢出成几百纳秒;补丁抽出 sugov_update_rate_limit_us() 强制扩到 s64
在 sysfs 与启动两条路径上一并修复,并抄送 stable。