4. 电源管理基础:Linux电源管理框架概述、系统睡眠状态与Runtime PM
电源管理,说白了就是让系统在“干活”和“歇着”之间找到平衡。嵌入式设备对功耗极其敏感,一块电池撑一天还是撑一周,往往就取决于电源管理做得好不好。Linux内核的电源管理框架相当庞大,但核心脉络其实很清晰:系统级的睡眠状态,以及设备级的运行时电源管理。
我个人习惯把电源管理分成两个维度来看:全局睡眠和局部运行时控制。前者是让整个系统进入低功耗模式,后者是让某个设备在不使用时自动断电。两者配合,才能把功耗压到最低。
4.1 Linux电源管理框架概览
Linux的电源管理框架,从软件栈的角度看,大致分为三层:
- 核心层 (PM Core):提供统一的接口和数据结构,比如
struct dev_pm_ops,管理各种电源状态。 - 子系统层 (Subsystem):比如PCI、I2C、SPI总线驱动,它们会实现通用的电源管理回调。
- 设备驱动层 (Device Driver):具体硬件的控制代码,负责保存/恢复寄存器状态、开关时钟等。
你想想看,一个设备要进入低功耗,驱动得先保存上下文,然后关掉时钟,最后可能还要关掉电源。这一套动作,就是通过 dev_pm_ops 里的回调函数来完成的。
核心数据结构:struct dev_pm_ops
struct dev_pm_ops {
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
int (*freeze)(struct device *dev);
int (*thaw)(struct device *dev);
int (*poweroff)(struct device *dev);
int (*restore)(struct device *dev);
int (*suspend_late)(struct device *dev);
int (*resume_early)(struct device *dev);
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
};
嗯,这里要注意,suspend 和 resume 是系统睡眠时用的,而 runtime_suspend 和 runtime_resume 是运行时电源管理用的。两者虽然名字像,但调用时机完全不同。
4.2 系统睡眠状态
系统睡眠,就是让整个系统进入一个低功耗模式。Linux内核定义了四种主要的睡眠状态,通过 /sys/power/state 文件可以查看和设置。
| 状态名称 | 字符串标识 | 功耗等级 | 唤醒延迟 | 上下文保留 |
|---|---|---|---|---|
| Suspend-to-Idle | freeze | 最低 | 极短 | 完全保留 |
| Standby | standby | 较低 | 短 | 完全保留 |
| Suspend-to-RAM | mem | 低 | 中等 | 内存保留 |
| Hibernate | disk | 极低(关机级) | 长 | 保存到磁盘 |
4.2.1 Suspend-to-RAM (STR)
这是最常用的深度睡眠模式。系统会把CPU、外设都关掉,只给内存供电。唤醒时,从内存中恢复执行。我在项目中遇到过一个问题:某个板子在STR唤醒后,网卡死活不通。查了半天,发现是网卡的PHY芯片在睡眠时被断电了,但驱动没有在resume时重新初始化PHY。嗯,这就是典型的驱动没处理好上下文恢复。
避坑指南:我曾经在调试STR时,发现唤醒后串口打印乱码。原因是串口驱动在suspend时保存了寄存器,但resume时恢复的顺序不对。记住,恢复的顺序要和保存的顺序完全相反,尤其是时钟和电源域相关的寄存器。
4.2.2 Standby
Standby比STR浅一些。它通常只关闭CPU的时钟,但保持CPU电源。外设可能进入低功耗模式,但不需要完全断电。唤醒速度很快,适合需要频繁响应的场景。
说白了,Standby就是“打个盹”,STR是“睡死过去”。
4.2.3 Hibernate
Hibernate是最彻底的睡眠方式。系统会把整个内存镜像保存到磁盘(通常是swap分区),然后完全断电。下次开机时,从磁盘恢复镜像。这种方式功耗为零,但唤醒时间很长,因为要从磁盘读回整个内存。
我个人习惯在开发板上很少用Hibernate,因为嵌入式设备的存储空间有限,而且恢复时间太长。但在笔记本电脑上,Hibernate还是很实用的。
4.3 系统睡眠流程
系统进入睡眠的流程,可以画成一张图。我画了一个简单的流程图,帮你理解这个过程。
唤醒流程就是反过来:先恢复CPU,然后按设备注册顺序的正序执行resume回调,最后解冻用户进程。这个顺序很重要,因为有些设备依赖其他设备先恢复。
4.4 Runtime PM概念
Runtime PM,也叫运行时电源管理。它允许设备在不使用时自动进入低功耗状态,而不需要整个系统睡眠。你想想看,一个嵌入式设备上可能有几十个外设,但同一时刻只有几个在工作。如果能让空闲的设备自动断电,那省下来的功耗就很可观了。
Runtime PM的核心思想是:设备空闲时自动挂起,有请求时自动唤醒。它通过引用计数来管理:当设备的使用计数降到0时,触发 runtime_suspend;当计数从0变成1时,触发 runtime_resume。
Runtime PM的典型使用模式
// 启用Runtime PM
pm_runtime_enable(dev);
// 在设备不再使用时,释放引用
pm_runtime_put_sync(dev);
// 或者异步释放
pm_runtime_put(dev);
// 在需要访问设备时,获取引用
pm_runtime_get_sync(dev);
// 或者异步获取
pm_runtime_get(dev);
我在项目中遇到过一个问题:某个I2C触摸屏,在系统空闲时总是无法进入runtime suspend。查了半天,发现是某个内核线程一直在轮询触摸屏的状态,导致引用计数始终不为0。嗯,这就是典型的“谁在占用设备”的问题。
警告:Runtime PM的引用计数必须成对出现。如果你调用了 pm_runtime_get 但没有对应的 pm_runtime_put,设备将永远无法进入低功耗状态。我曾经见过一个驱动,在中断处理函数中调用了 pm_runtime_get 但忘记在退出时 pm_runtime_put,结果设备功耗比预期高了30%。
4.5 Runtime PM与系统睡眠的交互
Runtime PM和系统睡眠是两套独立的机制,但它们会相互影响。当系统进入睡眠时,内核会先调用设备的 runtime_suspend(如果设备正在运行),然后再调用 suspend 回调。唤醒时则相反。
说白了,系统睡眠是“强制所有设备睡觉”,而Runtime PM是“设备自己决定要不要睡觉”。两者配合使用时,要特别注意:如果设备在Runtime PM中已经挂起了,系统睡眠时就不应该再重复挂起。内核通过 dev->power.is_suspended 标志来避免这种情况。
个人经验:我建议在驱动中同时实现Runtime PM和系统睡眠回调,但让Runtime PM的 runtime_suspend 和 runtime_resume 做真正的硬件操作,而系统睡眠的 suspend 和 resume 只做软件层面的状态保存和恢复。这样可以避免重复操作,也更容易调试。
4.6 总结
Linux电源管理框架的核心,就是系统睡眠和Runtime PM这两条线。系统睡眠让整个系统进入低功耗模式,Runtime PM让单个设备按需休眠。两者相辅相成,缺一不可。
嗯,这一章的内容就到这里。记住,电源管理不是一蹴而就的,需要你在实际项目中不断调试和优化。尤其是设备树中的电源管理相关属性,我们会在后面的章节中详细讲解。