0/2 已展开

LLM 分析

cpufreq schedutil:修复 rate limit 数值溢出

系列概况

  • 标题:[PATCH] cpufreq: schedutil: Fix rate limit overflow
  • 作者:Hui Su sh_def@163.com
  • 版本:v1(单封 patch,无 series 标注)
  • 规模:1 个 patch,1 个文件,约 +7 / -2
  • 修改文件:kernel/sched/cpufreq_schedutil.c
  • 代码统计:新增 1 个 helper 函数(5 行),在 sysfs store 与 governor start 各替换 1 行
  • Message-ID:20260805143942.805176-1-sh_def@163.com
  • 完整性:commit message 与 hunk 完整;带 Fixes: tag 指向引入 commit 9bdcb44e391d

补丁目的

schedutil governor 用 freq_update_delay_ns 控制两次 cpufreq 更新之间的最小间隔。该值由 sysfs 中的 rate_limit_us(单位微秒)乘以 NSEC_PER_USEC 得到。

rate_limit_usunsigned int(32 位),在 32 位平台上 rate_limit_us * NSEC_PER_USEC 会按 32 位算术执行,结果溢出回卷后才被赋给 64 位的 freq_update_delay_ns,导致实际节流间隔远小于用户配置。

补丁抽出 sugov_update_rate_limit_us(),在乘法之前把值 widen 到 u64,并在 sysfs store 与 governor start 两条路径上统一调用。

旧流程的问题

rate_limit_us    (u32) = 4294968
        |
        v
    * NSEC_PER_USEC         <-- 32-bit multiply
        |
        v
  wrap to 4294968000 mod 2^32 = 704
        |
        v
freq_update_delay_ns (u64) = 704   <-- stored delay (too small)

schedutil 之后会以 704 ns 的间隔反复触发 cpufreq 更新,频率调节几乎等同于无节流,远超用户配置的 4.29 秒。

新流程

rate_limit_us    (u32) = 4294968
        |
        v
    (u64) cast
        |
        v
    * NSEC_PER_USEC         <-- 64-bit multiply
        |
        v
freq_update_delay_ns (u64) = 4294968000   <-- correct delay

通过 helper 集中实现"先 widen 再相乘",sysfs 写路径 rate_limit_us_store() 与 governor 启动路径 sugov_start() 走同一份逻辑,避免遗漏或将来出现分支。

Patch 概览

新增 helper:

static void sugov_update_rate_limit_us(struct sugov_policy *sg_policy)
{
    sg_policy->freq_update_delay_ns =
        (u64)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);

关键实现

关键就是 cast 的位置:(u64) 必须放在乘法左操作数上。GCC 对 u32 * int 默认按 32 位算,乘完之后再赋给 u64 已经"晚了";先 widen 到 u64 再乘才能保留高位。

新 helper 的另一个价值是消除重复:旧代码在 store 和 start 两处各自完成换算,将来如果再加新的"设置 rate limit 入口",仍可能漏写 widen。集中到 helper 后只需要检查 helper 的调用点即可。

+---------------------+        +---------------------------+
| rate_limit_us_store | | sugov_start         |
| (sysfs write path)  |        | (governor init path)     |
+----------+----------+        +-------------+-------------+
           |                                 |
           v v
       +---+---------------------------------+---+
       | sugov_update_rate_limit_us()     |
       |  (u64)rate_limit_us * NSEC_PER_USEC |
       +-------------------+---------------------+
                           |
                           v
              freq_update_delay_ns (u64)

两条入口最终都通过同一个 helper 写入 freq_update_delay_ns,避免在32 位平台上各自发生溢出。

类比

rate_limit_us 想成水表上的"刻度",最大 4294967。NSEC_PER_USEC 是把刻度换算成实际水流量的乘数。

  • 旧流程:32 位水表(最大读数 4294967295),刻度超过 ~4.3M 后水表直接回零,最后记录的水量只剩"嘀嗒"那么少 —— 你以为关小了水龙头,结果水龙头是满开的。
  • 新流程:把水表换成 64 位版本,再大的刻度也不会溢出,水表如实记录水量。

Highlight:风险与注意点

  • 仅 32 位平台受影响:64 位内核里 u32 * int 自然提升为 64 位,所以这个 bug 在主流 server/手机 SoC 上看不到现象;但 armv7、部分 RISC-V SoC、m68k、xtensa 等 32 位系统都中招。
  • 没有同步加 rate_limit_us 上限校验:用户写一个极大值(比如 4000000,约 4 秒)现在不会再溢出,但会得到一个非常长的"看似安全"节流间隔,patch 没动这一块。
  • 是否还有其他位置直接写 freq_update_delay_ns 需要复查:例如未来如果支持 per-policy 的 override、或额外的 sysfs 节点,要确保它们也走 helper。
  • Qualcomm maintainer Zhongqiu Han 的回复确认修复方向,但本次抓取的 reply body 在 @@ -606 行处被截断,完整审阅意见需要回到 lore 原邮件跟进。

一句话总结

抽出 sugov_update_rate_limit_us(),在 rate_limit_us * NSEC_PER_USEC 之前先 widen 到 u64,并在 sysfs store 与 sugov_start 两条路径统一调用,修复 32 位系统上 schedutil governor 频率更新间隔因 32-bit 整数乘法回卷而失效的 bug。