1. 电源管理基础:Linux电源管理子系统概述、ACPI与设备树、动态电源管理(DPM)概念
各位同学,咱们今天聊聊电源管理。说实话,这玩意儿在嵌入式开发里,经常被当成「锦上添花」的东西。但我做过几个项目后,发现这其实是「生死攸关」的——尤其是做电池供电的设备,功耗降不下来,产品根本没法用。
我个人习惯把电源管理分成两个层面来看:一个是系统级的,比如整个SoC怎么休眠、怎么唤醒;另一个是设备级的,比如某个外设不用了,怎么把它的时钟关掉。今天这一讲,咱们先把基础打牢。
1.1 Linux电源管理子系统:一个三层架构
Linux的电源管理,说白了就是一套「省电机制」。它从上到下分三层:
- 用户空间层:你写个脚本,echo mem > /sys/power/state,系统就休眠了。这是最上层的接口。
- 内核核心层:PM core,负责调度、状态机管理。它像个大管家,协调各个设备。
- 设备驱动层:这才是咱们的战场。每个驱动都要实现suspend/resume回调。
关键点:驱动开发者主要跟设备驱动层打交道。你写的probe、remove、suspend、resume,就是电源管理的「最后一公里」。
我记得刚入行时,有个前辈跟我说:「驱动写得好不好,看suspend/resume写得干不干净。」当时不理解,后来踩了坑才明白——系统休眠时,设备没关干净,唤醒后直接死机。嗯,这种问题查起来特别痛苦。
1.2 ACPI与设备树:两种「硬件说明书」
你想想看,内核怎么知道你的板子上有哪些设备?哪些设备支持休眠?这就需要一种描述硬件的方式。
目前主流的有两种:
| 特性 | ACPI | 设备树(Device Tree) |
|---|---|---|
| 主要应用 | x86、服务器、笔记本 | ARM、嵌入式、RISC-V |
| 描述方式 | AML字节码,像个微型程序 | dts文本,编译成dtb |
| 电源管理 | 提供_PSC、_PS0/_PS3等方法 | 通过power-domains、regulator等属性 |
| 运行时修改 | 支持动态加载 | 通常固定,需重新编译 |
ACPI 这边,我做过一个x86平板项目。ACPI表里定义了设备的电源状态,比如D0(全速)、D3hot(关闭主电源但保留Vaux)、D3cold(完全断电)。驱动里要调用acpi_device_set_power()来切换状态。说实话,ACPI的AML代码调试起来挺烦的,我建议你备好iasl反编译工具。
设备树 这边,嵌入式开发更常见。比如你有个I2C触摸屏,在dts里这样描述:
i2c_touch: touch@38 {
compatible = "goodix,gt911";
reg = <0x38>;
interrupt-parent = <&gpio1>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
power-domains = <&pd_core>;
/* 这个属性告诉内核:触摸屏属于哪个电源域 */
};
我曾经在项目里遇到一个坑:设备树里忘了加power-domains属性,结果触摸屏休眠后唤不醒。查了两天才发现,是电源域没关联上。所以,设备树里的电源相关属性,一个都不能少。
1.3 动态电源管理(DPM):让设备「随用随开」
DPM这个概念,说白了就是「按需供电」。设备在用的时候全速跑,不用的时候立刻降频或断电。跟家里关灯一个道理——人走灯灭。
Linux内核里,DPM主要通过两种机制实现:
- 系统睡眠:整个系统一起睡。suspend-to-RAM、suspend-to-disk。
- 运行时电源管理(Runtime PM):单个设备独立管理。这是更细粒度的控制。
我的建议:优先用Runtime PM。系统睡眠是「大炮打蚊子」,Runtime PM才是「精准打击」。比如一个USB鼠标,5分钟不动,内核自动把它挂起;你一动,立刻唤醒。用户根本感觉不到。
Runtime PM的核心API就几个:
// 通知内核:设备正在忙,别挂起
pm_runtime_get_sync(dev);
// 通知内核:设备空闲了,可以挂起
pm_runtime_put_sync(dev);
// 在驱动中实现挂起/恢复回调
static int mydev_runtime_suspend(struct device *dev) {
// 关闭时钟、降低电压
clk_disable(mydev->clk);
regulator_disable(mydev->vcc);
return 0;
}
static int mydev_runtime_resume(struct device *dev) {
// 重新上电、恢复时钟
regulator_enable(mydev->vcc);
clk_enable(mydev->clk);
return 0;
}
这里有个容易踩的坑:pm_runtime_get_sync和pm_runtime_put_sync必须成对出现。我见过一个同事,get了三次但只put了两次,结果设备永远挂不起来——引用计数没归零。嗯,这种bug查起来特别隐蔽。
1.4 电源管理中的「状态机」思维
做电源管理驱动,你脑子里要有一张状态图。每个设备无非就这几个状态:
- ACTIVE:正常工作,该干啥干啥。
- SUSPENDING:正在准备休眠,保存上下文。
- SUSPENDED:已经休眠,功耗极低。
- RESUMING:正在唤醒,恢复上下文。
驱动里要处理状态切换时的「边界情况」。比如,设备正在传输数据时,突然来了个suspend请求怎么办?正确的做法是:等当前传输完成,再进入suspend。强行打断会导致数据损坏。
警告:不要在suspend回调里调用可能引起阻塞的函数,比如msleep()。内核在suspend流程中持有锁,你这一睡,整个系统都卡住了。我早期犯过这个错,结果系统休眠要等30秒——用户差点把设备砸了。
1.5 小结:这一章你该带走什么?
好了,咱们捋一下今天的内容:
- Linux电源管理分三层,驱动层是咱们的主战场。
- ACPI和设备树是两种硬件描述方式,嵌入式开发重点掌握设备树。
- DPM的核心思想是「按需供电」,Runtime PM是实现它的利器。
- 状态机思维很重要,处理好suspend/resume的边界条件。
下一章,咱们会深入Runtime PM的源码实现,看看内核到底是怎么管理设备电源的。到时候我会带你看几个实际项目里的案例——那些让我熬夜查bug的案例。
先消化这些,有问题咱们群里聊。