sched discussion
[PATCH] cpufreq: schedutil: Fix rate limit overflow
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 指向引入 commit9bdcb44e391d
补丁目的
schedutil governor 用 freq_update_delay_ns 控制两次 cpufreq 更新之间的最小间隔。该值由 sysfs 中的 rate_limit_us(单位微秒)乘以 NSEC_PER_USEC 得到。
rate_limit_us 是 unsigned 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。