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。

它的工作流程很简单:

  1. 内核维护一个全局的wakeup source计数器
  2. 每次有wakeup source申请或释放,计数器都会变化
  3. 当计数器归零时,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低功耗调试你就入门了。

下一章,我会带大家实战分析一个真实的休眠唤醒问题,看看怎么用这些机制定位功耗异常。到时候记得带上你的串口日志。