4. CPUIdle 深度解析:C-States、Governor、Latency 与 Residency

省电这件事,到了 CPU 这里,最核心的手段就是让它「睡过去」。但睡觉也分深浅,浅睡容易被唤醒,省电效果差;深睡省电多,但进出都慢。怎么平衡?这就是 CPUIdle 子系统要解决的问题。

我个人习惯把 CPUIdle 比作一个酒店的门禁系统。CPU 是住客,C-State 就是不同楼层的房间。Governor 是前台经理,他根据住客什么时候回来(预测空闲时长),决定让住客去几楼休息。Latency 是电梯速度,Residency 是住客至少得待多久才划算。

嗯,咱们今天就一层层拆开看。

4.1 C-States:CPU 的睡眠等级

C-States 是 ACPI 标准定义的 CPU 电源状态。C0 是运行态,C1 到 Cn 是各种睡眠态。数字越大,睡得越死,功耗越低,但唤醒延迟也越高。

状态 名称 功耗 唤醒延迟 关键行为
C0 Active 高 0 CPU 正常执行指令
C1 Halt 中低 ~1µs 停止指令执行,保持缓存一致性
C2 Stop-Clock 低 ~10µs 关闭部分时钟树,缓存保持
C3 Sleep 极低 ~100µs 关闭 PLL、L2 缓存可能掉电
注意: 实际硬件上的 C-State 数量和命名因架构而异。ARM 的 WFI/WFE 通常对应 C1,而像 i.MX 或 Rockchip 的深度睡眠模式可能叫 C2/C3。不要只看名字,要看 Latency 和 Residency 的具体数值。

4.2 Governor:谁来决定睡多深?

Governor 是 CPUIdle 的决策引擎。它根据当前 CPU 的空闲预测,选择一个合适的 C-State。Linux 内核里主要有两个 Governor:Menu 和 TEO。

4.2.1 Menu Governor(经典方案)

Menu Governor 是内核长期以来的默认选择。它的核心思路是:预测下一次中断何时到来。

  • 预测模型:基于历史中断间隔,使用一个简单的指数加权平均。
  • 动态修正:如果预测偏差大,它会主动降低预测值,避免选到过深的 C-State。
  • IO 感知:如果当前 CPU 上有密集的 IO 中断,它会倾向于选浅睡眠,因为 IO 中断来得快。

我在项目中遇到过一个问题:某个网卡驱动在中断处理里做了太多事,导致 Menu Governor 总是预测不准,CPU 频繁进出 C3,功耗反而比一直跑 C1 还高。后来调整了驱动的 NAPI 预算,才把问题解决。

4.2.2 TEO Governor(Timer Oriented)

TEO 是后来加入的 Governor,专门针对定时器驱动的场景做了优化。它比 Menu 更激进,也更简单。

  • 核心逻辑:只看下一个定时器的到期时间,不再做复杂的预测。
  • 优势:在定时器密集的场景下(比如音频播放、视频渲染),TEO 的决策更准确,延迟更低。
  • 劣势:对 IO 中断的适应性不如 Menu,如果系统里中断很随机,TEO 可能会选错。
我的建议: 如果你的产品是纯计算型(比如跑 AI 推理),用 Menu。如果是多媒体播放型(定时器多),试试 TEO。我最近一个项目从 Menu 切到 TEO,待机功耗降了 8%,而且没出现卡顿。

4.3 Latency 与 Residency:两个关键指标

选 C-State 不是越深越好。有两个硬指标必须满足:

  • Latency(延迟):从发出唤醒信号到 CPU 恢复执行第一条指令的时间。单位通常是 µs。
  • Residency(驻留时间):进入该 C-State 后,至少需要待多久才能「回本」。如果待的时间比 Residency 还短,那省的电还不够进出消耗的。

说白了,Governor 的决策逻辑就是:预测空闲时间 > Residency + 安全余量,才允许进入该 C-State。

C-State Latency (µs) Residency (µs) 省电收益
C1 1 2 低
C2 10 100 中
C3 100 1000 高
关键点: 如果系统里中断频率很高(比如 1ms 一次),那 C3 的 Residency 是 1000µs,你根本进不去。强行进去只会导致频繁进出,功耗反而更高。这就是为什么有时候「浅睡」比「深睡」更省电。

4.4 核心流程:CPUIdle 的决策路径

下面这张图展示了 CPUIdle 从 idle 触发到选择 C-State 的完整流程。你可以看到 Governor 是如何结合预测、Latency 和 Residency 来做决策的。

CPUIdle 决策流程图 CPUIdle 决策流程 CPU 空闲 (idle) Governor 预测空闲时长 遍历可用 C-States 检查 Latency & Residency 进入选定 C-State 说明: 1. Governor 预测空闲时长 2. 从浅到深遍历 C-States 3. 满足 Residency 才进入

4.5 实战:如何查看和调整 CPUIdle

在嵌入式 Linux 系统上,你可以通过 sysfs 直接查看和调整 CPUIdle 的行为。我个人习惯先看当前状态,再调参数。

4.5.1 查看当前 C-State 使用情况

# 查看 CPU0 的 C-State 统计
cat /sys/devices/system/cpu/cpu0/cpuidle/state*/name
cat /sys/devices/system/cpu/cpu0/cpuidle/state*/time
cat /sys/devices/system/cpu/cpu0/cpuidle/state*/usage

time 是累计驻留时间(单位 µs),usage 是进入次数。如果某个深睡眠状态的 usage 很少,说明它几乎没被用到。

4.5.2 切换 Governor

# 查看当前 Governor
cat /sys/devices/system/cpu/cpuidle/current_governor

# 切换为 TEO
echo "teo" > /sys/devices/system/cpu/cpuidle/current_governor
注意: 切换 Governor 是全局操作,会影响所有 CPU。建议在系统空闲时操作,避免影响正在运行的任务。

4.5.3 禁用某个 C-State(调试用)

# 禁用 CPU0 的 C3 状态
echo 1 > /sys/devices/system/cpu/cpu0/cpuidle/state3/disable

我曾经在调试一个音频卡顿问题时,发现 C3 的进出延迟导致音频缓冲区 underrun。临时禁掉 C3,问题立刻消失。后来通过调整 Residency 阈值,才在省电和性能之间找到平衡。

4.6 避坑指南

  • 不要盲目追求深睡眠:C3 省电,但如果中断频繁,进出开销反而更大。先看 /proc/interrupts 了解中断频率。
  • 注意外设的 Latency 要求:有些外设(比如 USB、SDIO)有严格的唤醒延迟要求。如果 CPU 睡得太深,外设可能超时。可以通过 pm_qos 接口设置 CPU 的延迟约束。
  • 多核系统的陷阱:在 SMP 系统上,一个核进入深睡眠可能会影响其他核的缓存一致性。ARM 的 big.LITTLE 架构尤其要注意。
我的经验: 调试 CPUIdle 时,先用 trace-cmd 抓一下 cpu_idle 事件,看看实际进出情况。很多时候,你以为 CPU 在睡 C3,其实它一直在 C1 和 C2 之间反复横跳。

CPUIdle 是低功耗设计的基石。理解了 C-States、Governor、Latency 和 Residency 这四者的关系,你就能精准控制 CPU 的睡眠行为。下一层,我们会看看 CPUFreq 如何与 CPUIdle 协同工作,实现更精细的功耗管理。