系统休眠状态:从 ACPI 标准到 Linux 实现

说到休眠状态,很多人第一反应就是「按电源键让电脑睡过去」。但实际做底层开发的人都知道,这里面的门道深着呢。我最早接触 ACPI 是在做一款平板电脑的电源管理时,被各种 S 状态搞得晕头转向。后来踩了几个坑,才真正搞明白这些状态到底在干什么。

这一节,我们就来把 ACPI 定义的 S0 到 S5 状态,以及 Linux 具体实现的几种休眠模式,彻底讲清楚。

ACPI 标准:S0 - S5 状态

ACPI(Advanced Configuration and Power Interface)是业界通用的电源管理标准。它定义了六种系统状态,从 S0(全速运行)到 S5(完全关机)。

我习惯把这几个状态分成三类:

  • 工作状态:S0
  • 睡眠状态:S1、S2、S3
  • 休眠/关机状态:S4、S5

下面这张表,是我做培训时必讲的。它把每个状态的核心特征都列出来了:

状态 名称 CPU 状态 内存状态 设备状态 恢复时间
S0 正常工作 运行 活动 活动 —
S1 浅度睡眠 停止(缓存保留) 自刷新 部分关闭 秒级
S2 深度睡眠 停止(缓存丢失) 自刷新 大部分关闭 秒级
S3 挂起到内存 关闭 自刷新 关闭 秒级
S4 挂起到磁盘 关闭 保存到磁盘后关闭 关闭 分钟级
S5 软关机 关闭 关闭 关闭 需重新启动

关键点:S1 和 S2 在现代 x86 平台上基本已经见不到了。大多数 PC 和服务器只实现 S0、S3、S4、S5。你想想看,S1 和 S2 省电效果不明显,恢复速度也没比 S3 快多少,厂商自然就把它俩砍掉了。

Linux 支持的四种休眠状态

Linux 内核在 ACPI 标准之上,抽象出了四种具体的休眠状态。它们定义在 /sys/power/state 文件中,你可以直接 cat 出来看看:

# cat /sys/power/state
freeze mem disk

嗯,这里要注意。不同内核版本、不同硬件平台,输出的内容可能不一样。但核心就是这三种:freeze、mem、disk。它们分别对应:

  • freeze → Suspend-to-Idle(S0ix)
  • mem → Suspend-to-RAM(S3),也可能是 Standby(S1)
  • disk → Hibernate(S4)

下面我逐个展开讲。

1. Suspend-to-Idle(Freeze)

这是最浅的休眠状态。说白了,就是把用户空间的进程都冻结住,CPU 进入 idle 循环,外设尽可能进入低功耗模式。但内存是保持供电的,系统上下文也都在。

我在做一款 IoT 网关时,就大量用了这个模式。设备大部分时间都在等网络数据,用 Suspend-to-Idle 可以把功耗从 5W 降到 0.5W,而且恢复速度极快——几十毫秒就能响应外部中断。

适用场景:对响应延迟敏感的设备,比如智能音箱、工业控制器。它们需要「随时待命」,但又不能一直满功率运行。

2. Standby(S1)

这个状态在 Linux 里其实有点尴尬。它对应 ACPI S1,但前面说了,现代硬件基本不实现了。如果你在 /sys/power/state 里看到 standby 字样,那通常是平台兼容性考虑。

我个人建议:别在 Standby 上花太多精力。除非你维护的是十几年前的老硬件,否则直接跳过它,用 Suspend-to-Idle 或者 Suspend-to-RAM 就好。

3. Suspend-to-RAM(S3)

这是最常见的休眠模式。系统把设备状态保存好,CPU 断电,内存进入自刷新模式。恢复时,CPU 重新上电,从固件定义的唤醒向量开始执行,然后内核恢复设备状态。

我遇到过最典型的坑是:显卡驱动在恢复后黑屏。有一次做笔记本项目,合盖休眠再打开,屏幕死活不亮。查了两天才发现是 GPU 的 PCIe 链路在恢复时没有重新协商。解决方案是在 resume 回调里加了个延迟,等链路稳定后再做 modeset。

注意:Suspend-to-RAM 的功耗通常在 100mW - 1W 之间。如果电池供电时发现掉电太快,先检查有没有设备没进入 D3cold 状态。

4. Hibernate(S4)

Hibernate 是把整个内存镜像写到磁盘上,然后完全断电。下次开机时,引导加载程序检测到 hibernation 镜像,直接恢复系统状态。

这里有个容易忽略的问题:内存镜像大小。如果内存是 16GB,那 swap 分区至少得 16GB。我见过有人 swap 分区只分了 8GB,结果 hibernation 写到一半就失败了,系统直接崩溃。

# 检查 swap 分区大小
swapon --show

# 设置 resume 设备(在 GRUB 中)
resume=/dev/sda2

经验之谈:Hibernate 的恢复时间主要花在从磁盘读镜像上。用 SSD 的话,16GB 镜像大概 10-15 秒。如果用机械硬盘,那就得等一两分钟了。

状态切换流程:从用户请求到内核执行

理解了四种状态,我们再来看它们是怎么切换的。我画了一张流程图,把整个调用链串起来:

Linux 休眠状态切换流程 用户写入 /sys/power/state PM core: state_store() suspend_prepare() freeze: 冻结进程 + CPU idle mem: suspend_enter() disk: hibernation_ops suspend_device() suspend_device() save_image() S0ix (冻结状态) S3 (内存自刷新) S4 (磁盘镜像) 恢复流程:中断唤醒 → 恢复设备 → 解冻进程(或从磁盘加载镜像)

整个流程其实可以概括为三步:

  1. 准备阶段:冻结用户进程、通知文件系统同步、注册 suspend notifier
  2. 设备 suspend:按设备依赖树,从叶子节点到根节点,依次调用驱动的 suspend 回调
  3. 进入状态:CPU 执行 wfi(wait for interrupt)指令,或者写磁盘镜像

恢复时顺序反过来:先唤醒 CPU,然后按依赖树反向恢复设备,最后解冻进程。

调试技巧:如果系统休眠后唤不醒,可以在内核启动参数里加 no_console_suspend,这样串口日志在休眠过程中不会关闭。我曾经靠这个参数,抓到了某个 USB 控制器在 suspend 时卡死的现场。

四种状态的选择策略

最后,我根据实际项目经验,给一个简单的选择建议:

  • 嵌入式设备(电池供电):优先用 Suspend-to-Idle。省电效果明显,而且响应快。
  • 笔记本电脑:合盖用 Suspend-to-RAM。如果电池快没电了,自动切换到 Hibernate。
  • 服务器:用 Suspend-to-Idle 就够了。服务器不需要那么深的睡眠,关键是快速响应网络请求。
  • 桌面 PC:Hibernate 适合长时间离开的场景。Suspend-to-RAM 适合短时间离开。

嗯,这一节的内容就到这里。ACPI 状态和 Linux 休眠模式是后续所有调优工作的基础。你把这些概念吃透了,后面讲设备驱动 suspend/resume、电源管理 QoS、wakeup source 的时候,就不会觉得乱了。