0/6 已展开

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()

  1. 调用前记录 was_stopped = tick_nohz_tick_stopped()
  2. 调用 tick_nohz_idle_stop_tick() 真正尝试停 tick。
  3. 调用后再次检查 tick_nohz_tick_stopped();只有当这次调用把 tick 从未停止切到停止,才额外发起一次 cpufreq_update_util(rq, SCHED_CPUFREQ_IDLE)
  4. 进入 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 期间白白耗电。