3. Linux内核Suspend-to-RAM (STR)与Android睡眠机制

好,咱们今天聊点硬核的。内核的电源管理,说白了就是让系统该睡的时候睡,该醒的时候醒。我做了这么多年嵌入式,发现很多问题都出在睡眠唤醒这个环节上。你想想看,一个车机系统,用户锁车走人了,结果屏幕还亮着,或者该唤醒导航的时候死活醒不过来——这都是大事。

3.1 内核PM核心框架:谁在管睡觉这件事?

Linux内核的电源管理框架,我习惯把它理解成一个「三层架构」:

  • 用户空间层:通过 /sys/power/state 写字符串触发睡眠
  • 核心层:PM core,负责协调所有设备的 suspend/resume 顺序
  • 驱动层:每个设备驱动实现自己的 .suspend().resume() 回调

嗯,这里要注意。内核支持的睡眠状态有这么几种:

状态 字符串 说明
Suspend-to-Idle freeze 浅度睡眠,CPU停转,外设不断电
Standby standby 比 freeze 深一点,但恢复很快
Suspend-to-RAM mem 内存自刷新,大部分外设断电
Suspend-to-Disk disk hibernate,内存镜像写到磁盘

在车机场景下,我们最常用的是 Suspend-to-RAM (STR)。为什么?因为车机要求快速启动,hibernate 太慢了。我在项目中遇到过,某款车机从 hibernate 恢复要 8 秒,用户都开出停车场了屏幕还没亮——这肯定不行。

3.2 Suspend/Resume 完整流程:从用户态到内核态

咱们走一遍完整的流程。假设用户按下了电源键,系统要进入睡眠:

  1. 用户空间写 echo mem > /sys/power/state
  2. 内核 PM core 收到请求,开始冻结用户空间进程(freeze_processes)
  3. 依次调用所有设备的 .suspend() 回调(按设备依赖顺序)
  4. 关闭非启动 CPU(secondary CPUs off)
  5. 最后一条指令:写寄存器让 SoC 进入 STR 模式

恢复流程反过来:

  1. 唤醒源(如 GPIO 中断、RTC 闹钟)触发 SoC 退出 STR
  2. 启动 CPU 恢复执行,重新开启 secondary CPUs
  3. 依次调用所有设备的 .resume() 回调
  4. 解冻用户空间进程(thaw_processes)
  5. 系统恢复正常运行

这里有个关键点——设备 suspend/resume 的顺序。内核用 dev_pm_domaindevice_links 来管理依赖关系。我建议你在调试时重点关注这个顺序,很多诡异的问题都是因为设备依赖没处理好。

核心数据结构:每个设备都有一个 struct dev_pm_ops,里面定义了 suspend/resume 回调。内核 PM core 通过 dpm_list 链表管理所有设备,按依赖关系排序后依次调用。

3.3 Android 的 early_suspend / late_resume:一段历史

说到这个,我得先讲讲历史。早期的 Android(2.x 时代)引入了一套叫 early_suspendlate_resume 的机制。为什么要有这个?

你想想看,标准的 Linux suspend 流程是「一刀切」的——所有设备一起睡,所有设备一起醒。但 Android 需要更精细的控制。比如屏幕关了,但蓝牙还在传文件;或者用户按了电源键,屏幕要先亮起来,其他设备慢慢恢复。

early_suspend 的流程是这样的:

  • 用户按下电源键 → 屏幕背光先关(early_suspend)
  • 过一会儿,如果没其他活动,再进入完整 suspend
  • 用户按电源键唤醒 → 先执行 late_resume(屏幕亮)
  • 其他设备再慢慢恢复

说白了,这就是一个「分阶段睡眠/唤醒」的机制。我在早期的一个平板项目里用过这套方案,当时觉得挺巧妙的。但后来发现,它有个致命问题——与内核主线框架的冲突

注意:early_suspend 是 Android 自己加的补丁,没有合入 Linux 内核主线。这意味着每次升级内核版本,Google 都要重新移植这套补丁,维护成本极高。

3.4 现状:wakeup_source 与 autosleep

到了 Android 4.4 以后,Google 终于放弃了 early_suspend,转而使用内核主线的 wakeup_source 框架。现在 Android 的睡眠机制是这样的:

  • wakeup_source:每个设备可以注册一个 wakeup source,表示「我在工作,别让我睡」
  • autosleep:内核自动检测,当所有 wakeup source 都释放了,就自动进入 suspend
  • wake_lock:用户空间通过 /sys/power/wake_lock 阻止系统睡眠

这套机制的好处是——与内核主线完全兼容。你不用再打一堆补丁了。而且调试起来也方便,通过 /sys/kernel/debug/wakeup_sources 可以查看当前谁在阻止系统睡眠。

调试技巧:我经常用这个命令排查「系统睡不下去」的问题:cat /sys/kernel/debug/wakeup_sources | sort -k2 -rn。看看哪个 wakeup source 的 active_count 最大,基本就是它的问题。

3.5 实战中的坑与经验

最后分享几个我踩过的坑:

  • GPIO 唤醒配置:我曾经遇到一个案子,车机休眠后怎么都唤不醒。查了两天,发现是 GPIO 的唤醒能力没配好。记住,不是所有 GPIO 都能唤醒 SoC,要看 datasheet 里的 wakeup capable 引脚。
  • RTC 闹钟唤醒:车机经常需要定时唤醒(比如预约充电)。我建议用 /sys/class/rtc/rtc0/wakealarm 来设置,比用 alarmtimer 更可靠。
  • 设备 resume 超时:内核默认给每个设备的 resume 时间是 5 秒。如果某个设备 resume 太慢,系统会打印 PM: resume of devices failed。我一般把这个值改大一点,或者优化驱动里的 resume 逻辑。

嗯,关于 STR 和 Android 睡眠机制,今天就聊这么多。下一节我们会深入 wakeup_source 的源码实现,看看内核到底是怎么管理这些「不想睡」的设备的。