第2章:Linux电源管理框架全景
各位同学,欢迎来到第二章。上一章我们聊了为什么要做电源管理,这一章咱们直接切入正题——看看Linux电源管理这个庞然大物到底长什么样。
我第一次接触Linux电源管理时,说实话有点懵。代码散落在内核各个角落,有叫PM的,有叫Runtime的,还有CPU Freq、CPU Idle……感觉像是一堆独立的东西。后来我才明白,它们其实是一个有机的整体。
2.1 电源管理子系统架构概览
Linux电源管理,说白了就是一套“省电方法论”的内核实现。它不是一个单一的模块,而是一组相互协作的子系统。
我习惯把整个架构分成三层:
- 用户空间层:sysfs、procfs接口,以及各种策略决策者(比如调频governor)
- 内核核心层:PM Core、Runtime PM框架、Suspend/Resume流程
- 硬件抽象层:CPU Freq驱动、CPU Idle驱动、Devfreq驱动、设备驱动中的PM回调
你想想看,用户空间说“我要省电”,这个指令怎么传到硬件?就是通过这三层一层层传递下去的。
核心观点:电源管理不是某个驱动的事,而是整个系统的事。从应用到底层硬件,每个环节都要参与。
2.2 PM Core——电源管理的中枢神经
PM Core是整个电源管理框架的“大脑”。它定义了一套标准接口,让所有设备驱动都能以统一的方式参与电源管理。
PM Core主要干这几件事:
- 管理设备的电源状态(active、suspended、runtime suspended等)
- 维护suspend/resume的调用顺序(先挂起谁、后唤醒谁)
- 提供通用的PM回调函数模板
我记得有一次调试一个USB设备的挂起问题,折腾了两天没找到原因。后来发现是PM Core里设备依赖关系没配好,导致父设备先睡了,子设备还在干活——这不乱套了吗?
个人经验:调试PM相关问题时,先检查设备树里power-domains和supply的配置。很多诡异问题都是这里埋的坑。
2.3 Runtime PM——运行时动态电源管理
Runtime PM,顾名思义,是系统运行时动态管理设备电源的机制。它不像suspend那样把整个系统都挂起,而是针对单个设备——设备空闲了就关掉它的时钟或电源,需要时再重新打开。
它的核心思想很简单:用的时候开,不用的时候关。但实现起来可没那么简单。
Runtime PM有三个关键状态:
| 状态 | 含义 | 典型场景 |
|---|---|---|
| ACTIVE | 设备正在工作 | 设备正在处理I/O |
| SUSPENDED | 设备已挂起 | 设备空闲超过阈值 |
| SUSPENDING/RESUMING | 状态切换中 | 正在执行回调函数 |
驱动开发者需要实现三个回调:runtime_suspend、runtime_resume、runtime_idle。嗯,这里要注意——runtime_idle不是必须的,但它可以帮你做“预判”,在设备真正空闲前就决定要不要挂起。
// 一个典型的Runtime PM驱动片段
static int my_device_runtime_suspend(struct device *dev)
{
struct my_device *mydev = dev_get_drvdata(dev);
// 关闭设备时钟
clk_disable(mydev->clk);
// 关闭设备电源
regulator_disable(mydev->regulator);
dev_dbg(dev, "runtime suspended\n");
return 0;
}
static int my_device_runtime_resume(struct device *dev)
{
struct my_device *mydev = dev_get_drvdata(dev);
// 打开设备电源
regulator_enable(mydev->regulator);
// 打开设备时钟
clk_enable(mydev->clk);
dev_dbg(dev, "runtime resumed\n");
return 0;
}
避坑指南:我曾经在某个项目中,runtime_resume里忘了恢复寄存器状态,结果设备唤醒后工作异常。记住:挂起时保存上下文,恢复时还原上下文——这是基本功。
2.4 Suspend/Resume——系统级休眠与唤醒
Runtime PM管的是单个设备,而Suspend/Resume管的是整个系统。常见的suspend状态有:
- Suspend-to-Idle (S0):轻度睡眠,CPU进入idle状态,但内存保持供电
- Suspend-to-RAM (S3):深度睡眠,CPU断电,内存保持自刷新
- Suspend-to-Disk (S4):休眠,系统状态保存到磁盘,完全断电
Suspend/Resume的流程是分阶段的。我简单画一下:
suspend流程:
1. 用户空间冻结(freeze processes)
2. 设备挂起(device suspend)
- 按依赖顺序:先挂起子设备,再挂起父设备
3. 系统核心挂起(syscore suspend)
4. CPU挂起(CPU suspend)
resume流程:
1. CPU恢复
2. 系统核心恢复
3. 设备恢复(device resume)
- 按依赖顺序:先恢复父设备,再恢复子设备
4. 用户空间解冻
为什么suspend和resume的顺序是反的?你想想看——父设备是子设备的依赖,父设备挂了,子设备肯定没法正常工作。反过来,恢复时得先把父设备弄好,子设备才能干活。
2.5 CPU Freq——动态调频
CPU Freq(也叫cpufreq)是动态调整CPU频率和电压的机制。它的核心逻辑是:负载高时提频,负载低时降频。
cpufreq有两个关键角色:
- Governor:策略制定者,决定当前该用什么频率
- Driver:策略执行者,负责实际切换频率
常见的governor有:
| Governor | 策略 | 适用场景 |
|---|---|---|
| performance | 始终最高频 | 追求性能,不在乎功耗 |
| powersave | 始终最低频 | 极致省电,性能无所谓 |
| ondemand | 按需调频 | 大多数场景的平衡方案 |
| conservative | 缓慢调频 | 对频率变化敏感的场景 |
| schedutil | 基于调度器负载 | 最新、最精准的方案 |
我个人比较推荐schedutil。它直接利用调度器的负载信息来做调频决策,响应更快、更精准。我在一个视频编解码项目里用过,功耗比ondemand低了将近15%。
2.6 CPU Idle——空闲时省电
CPU Idle和CPU Freq是两码事。CPU Freq是“干活时用多大力”,CPU Idle是“不干活时怎么休息”。
CPU Idle的核心是idle state(也叫C-state)。每个C-state代表一种睡眠深度:
- C0:正常工作状态
- C1:浅度睡眠,随时可以唤醒
- C2:中度睡眠,唤醒延迟稍大
- C3:深度睡眠,省电最多但唤醒最慢
idle governor负责选择进入哪个C-state。常见的governor有:
- menu:根据预测的空闲时间选择最合适的C-state
- ladder:逐级加深睡眠,比较保守
menu governor是目前的主流选择。它会根据历史数据预测CPU会空闲多久,然后选择一个“性价比最高”的C-state。
一个小技巧:如果你发现系统响应变慢,可以检查一下是不是进入了太深的C-state。有时候省电和省性能需要权衡。
2.7 Devfreq——设备动态调频
Devfreq是CPU Freq的“兄弟”,但它管的是非CPU设备——比如GPU、DDR控制器、总线等。
Devfreq的架构和cpufreq非常像:
- Governor:决定设备的工作频率
- Driver:执行频率切换
常见的devfreq governor:
- simple_ondemand:类似cpufreq的ondemand
- userspace:用户空间手动控制
- passive:跟随父设备的频率变化
我记得在一个GPU项目中,默认的simple_ondemand调频太激进,导致画面卡顿。后来换成了自定义的governor,根据帧率来做调频决策,效果好了很多。
2.8 各组件如何协同工作
好了,上面讲了五个核心组件。它们不是孤立的,而是相互配合的。举个例子:
- 系统空闲时,CPU Idle让CPU进入C-state省电
- 如果某个设备空闲了,Runtime PM把它挂起
- CPU负载降低,CPU Freq自动降频
- GPU负载降低,Devfreq自动降频
- 如果整个系统都空闲了,可以触发Suspend-to-RAM
你看,从单个设备到整个系统,从CPU到外设,每个组件都在自己的层面做省电。这就是Linux电源管理的精髓——分层协作,各司其职。
总结一下:PM Core是框架,Runtime PM管设备级,Suspend/Resume管系统级,CPU Freq管CPU频率,CPU Idle管CPU空闲,Devfreq管外设频率。把这几个搞明白了,Linux电源管理你就入门了。
下一章,我们会深入PM Core的代码实现,看看这些回调函数到底是怎么注册和调用的。到时候我会带大家手撕源码,敬请期待。