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 缓存可能掉电 |
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 可能会选错。
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 | 高 |
4.4 核心流程:CPUIdle 的决策路径
下面这张图展示了 CPUIdle 从 idle 触发到选择 C-State 的完整流程。你可以看到 Governor 是如何结合预测、Latency 和 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
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 架构尤其要注意。
trace-cmd 抓一下 cpu_idle 事件,看看实际进出情况。很多时候,你以为 CPU 在睡 C3,其实它一直在 C1 和 C2 之间反复横跳。
CPUIdle 是低功耗设计的基石。理解了 C-States、Governor、Latency 和 Residency 这四者的关系,你就能精准控制 CPU 的睡眠行为。下一层,我们会看看 CPUFreq 如何与 CPUIdle 协同工作,实现更精细的功耗管理。