1. 课程导入
在嵌入式、移动终端、车载与服务器场景中,Linux 设备的功耗与稳定性往往同时被考核:既要“该睡就睡、该醒就醒”,又要保证设备在热插拔、系统休眠、驱动卸载等过程中不丢状态、不崩溃。本课程围绕一条主线展开——把设备的电源状态变化与设备生命周期事件放在同一套协同模型中理解与实现。学完本课程,你应能读懂并编写 Runtime PM、System Sleep、设备树电源域、时钟/稳压器/复位等资源管理代码,并能定位“休眠后设备不工作”“卸载驱动后仍耗电”“唤醒源失效”等典型问题。
一、核心概念:设备、电源状态与生命周期
Linux 设备模型用 struct device 描述一个设备实例,用 struct device_driver 描述驱动,用总线(platform、i2c、spi、pci、usb 等)完成匹配与绑定。电源管理并不是独立模块,而是嵌入在设备模型中的一组回调与状态字段:
- Runtime PM:设备在系统运行期间的动态电源管理,核心是
pm_runtime_get_sync()/pm_runtime_put_sync()的引用计数,以及runtime_suspend/runtime_resume回调。 - System Sleep:系统级休眠(suspend-to-idle、suspend-to-RAM、hibernation),对应
suspend/resume回调,按设备树或总线顺序遍历设备。 - 电源域(Power Domain):多个设备共享同一供电/时钟/复位资源,形成层级依赖,典型实现为 genpd(Generic Power Domain)。
- 生命周期事件:
probe、remove、shutdown、runtime_suspend、system_suspend、wakeup等,它们与电源状态迁移一一对应。
理解这些概念的关键,是认识到“电源状态”不是孤立的布尔值,而是由引用计数、子设备状态、父域状态共同决定的复合状态。一个设备能否进入低功耗,取决于它自身是否空闲、子设备是否已挂起、父电源域是否允许关闭。
二、整体知识框架与协同关系
下图给出本课程的知识框架:左侧是设备生命周期事件,右侧是电源状态迁移,中间通过设备模型与电源域拓扑耦合。图中同时标出 Runtime PM 与 System Sleep 两条路径,以及它们共享的回调与资源。
从图中可以看到,生命周期事件是“触发源”,电源状态是“结果”,而设备模型与电源域拓扑是“约束条件”。三者缺一不可:只改状态机不理解依赖,会出现父域已关、子设备仍访问寄存器导致总线挂死;只处理生命周期不管理引用计数,会出现设备被提前挂起、数据传输失败。
三、方法流程:从事件到状态的协同路径
实际开发中,推荐按以下流程建立协同逻辑:
- 识别设备依赖:通过设备树
power-domains、clocks、regulators等属性,列出该设备依赖的父域与资源。 - 实现 Runtime PM 回调:在
runtime_suspend中关闭时钟、释放复位、保存寄存器;在runtime_resume中反向恢复。 - 接入引用计数:在数据传输前后成对调用
pm_runtime_get_sync()与pm_runtime_put_sync(),避免设备在传输中被挂起。 - 处理 System Sleep:实现
suspend/resume,必要时用pm_runtime_force_suspend()统一两条路径。 - 配置唤醒源:通过
device_set_wakeup_capable()与enable_irq_wake()让设备在低功耗下仍能唤醒系统。 - 验证与观测:使用
/sys/kernel/debug/pm_genpd/、powertop、dmesg观察状态迁移与失败原因。
/* 典型 Runtime PM 回调骨架 */
static int mydev_runtime_suspend(struct device *dev)
{
struct mydev *d = dev_get_drvdata(dev);
clk_disable_unprepare(d->clk);
regulator_disable(d->vdd);
return 0;
}
static int mydev_runtime_resume(struct device *dev)
{
struct mydev *d = dev_get_drvdata(dev);
regulator_enable(d->vdd);
clk_prepare_enable(d->clk);
return 0;
}
static const struct dev_pm_ops mydev_pm_ops = {
SET_RUNTIME_PM_OPS(mydev_runtime_suspend,
mydev_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(mydev_suspend, mydev_resume)
};
四、案例场景与常见误区
以一个 I2C 温度传感器为例:系统空闲时希望它进入 Runtime Suspend,关闭供电;但应用层每秒读取一次温度。若驱动在读取前未调用 pm_runtime_get_sync(),设备可能正处于挂起中,I2C 传输会超时。正确做法是在读取函数入口获取引用,读取完成后释放,让 Runtime PM 在两次读取之间自动挂起。
| 误区现象 | 根因 | 排查手段 |
|---|---|---|
| 休眠后设备不工作 | resume 未恢复时钟/寄存器 | 检查 resume 回调与 dmesg |
| 卸载驱动后仍耗电 | remove 未释放 regulator/clk | 查看 /sys/kernel/debug/clk |
| 唤醒源失效 | 未调用 enable_irq_wake() | 检查 /sys/kernel/debug/wakeup_sources |
| 父域关闭导致子设备异常 | 缺少 device_link 依赖 | 检查设备树 power-domains |
cat /sys/kernel/debug/pm_genpd/pm_genpd_summary 观察一次真实状态变化,把抽象回调与具体功耗数字对应起来。
本课程后续将沿着“设备模型 → Runtime PM → System Sleep → 电源域与资源 → 唤醒与调试”的路线逐步展开。掌握本节框架后,你在面对任何功耗问题时,都可以先问三个问题:当前处于哪个生命周期事件?目标电源状态是什么?依赖的资源与父域是否允许这次迁移?这三个问题,就是设备电源与生命周期协同的分析起点。