sched discussion
[PATCH v2] cpufreq: schedutil: Fix rate limit overflow
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_USEC。rate_limit_us 是 unsigned int,NSEC_PER_USEC 是 1000L。
在 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 位精度。
旧流程的问题
旧代码在两个地方直接换算:
rate_limit_us_store():用户写 sysfs 后立刻更新延迟。sugov_start():governor 启动时初始化延迟。
两处的表达式都是 rate_limit_us * NSEC_PER_USEC。在 32 位平台上,由于乘法左操作数是 unsigned int、NSEC_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_USEC(long)相乘得到 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 例行"留底引用",等后续回复。