CPU空闲管理基础:cpuidle子系统架构、C-state与P-state的区别、idle governor的工作原理

各位同学,咱们今天聊聊CPU空闲管理。说白了,就是CPU没事干的时候怎么省电。

我刚开始接触这块时,觉得挺简单的——CPU闲着就让它睡呗。但实际做项目才发现,这里面的门道多着呢。你让它睡得太深,醒来就慢;睡得太浅,又省不了多少电。这个平衡,就是cpuidle子系统要解决的问题。

cpuidle子系统架构

先看看整体架构。cpuidle子系统在内核里的位置,我习惯把它分成三层:

  • Governor层:负责决策,判断该进哪个idle状态
  • Driver层:负责执行,告诉硬件怎么睡
  • Core层:负责协调,把governor和driver串起来

嗯,这里要注意,governor和driver是分开的。为什么这么设计?

因为不同硬件平台支持的idle状态不一样。比如Intel的CPU有C1、C1E、C6等,ARM的CPU可能有WFI、WFI+retention、powerdown等。governor是通用的算法,driver是平台相关的实现。这样换平台时,只需要换driver,governor不用动。

核心数据结构:每个CPU都有一个cpuidle_device,记录这个CPU当前的状态。每个idle状态对应一个cpuidle_state,包含退出延迟、功耗等信息。

我在项目中遇到过一个问题:某个ARM平台的CPU,进idle后竟然不响应中断了。查了半天,发现是driver里某个寄存器没配对。所以说,driver这层最容易出幺蛾子。

C-state与P-state的区别

这两个概念,我见过很多工程师搞混。咱们一次说清楚。

特性 C-state P-state
控制什么 CPU是否执行指令 CPU的运行频率/电压
状态含义 C0=运行,C1=暂停,C2=更深睡眠... P0=最高频,P1=次高频...
切换代价 微秒到毫秒级 微秒级
省电效果 可关闭时钟甚至电源 降低频率和电压
谁管理 cpuidle子系统 cpufreq子系统

说白了,C-state是「睡多深」,P-state是「跑多快」。两者是正交的,但实际中会互相影响。

举个例子:CPU在C0状态时,可以调整P-state来省电。但进了C6状态,CPU已经断电了,P-state就没意义了。你想想看,人都睡着了,还调什么呼吸频率?

避坑指南:我曾经在调试功耗时,发现CPU明明进了C6,功耗却不降。后来发现是某个外设没关,导致CPU被频繁唤醒。所以检查idle状态时,一定要看实际驻留时间,别只看进了哪个状态。

idle governor的工作原理

governor的核心任务就一个:预测CPU接下来会空闲多久,然后选一个合适的idle状态。

内核里目前有两个governor:

  • menu governor:默认的,用于大部分场景
  • ladder governor:用于实时性要求高的场景

我主要讲menu governor,因为用得最多。

它的工作流程是这样的:

  1. CPU要进idle时,governor先看最近的idle历史
  2. 根据历史数据,预测这次会空闲多久
  3. 从可用的idle状态中,选一个退出延迟小于预测时间的
  4. 同时考虑功耗和延迟的平衡

这里有个关键点:预测不准怎么办?

嗯,menu governor用了指数加权移动平均(EWMA)来做预测。说白了,就是给最近的数据更高权重,老数据慢慢衰减。这样能快速响应负载变化。

核心代码片段

// menu governor的预测逻辑(简化版)
static int menu_select(struct cpuidle_driver *drv, struct cpuidle_device *dev) {
    // 获取最近的idle时长
    unsigned int predicted_us = get_typical_interval(dev);
    
    // 遍历所有idle状态,选最合适的
    for (i = 0; i < drv->state_count; i++) {
        if (drv->states[i].exit_latency > predicted_us)
            break;
        // 还要考虑功耗收益
        if (drv->states[i].power_savings > threshold)
            break;
    }
    return i - 1;
}

我曾经遇到过一个问题:某个场景下,CPU频繁在C1和C2之间切换,功耗反而更高了。为什么?因为每次切换都有开销,如果空闲时间太短,进C2省的电还不够切换成本。

后来我加了个判断:如果预测空闲时间小于某个阈值,干脆不进idle,直接忙等。你想想看,有时候「不做事」反而是最好的策略。

注意事项:menu governor的预测是基于历史数据的。如果负载突然变化(比如用户突然点了一下屏幕),预测就会不准。这时候CPU可能会选一个太深的idle状态,导致响应变慢。实时性要求高的场景,建议用ladder governor。

最后说一句,idle governor的调优,说白了就是找「省电」和「响应」的平衡点。没有银弹,只能根据具体场景来调。我一般会先跑一遍workload,看实际驻留时间分布,再调整governor的参数。

好了,这一章就到这里。下一章咱们聊聊实际调优时,怎么用tracepoint和powertop来定位问题。