0/2 已展开

LLM 分析

cpufreq/schedutil:修复 32 位 rate_limit_us 乘法回绕

系列概况

  • 标题[PATCH v2] cpufreq: schedutil: Fix rate limit overflow
  • 作者:Hui Su <sh_def@163.com>
  • 版本:v2(v1 Message-ID 为 20260805143942.805176-1-sh_def@163.com
  • 规模:单文件改动,kernel/sched/cpufreq_schedutil.c,新增 8 行、删除 2 行- 修改文件kernel/sched/cpufreq_schedutil.c
  • 代码统计(v1 diffstat):1 file changed, 8 insertions(+), 2 deletions(-)
  • Message-ID(首封):20260806072656.1386351-1-sh_def@163.com
  • 完整性:patch diff 完整、commit message 完整、Fixes / Cc stable / Signed-off-by / Reviewed-by 签名齐全;包含 v1->v2 changelog

补丁目的

schedutil governor 在把 sysfs 中的 rate_limit_us(微秒)换算成 freq_update_delay_ns(纳秒)时,直接执行 rate_limit_us * NSEC_PER_USECrate_limit_usunsigned intNSEC_PER_USEC1000L

在 32 位系统上,C 语言的整数提升规则会让乘法按 unsigned int * long 进行,结果先截断到 32 位再赋给 s64 freq_update_delay_ns。当 rate_limit_us 大于约 4294967(即 UINT_MAX / 1000)时,乘积会发生回绕:例如写入 4294968,原本期望约 4.29 s 的延迟,会变成 704 ns,导致 schedutil 频率更新频率被异常放大约 600 万倍。

本补丁把换算封装成 sugov_update_rate_limit_us(),先把 rate_limit_us 强转为 s64,再与 NSEC_PER_USEC 相乘,从而保证 64 位精度。

旧流程的问题

旧代码在两个地方直接换算:

  1. rate_limit_us_store():用户写 sysfs 后立刻更新延迟。
  2. sugov_start():governor 启动时初始化延迟。

两处的表达式都是 rate_limit_us * NSEC_PER_USEC。在 32 位平台上,由于乘法左操作数是 unsigned intNSEC_PER_USEC 虽然是 long,但提升规则只把"较小类型"提升到"较大类型"的宽度,结果仍按 32 位 unsigned 解释,然后再以 s64 接收,低32 位的回绕被无声忽略。

新流程

抽出公共辅助函数 sugov_update_rate_limit_us(),把 rate_limit_us(s64) 拓宽,再乘 NSEC_PER_USEC,结果自然落在 64 位;sysfs store 与 sugov_start() 两个调用点都改用 helper。

static void sugov_update_rate_limit_us(struct sugov_policy *sg_policy)
{
    sg_policy->freq_update_delay_ns =
        (s64)sg_policy->tunables->rate_limit_us * NSEC_PER_USEC;
}

两个调用点:

/* rate_limit_us_store() 中 */
sugov_update_rate_limit_us(sg_policy);

/* sugov_start() 中 */
sugov_update_rate_limit_us(sg_policy);

关键实现

  • 触发时机sg_policy->tunables->rate_limit_us 变化(sysfs 写)以及 governor 启动初始化。
  • 核心修复点:在乘法前先把 unsigned int 提升到 s64,与 NSEC_PER_USEClong)相乘得到 64 位结果,再赋给 s64 字段,全程不再发生 32 位截断。
  • 修复范围:同一 helper 覆盖 sysfs 写入与 governor 启动两个入口,避免再有第三处直接相乘漏改。
  • 函数命名:与既有 sugov_should_update_freq 等 sugov 风格一致,便于阅读与扩展。

类比

rate_limit_us * NSEC_PER_USEC 想象成往一个老式 32 位水表里灌水:每灌 1 us 就滴出 1 mL,但水表只能显示 4 升以内的总量;只要输入超过 4294967 us(约 4.29 s),水表就转一圈回到 704 mL,读数与实际灌入量完全脱节。补丁相当于把水表换成64 位的电子流量计:先把 us 在计数器端拓宽,再相乘,于是无论灌多久都和实际灌入量一致。

ASCII 流程图

Old flow (32-bit unsigned wrap):

  sysfs write                sugov_start()
       |                          |
       v v
  rate_limit_us (u32)       rate_limit_us (u32)
       |                          |
       +----------+   +-----------+
 v   v u32 * 1000L   (32-bit unsigned mul)
                    |
                    v
        freq_update_delay_ns (s64, low-32 wrap kept)


New flow (s64 widening):

  rate_limit_us (u32) --(s64)--> s64 --- * NSEC_PER_USEC ---> s64
                       sugov_update_rate_limit_us()
 |
                                  v
                  freq_update_delay_ns (no wrap)


Call sites unified through helper:

  rate_limit_us_store() --+
 |
  sugov_start()           --+--> sugov_update_rate_limit_us(sg_policy)

Highlight:风险与注意点

  • 64 位平台几乎不会触发,但只要运行 32 位内核(armv7、x86 pae/non-pae 等)且被配置较大 rate_limit_us,schedutil 会以毫秒级频率反复评估频率,造成调度抖动与功耗浪费;建议对该路径加 KUnit 用例或内核启动参数检查。
  • rate_limit_us 上限本身是否受 sysfs 约束?若没有用户态上限校验,仅靠强转只是"治标";根因层面可以加 kstrtoul 范围校验,把上限设到 INT_MAX / 1000 附近。
  • 辅助函数命名 sugov_update_rate_limit_us 与既有 sugov_should_update_freq 等风格一致,复用良好;后续如再增加 rate_limit_us 的引用点,应统一走该 helper,避免再漏一处。
  • Rafael 的回复未给出 review tag 或结论,需要关注后续是否 ACKed 或被收进 cpufreq 维护者队列。

版本变化(v1 -> v2)

  • 进一步解释"为什么 32 位系统上会使用 32 位无符号算术",便于读者不必再去翻 C 标准;
  • 把 helper 内 rate_limit_us 显式强转为 s64,与目标字段 freq_update_delay_ns 类型对齐;
  • 加上 Zhongqiu Han 的 Reviewed-by 标签。

一句话总结

schedutil 在 32 位平台上做 rate_limit_us * NSEC_PER_USEC 会因 32 位无符号回绕而把延迟算成几百纳秒,本补丁用统一的 helper 在乘法前把 rate_limit_us 拓宽到 s64,避免 schedutil 频率更新频率被异常放大。

逐封邮件中文翻译

  • mail #1(Hui Su, [PATCH v2]):见上文"补丁目的 / 关键实现"对应位置,已包含完整 commit message、Fixes/Cc/SOB/Reviewed-by 签名与 v1->v2 changelog 的中文概述;diff 内的代码行未逐行翻译。
  • mail #2(Rafael J. Wysocki, 回复):仅引用并复读了 v2 整段 commit message 与 v1 diffstat,未给出独立评审意见;属于 maintainer 例行"留底引用",等后续回复。