4、Linux电源管理子系统概述
电源管理,说白了就是让系统该睡的时候睡,该醒的时候醒,别在那空转耗电。我做了这么多年嵌入式,见过太多产品因为电源管理没做好,电池续航直接砍半的案例。Linux的电源管理子系统,就是帮我们解决这个问题的核心框架。
这一章,我们把它拆开来看。从顶层架构到底层接口,从设备树配置到调试手段,一条线捋清楚。
4.1 Linux电源管理架构
Linux的电源管理,不是一个大泥球。它分了三层,各管各的事。我习惯把它想象成一个「三层指挥系统」:
- PM Core:总指挥部。负责全局策略、状态切换、通知机制。
- Runtime PM:基层班长。管设备用的时候供电,不用的时候断电,动态调整。
- Suspend/Resume:大部队行动。整个系统一起睡,一起醒。
这三层配合得好,系统才能既省电又稳定。我在一个工业平板项目里,就见过Runtime PM和Suspend/Resume打架的情况——设备明明在跑,却被强行挂起了。嗯,这就是分层没协调好。
核心关系图:PM Core 向下调度 Runtime PM 和 Suspend/Resume,向上对接应用层的电源策略。
4.2 PM Core:总指挥部
PM Core 是电源管理的核心模块。它不直接操作硬件,而是提供一套框架,让驱动和子系统能注册进来。你想想看,如果没有这个统一调度,每个驱动自己搞一套电源管理,那系统不乱套才怪。
PM Core 主要干这几件事:
- 管理电源状态机:系统级的状态切换(Active、Suspend、Hibernate)。
- 通知链(Notification Chain):状态变化前、变化后,通知所有关心这件事的模块。
- 回调注册:驱动通过
struct dev_pm_ops注册自己的电源管理回调。
我记得第一次看 struct dev_pm_ops 的时候,被里面十几个回调函数吓到了。其实常用的就几个:.suspend、.resume、.runtime_suspend、.runtime_resume。其他的,用到再查就行。
小技巧:调试 PM Core 的问题,可以打开 CONFIG_PM_DEBUG 和 CONFIG_PM_SLEEP_DEBUG,内核会打印详细的电源状态切换日志。
4.3 Runtime PM:动态节能的利器
Runtime PM 是我个人最喜欢的机制。它让设备在空闲时自动断电,有请求时自动上电。整个过程对应用层透明——应用根本不知道设备被关过。
它的工作流程很简单:
- 设备空闲,驱动调用
pm_runtime_put_sync()或pm_runtime_put_autosuspend()。 - PM Core 检查引用计数,如果为0,调用驱动的
runtime_suspend回调。 - 设备断电,进入低功耗状态。
- 有新的I/O请求,驱动调用
pm_runtime_get_sync()。 - PM Core 调用
runtime_resume回调,设备上电。
这里有个坑,我踩过好几次:自动挂起超时(autosuspend delay)。如果设备频繁地在挂起和唤醒之间切换,反而更耗电。我建议给每个设备设置一个合理的超时时间,比如100ms。这样设备不会因为一次短暂的闲置就被关掉。
注意:Runtime PM 不是万能的。有些设备不支持动态电源管理,比如老旧的UART控制器。强行开启 Runtime PM 可能导致设备工作异常。
4.4 Suspend/Resume:系统级的睡眠与唤醒
Suspend/Resume 是系统级的操作。用户按下电源键,系统进入休眠;再按一下,系统恢复。这个过程涉及所有设备、CPU、内存控制器。
Linux 支持几种休眠状态:
| 状态 | 名称 | 功耗 | 唤醒延迟 | 说明 |
|---|---|---|---|---|
| PM_SUSPEND_ON | 正常工作 | 高 | 0 | 系统全速运行 |
| PM_SUSPEND_STANDBY | 待机 | 中 | 短 | CPU暂停,外设保持供电 |
| PM_SUSPEND_MEM | 内存休眠 | 低 | 中 | 内存自刷新,CPU和外设断电 |
| PM_SUSPEND_MAX | 深度休眠 | 极低 | 长 | 取决于硬件支持 |
我在一个电池供电的IoT设备上,用的就是 PM_SUSPEND_MEM。系统大部分时间都在休眠,只有传感器触发时才唤醒。整机功耗从200mW降到了5mW,效果非常明显。
Suspend/Resume 的执行顺序很重要:
- Suspend:先冻结用户进程,然后按设备树的依赖顺序,从叶子节点向根节点依次挂起。
- Resume:反过来,从根节点向叶子节点依次恢复。
为什么这样设计?因为设备之间有依赖关系。比如I2C控制器必须先恢复,挂在其上的I2C设备才能正常工作。这个顺序搞反了,系统就起不来了。
4.5 设备树中的电源管理节点
设备树(Device Tree)是嵌入式Linux的标配。电源管理的配置,也写在设备树里。我见过不少工程师,代码写得挺好,设备树里电源管理节点却配得乱七八糟。
常用的电源管理属性:
power-domains:指定设备所属的电源域。wakeup-source:标记设备可以作为唤醒源。regulator-*:电源调节器相关属性。clocks:时钟源,关闭时钟也是省电的重要手段。
举个例子,一个支持唤醒的触摸屏设备树节点:
&i2c1 {
touchscreen@38 {
compatible = "edt,edt-ft5406";
reg = <0x38>;
interrupt-parent = <&gpio1>;
interrupts = <9 IRQ_TYPE_EDGE_FALLING>;
wakeup-source; /* 标记为唤醒源 */
power-domains = <&pd_touch>; /* 所属电源域 */
};
};
这里 wakeup-source 是关键。没有它,触摸屏的中断无法唤醒系统。我曾经在一个项目里漏了这个属性,结果设备怎么按都唤不醒,排查了半天才发现是设备树的问题。
建议:在设备树里,每个可能作为唤醒源的设备,都要显式加上 wakeup-source。不要依赖默认值,不同内核版本的默认行为可能不一样。
4.6 sysfs与debugfs接口
调试电源管理,光看代码是不够的。sysfs 和 debugfs 提供了运行时接口,让我们能直接观察和控制系统状态。
sysfs 接口
sysfs 是用户空间和内核空间交互的主要通道。电源管理相关的 sysfs 节点主要集中在 /sys/power/ 和 /sys/devices/.../power/。
| 路径 | 作用 | 常用操作 |
|---|---|---|
/sys/power/state |
控制系统休眠状态 | echo mem > /sys/power/state |
/sys/power/wakeup_count |
查看/设置唤醒计数 | 用于处理并发唤醒事件 |
/sys/devices/.../power/control |
控制设备的Runtime PM | echo on/auto > control |
/sys/devices/.../power/wakeup |
查看/使能设备唤醒能力 | echo enabled > wakeup |
/sys/devices/.../power/autosuspend_delay_ms |
设置自动挂起超时 | echo 100 > autosuspend_delay_ms |
我调试时最常用的命令:
# 查看系统支持的休眠状态
cat /sys/power/state
# 强制设备进入休眠
echo mem > /sys/power/state
# 查看某个设备的Runtime PM状态
cat /sys/devices/platform/soc/xxx/power/control
# 使能设备的唤醒功能
echo enabled > /sys/devices/platform/soc/xxx/power/wakeup
debugfs 接口
debugfs 提供了更详细的调试信息。需要内核开启 CONFIG_DEBUG_FS 和 CONFIG_PM_DEBUG。
常用的 debugfs 节点:
/sys/kernel/debug/pm_qos:查看PM QoS约束。/sys/kernel/debug/suspend_stats:查看休眠/唤醒的统计信息。/sys/kernel/debug/wakeup_sources:查看注册的唤醒源。
我记得有一次,系统总是莫名其妙地唤醒。用 cat /sys/kernel/debug/wakeup_sources 一看,发现是网卡的中断在频繁触发。后来在设备树里把网卡的 wakeup-source 去掉,问题就解决了。
注意:debugfs 在生产环境中建议关闭。它暴露了太多内核内部信息,有安全风险。调试完记得去掉 CONFIG_DEBUG_FS。
4.7 小结
Linux电源管理子系统,说白了就是一套「省电调度框架」。PM Core 定规矩,Runtime PM 管日常,Suspend/Resume 管大动作。设备树告诉内核硬件怎么接,sysfs 和 debugfs 让我们能看得见、摸得着。
这一章的内容,是后面所有低功耗设计的基础。把架构理清了,后面调驱动、调策略、调性能,才能有的放矢。