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也得跟着降。

我画了一张图,帮你理清它们的关系:

Linux电源管理三大子系统协作关系 Linux电源管理三大子系统协作关系 Linux内核PM核心 cpuidle CPU空闲状态管理 cpufreq CPU频率/电压调节 devfreq 外设频率/电压调节 硬件层:CPU核心、DDR、GPU、总线、时钟、电源域 协作要点: • cpuidle和cpufreq共享CPU空闲/繁忙状态信息,策略需协同 • devfreq与cpufreq可能共享电压域,调频时需考虑相互影响 • 三者通过PM QoS机制传递延迟/性能约束,避免策略冲突

从图上你能看到:

  • 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 确认支持哪些频率。