4. Linux内核电源管理框架:suspend/resume流程,wakeup source机制,autosleep与earlysuspend
各位做Android底层开发的兄弟,今天我们来啃一块硬骨头——Linux内核的电源管理框架。说实话,这玩意儿我刚接触时也是一头雾水,什么suspend、resume、wakeup source,还有那堆历史遗留的earlysuspend,光看名字就够绕的。但搞明白之后你会发现,整个Android系统的省电逻辑,说白了就是围绕这几个机制在转。
4.1 suspend/resume:系统休眠的标准流程
先聊聊最核心的suspend/resume流程。这是Linux内核标准的休眠唤醒机制,Android系统深度睡眠(比如你按电源键锁屏后几分钟)走的就是这条路。
整个流程分四步走:
- 冻结用户空间进程:内核会先发信号给所有用户态进程,让它们进入可中断的睡眠状态。我遇到过一个问题,某个第三方App的进程死活不响应冻结信号,导致系统无法休眠,最后发现是它搞了个死循环的alarm定时器。
- 暂停内核线程:接着冻结内核线程,比如kworker、kswapd这些。这里有个坑——如果你自己写的内核模块里开了内核线程,记得注册freezer回调,不然系统会卡在这一步。
- 设备suspend回调:按设备注册顺序,依次调用驱动的suspend函数。顺序很重要:先调用late suspend,再调用noirq suspend。我曾经在某个项目里,把GPIO控制器和I2C控制器的suspend顺序搞反了,结果I2C设备在休眠时还去操作GPIO,直接导致系统死锁。
- 进入休眠状态:最后CPU执行WFI指令,等待唤醒事件。
核心要点:suspend/resume是全局操作,一旦触发,整个系统都会进入休眠。所以那些需要保持唤醒的设备(比如音乐播放),必须通过wakeup source机制来阻止系统休眠。
4.2 wakeup source:谁在阻止系统休眠?
wakeup source,直译就是「唤醒源」。它的作用有两个:一是阻止系统进入休眠(比如你在充电时,系统不该睡);二是当系统已经休眠时,能把它叫醒(比如按电源键)。
我个人的习惯是,把wakeup source想象成一把「锁」。每个设备都可以申请这把锁,只要还有锁没释放,系统就不能睡。代码里是这样用的:
// 在驱动中注册wakeup source
struct wakeup_source *ws = wakeup_source_register(dev, "my_wakeup_source");
// 阻止系统休眠
__pm_stay_awake(ws);
// 允许系统休眠
__pm_relax(ws);
// 注销
wakeup_source_unregister(ws);
嗯,这里要注意:__pm_stay_awake和__pm_relax必须成对出现。我曾经在调试一个蓝牙耳机项目时,发现系统总是无法休眠,排查了两天才找到原因——某个驱动的wakeup source在异常路径下只调了stay_awake,没调relax,导致锁一直没释放。
避坑指南:我曾经在某个平板项目里,发现系统休眠后功耗异常高。用powertop一查,发现触摸屏驱动在休眠前申请了wakeup source但没释放。记住:所有wakeup source必须在suspend之前全部释放,否则系统会强制唤醒。
4.3 autosleep:自动休眠的调度器
autosleep是Android引入的一个机制,说白了就是「没人用手机时自动休眠」。它基于wakeup source的计数来判断:当所有wakeup source都释放了,autosleep就会触发suspend。
它的工作流程很简单:
- 内核维护一个全局的wakeup source计数器
- 每次有wakeup source申请或释放,计数器都会变化
- 当计数器归零时,autosleep自动发起suspend
你可以在/sys/power/autosleep节点看到当前状态:
# 查看当前autosleep状态
cat /sys/power/autosleep
# 输出可能是:off, mem, standby, freeze 等
# 手动设置autosleep模式
echo mem > /sys/power/autosleep
我个人觉得,autosleep是Android省电的「最后一公里」。它解决了传统Linux需要用户手动休眠的问题,让系统在空闲时自动进入低功耗状态。但要注意,如果某个驱动频繁申请释放wakeup source,会导致系统反复休眠唤醒,反而更耗电。
4.4 earlysuspend:被淘汰的「老前辈」
earlysuspend是Android早期(2.x时代)的休眠机制,现在已经基本被淘汰了。但很多老项目里还能见到它的影子,所以我简单提一下。
它的设计思路是:当用户按下电源键关屏时,先执行early suspend(只关屏幕和触摸),但CPU和内存还在工作;等一段时间后,再执行真正的suspend。说白了就是「两步走」的休眠策略。
为什么被淘汰?原因很简单:
- 与wakeup source机制冲突:earlysuspend自己维护了一套唤醒逻辑,和内核标准的wakeup source不兼容
- 维护成本高:每个驱动都要实现两套suspend回调(early和normal)
- 功耗优化不彻底:earlysuspend阶段CPU还在跑,省电效果有限
我记得在Android 4.4之后,Google就全面转向了autosleep + wakeup source的方案。如果你现在还在维护老项目,我建议尽快迁移,不然新内核版本可能直接不支持earlysuspend了。
实战建议:在调试休眠唤醒问题时,可以用cat /sys/kernel/debug/wakeup_sources查看所有wakeup source的状态。哪个设备没释放锁,一目了然。这个命令我几乎每天都会用。
4.5 三者关系:一张图说清楚
最后,我用一个表格总结一下这三个机制的关系:
| 机制 | 作用 | 触发条件 | 当前状态 |
|---|---|---|---|
| suspend/resume | 系统级休眠唤醒 | 手动或autosleep触发 | 标准方案,持续使用 |
| wakeup source | 阻止/触发休眠 | 设备驱动申请/释放 | 核心机制,必须掌握 |
| autosleep | 自动休眠调度 | wakeup source计数归零 | Android默认方案 |
| earlysuspend | 分步休眠 | 用户关屏 | 已淘汰,不建议使用 |
你想想看,整个电源管理框架其实就围绕一个核心问题:「什么时候该睡,什么时候该醒?」wakeup source负责回答「能不能睡」,autosleep负责回答「该不该睡」,suspend/resume负责执行「怎么睡」。搞明白这三者的关系,Android低功耗调试你就入门了。
下一章,我会带大家实战分析一个真实的休眠唤醒问题,看看怎么用这些机制定位功耗异常。到时候记得带上你的串口日志。