sched discussion
[PATCH] sched/cpufreq: Reevaluate frequency before tickless idle
LLM 分析
sched/cpufreq:在 tickless idle 之前重新评估频率
系列概况
- 标题:[PATCH] sched/cpufreq: Reevaluate frequency before tickless idle
- 作者:Christian Loehle christian.loehle@arm.com
- 版本:单封 PATCH,无 v2/v3
- 规模:3 个文件,+32 / -3 行
- 修改文件:
- include/linux/sched/cpufreq.h
- kernel/sched/cpufreq_schedutil.c
- kernel/sched/idle.c
- Message-ID:20260824131842.2443385-1-christian.loehle@arm.com
- 完整性:除作者与 Hongyan Xia、Vincent Guittot 的回复外,回复正文未带新讨论内容;可见部分只引用了原 patch
补丁目的
当 runqueue 进入 idle 并触发 sugov_hold_freq() 时,schedutil 可能会因为 UCLAMP_MIN 把频率维持在较高的水位。
如果 cpuidle 紧接着停掉了 tick,那么之后在没有 tick 的 idle 阶段中不会再有新的 utilization 更新事件;CPU 用 WFI 等待中断时,电压就一直停在偏高的位置,整段 idle 都会白白耗电。
补丁的目的是:在 idle 路径真正把 tick 从“运行”切到“停止”的瞬间,发起一次额外的 cpufreq 更新,让 schedutil 看到最新(已经空闲)的 utilization,进而把频率拉回合理的最低值;tick 没有切换到 stopped 的情况下保持原 hold 行为。
旧流程的问题
旧路径只调用一次 tick_nohz_idle_stop_tick(),schedutil 完全依赖常规 tick/唤醒事件去刷新频率。一旦 idle 进入 tickless 状态且没有事件触发,就没有第二次机会下调频率,UCLAMP_MIN 保留的高频会一直撑到下一次 wakeup。
新流程
新路径把 tick_nohz_idle_stop_tick() 包进新的 idle_stop_tick():
- 调用前记录
was_stopped = tick_nohz_tick_stopped()。 - 调用
tick_nohz_idle_stop_tick()真正尝试停 tick。 - 调用后再次检查
tick_nohz_tick_stopped();只有当这次调用把 tick 从未停止切到停止,才额外发起一次cpufreq_update_util(rq, SCHED_CPUFREQ_IDLE)。 - 进入 cpufreq 的路径后,
sugov_should_update_freq()看到SCHED_CPUFREQ_IDLE标志,把need_freq_update置位,跳过 rate-limit,给出真正的下调。
Patch 概览
include/linux/sched/cpufreq.h | 1 +
kernel/sched/cpufreq_schedutil.c | 5 ++++-
kernel/sched/idle.c | 29 +++++++++++++++++++++++++++--
三处改动互相配合:头文件新增标志位;schedutil 解释该标志;idle 路径产生该标志。
关键实现
/* include/linux/sched/cpufreq.h */
#define SCHED_CPUFREQ_IOWAIT (1U << 0)
+#define SCHED_CPUFREQ_IDLE (1U << 1)
/* kernel/sched/cpufreq_schedutil.c */
static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
{
...
} else if (sg_policy->need_freq_update) {
- /* ignore_dl_rate_limit() wants a new frequency to be found. */
+ /* A forced update needs a new frequency to be found. */
/* kernel/sched/cpufreq_schedutil.c - sugov_update_single_common */
+ if (flags & SCHED_CPUFREQ_IDLE)
+ sg_cpu->sg_policy->need_freq_update = true;
/* kernel/sched/idle.c - 新增 idle_stop_tick() */
static void idle_stop_tick(void)
{
#ifdef CONFIG_CPU_FREQ
bool was_stopped = tick_nohz_tick_stopped();
#endif
tick_nohz_idle_stop_tick();
#ifdef CONFIG_CPU_FREQ
if (!was_stopped && tick_nohz_tick_stopped()) {
struct rq *rq = this_rq();
struct rq_flags rf;
rq_lock(rq, &rf);
update_rq_clock(rq);
cpufreq_update_util(rq, SCHED_CPUFREQ_IDLE);
rq_unlock(rq, &rf);
}
#endif
}
把 cpuidle_idle_call() 与 idle_call_stop_or_retain_tick() 中对 tick_nohz_idle_stop_tick() 的直接调用替换为 idle_stop_tick(),集中改写入口。
类比
把 CPU 想成一辆自动挡汽车,UCLAMP_MIN 是司机把油门“锁”在某个最低转速。车子进停车位熄火(idle)之前,行车电脑本来应该在熄火瞬间把转速降下来,但旧流程只在司机熄火后信任“下一次发动时会重新调速”。
问题是:tickless idle 期间这台车根本不重新发动,电压就停在高转速。补丁相当于在熄火瞬间额外按一次“怠速校准”按钮,告诉行车电脑“现在没人踩油门”,强制把转速拉回真正的怠速。!was_stopped && tick_nohz_tick_stopped() 这个条件就像只在真正完成熄火动作的那一次才按按钮,避免每次停车都重复操作。
Highlight:风险与注意点
- 锁顺序:
idle_stop_tick()在 idle 上下文内对this_rq()走rq_lock,要确认与 cpuidle 驱动的锁顺序不引入新死锁。 CONFIG_CPU_FREQ关闭时整段逻辑被#ifdef包住,行为完全等同旧代码;但要在 ARM 的 WFI/WFE 场景下确认 cpuidle 不依赖 tick 频率调整。SCHED_CPUFREQ_IDLE复用了need_freq_update,意味着这条路径会强制过ignore_dl_rate_limit(),需要关注短时间内多次进 idle 时的频率抖动。- 触发条件是“本次调用把 tick 从 running 切到 stopped”,已 tickless 时再调用一次不会重复触发,更新开销是有界的。
- 后续观察:Hongyan Xia 与 Vincent Guittot 的回复正文在数据中只保留了引用块,需要关注 v2 是否会回应他们关于 cpufreq policy 范围(single-policy 限制)以及
ignore_dl_rate_limit副作用的反馈。
ASCII 流程图
+------------------+ rq idle +---------------------+
| Running tasks | -----------------> | sugov_hold_freq() |
+------------------+ +---------------------+
|
v
+---------------------+
| cpuidle_idle_call() |
+---------------------+
|
v
+------------------------------------+
| idle_stop_tick() (NEW) |
| 1. was_stopped = tick_stopped() |
| 2. tick_nohz_idle_stop_tick() |
| 3. if (!was && now_stopped): |
| cpufreq_update_util( |
| rq, SCHED_CPUFREQ_IDLE) |
+------------------------------------+
|
v
+-------------------------------+
| sugov_should_update_freq() |
| need_freq_update = true |
| -> skip rate limit |
+-------------------------------+
|
v
+-------------------------------+
| frequency lowered, CPU in WFI |
+-------------------------------+
+-----------+ +---------------------+ +-----------------+
| UCLAMP_MIN| -----> | sugov holds freq | -----> | idle, tick on |
+-----------+ +---------------------+ +-----------------+
|
old: tick stops -> freq stays HIGH (waste) v
+-----------------+
| idle, tick off |
| freq still HIGH |
+-----------------+
new: tick transition -> forced update
+-----------------+
| freq lowered |
| WFI low voltage |
+-----------------+
与其他相关 patch 系列的关联
sugov_hold_freq()与sugov_iowait_boost()同属 schedutil 的频率决策面;本补丁通过新增SCHED_CPUFREQ_IDLE标志扩展该决策面,与已有SCHED_CPUFREQ_IOWAIT形成对称。- 与 Vincent Guittot 维护的 schedutil 与 cpuidle 交互讨论历史一致:tickless idle 与 cpufreq 的同步是长期关注点。
- 与
ignore_dl_rate_limit()路径相关:补丁借由need_freq_update触发该路径,应观察是否需要在 v2 增加注释解释 SCHED_CPUFREQ_IDLE 与 dl-rate-limit 的关系。
一句话总结
在 idle 路径停 tick 的瞬间补一次 cpufreq 更新,让 schedutil 在进入 tickless idle 前下调 UCLAMP_MIN 维持的高频,避免 WFI 期间白白耗电。