3、Linux电源管理框架:cpuidle、cpufreq与devfreq子系统概述
做嵌入式Linux功耗优化,你迟早得跟这三个子系统打交道。cpuidle、cpufreq、devfreq,它们仨构成了Linux内核电源管理的核心骨架。
我刚开始接触这块时,也觉得很乱。什么C-state、P-state、OPP,一堆缩写。后来调了几个项目才明白——说白了,它们解决的是同一个问题:系统不忙的时候,怎么省电。只不过省电的维度不同。
- cpuidle:管CPU空闲时干啥。没事做就睡,睡多深由它定。
- cpufreq:管CPU跑多快。活多就提频,活少就降频。
- devfreq:管外设(DDR、GPU、总线)的频率和电压。跟cpufreq思路类似,但管的是设备。
这三者配合好了,系统功耗能降一大截。配合不好……嗯,我见过一个项目,cpuidle和cpufreq策略冲突,CPU频繁进出深度睡眠,功耗反而比一直跑着还高。
3.1 cpuidle:CPU空闲时的省电之道
cpuidle的核心逻辑很简单:当CPU没有任务可执行时,进入一个低功耗状态。这个状态在ARM里叫WFI(Wait For Interrupt),在x86里叫C-state。
但问题来了——睡多深?睡多久?醒来要花多少代价?
cpuidle把空闲状态分成多个等级,每个等级对应不同的功耗和唤醒延迟。等级越高,功耗越低,但唤醒时间越长。
| 空闲状态 | 功耗 | 唤醒延迟 | 典型场景 |
|---|---|---|---|
| C0(运行) | 高 | 0 | CPU正在执行指令 |
| C1(Halt) | 中 | ~1μs | 短时空闲,比如等待锁释放 |
| C2(Stop-Clock) | 较低 | ~10μs | 稍长空闲,比如等待I/O |
| C3(Sleep) | 低 | ~100μs | 较长时间空闲,比如用户无操作 |
cpuidle的governor(策略器)负责决定进哪个状态。内核默认有两个:
- menu governor:根据历史空闲时间预测下次空闲时长,选最合适的等级。我一般用这个。
- ladder governor:逐级进入,先C1再C2再C3。保守但稳定。
关键点:cpuidle的调优核心是平衡功耗节省和唤醒延迟。如果系统对响应时间敏感(比如音频播放),就别进太深的睡眠。
我的经验:在调试cpuidle时,先看 /sys/devices/system/cpu/cpu0/cpuidle/state*/ 下的文件。每个state目录里有 name、time、usage、latency,能帮你快速定位哪个状态没生效。
3.2 cpufreq:动态调频调压(DVFS)
cpufreq管的是CPU的运行频率和电压。任务重就提频,任务轻就降频。这个机制叫DVFS(Dynamic Voltage and Frequency Scaling)。
cpufreq有几种governor:
| Governor | 行为 | 适用场景 |
|---|---|---|
| performance | 一直跑最高频 | 性能优先,不管功耗 |
| powersave | 一直跑最低频 | 省电优先,不管性能 |
| ondemand | 负载高就提频,负载低就降频 | 通用场景,响应快 |
| conservative | 类似ondemand,但频率变化更平滑 | 对频率抖动敏感的场景 |
| schedutil | 基于调度器负载反馈调频 | 新内核推荐,更精准 |
我个人习惯用 schedutil。它直接跟CFS调度器挂钩,负载感知更准。在4.7以后的内核里,schedutil已经是主流选择。
避坑指南:我曾经在一个项目里同时用了cpufreq的ondemand和cpuidle的menu governor,结果系统在低负载时频繁调频,功耗反而高了。后来换成schedutil + menu,问题解决。记住——调频和空闲策略要协同,别各玩各的。
查看当前cpufreq策略:
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies
3.3 devfreq:外设的DVFS
cpufreq只管CPU,那GPU、DDR、总线、存储控制器呢?这些设备也有动态调频调压的需求。devfreq就是干这个的。
devfreq的模型跟cpufreq很像:
- 设备:注册一个devfreq设备,指定支持的频率/电压表(OPP表)。
- governor:决定什么时候调频。常见的有
simple_ondemand、userspace、performance。 - 调频接口:通过
clk_set_rate或 regulator 接口改变频率和电压。
举个例子,DDR频率调节:
# 查看DDR的devfreq设备
ls /sys/class/devfreq/
# 查看当前governor
cat /sys/class/devfreq/1d84000.dmc/governor
# 手动设置频率(userspace模式下)
echo 400000000 > /sys/class/devfreq/1d84000.dmc/userspace/set_freq
核心区别:cpufreq管CPU,devfreq管其他所有需要DVFS的设备。两者机制相似,但设备树配置和驱动接口不同。
3.4 三者的协作关系
这三个子系统不是孤立的。它们共享同一个硬件资源——比如电压域。CPU和DDR可能共用一个电源域,CPU降频时DDR也得跟着降。
我画了一张图,帮你理清它们的关系:
从图上你能看到:
- cpuidle管CPU空闲时的行为,cpufreq管CPU繁忙时的频率,两者互补。
- devfreq管外设,跟cpufreq是平行关系,但可能共享电源域。
- 所有策略最终都落到硬件层——时钟、电压、电源域。
调优建议:在实际项目中,先确认硬件上CPU和外设是否共享电压域。如果共享,调cpufreq时一定要考虑devfreq的约束。我见过一个平板项目,CPU降频后DDR没跟着降,结果功耗只降了10%,远低于预期。
3.5 用户空间接口一览
这三个子系统都暴露了sysfs接口,方便调试和验证。记住这几个路径:
| 子系统 | sysfs路径 | 关键文件 |
|---|---|---|
| cpuidle | /sys/devices/system/cpu/cpu*/cpuidle/ |
state*/name, state*/latency, state*/time |
| cpufreq | /sys/devices/system/cpu/cpu*/cpufreq/ |
scaling_governor, scaling_cur_freq, stats/ |
| devfreq | /sys/class/devfreq/ |
governor, cur_freq, available_frequencies |
调试时,我习惯先看 cpuidle/state*/time,确认CPU在哪个状态待得最久。然后看 cpufreq/stats/,了解频率分布。最后看 devfreq/*/cur_freq,确认外设频率是否合理。
注意:有些SoC的cpufreq和devfreq驱动可能没完全实现。比如某些低端芯片,devfreq只支持固定频率,调了也没用。先读 available_frequencies 确认支持哪些频率。