# 让休眠的 CPU 安睡 — S0ix

* 原文：[Let Sleeping CPUs Lie — S0ix](https://freebsdfoundation.org/our-work/journal/browser-based-edition/laptop-desktop/let-sleeping-cpus-lie-s0ix/)
* 作者：Aymeric Wibo

现代笔记本电脑预设了一种魔法。合上盖子或按下睡眠按钮，把它扔进背包，几小时、几天或几周后，它应该会醒来，就像什么都没发生过一样，几乎没有电池消耗。这听起来像是一个相当简单的操作：你知道，你实际上只是要求计算机什么都不做。但在风扇停止转动、屏幕变暗、你的倒影盯着你的那个安静时刻，你的计算机及其所有小部件实际上正在努力完成它们的睡前例行程序。

## 电源管理的历史，或：高效无所事事的漫长道路

让我们漫步计算机电源管理的历史，以便更好地了解我们的来源。

第一批“移动”计算机更像是半便携式机器，如果它们有电池的话，实际上并不期望长时间脱离交流电源。对于 Osborne 1（奥斯本 1），虽然它确实有一个售后电池扩展包，但它的预期是你会在插座之间运行时开启和关闭它。

我们所知道的第一台真正的移动计算机可能是 1983 年发布的 Dulmont Magnum。它首次引入了“挂起到内存”功能，在其情况下意味着组件可以在内存保留在电池供电的 CMOS RAM 中的同时关闭。其他一些 DOS 机器也推出了专有暂停解决方案，但在大多数情况下，它们相当临时。

1985 年，英特尔发布了 i386 芯片，其 SL 变体（本身于 1990 年发布）针对笔记本电脑等节能机器。该芯片引入了 SMM（系统管理模式），能让 CPU 执行从操作系统切换到由 BIOS 写入和锁定的内存区域（SMI 处理程序，从 SMBASE 开始），该区域将以比 ring 0 更高的权限运行以运行电源管理代码。

这种执行切换是由 SMI（系统管理中断）启动的，SMI 可以由硬件（如按下电源按钮）或操作系统通过对特定端口的 I/O 访问来断言。这种电源管理方法存在重大缺陷，尤其是操作系统在这种安排中是被动参与者，完全受固件支配。事实上，如果 SMI 由硬件断言，操作系统将完全被回避，执行将基本上被冻结，因此它完全无法为暂停进入做准备。

SMI 处理程序也是漏洞的丰富来源 —— 特别是 NSA 臭名昭著的 ANT 目录中的许多“植入物” —— 这些植入物远程刷新 BIOS，恶意有效载荷可加载到 SMBASE，然后在系统运行期间定期运行。

1992 年引入的 APM（高级电源管理）最终为 x86 平台提供了标准化的电源管理接口。当发生以前会触发 SMI 的事件时，固件不再按硬件的意愿中断操作系统，而是通过电源管理事件通知操作系统。然后，操作系统决定如何响应（如果有的话），并且可以在通过 APM 函数（通常通过 BIOS 中断 0x15）实际请求固件进行电源转换之前，有足够的时间进行准备。

这是对 SMM 电源管理的绝对巨大改进。不过，一个问题仍然存在：APM 仍然假设 BIOS 供应商会编写没有漏洞的软件，而这些 APM 函数对这些 BIOS 要求很高。

APM 最终在 1996 年被 ACPI（高级配置和电源接口）取代，ACPI 是一个更全面的标准和接口，用于硬件和固件与操作系统通信并提供其功能。它至今仍在现代机器上使用。

ACPI 旨在将更多电源管理责任从固件转移到操作系统端，它将其命名为“OSPM”（OS 指导的电源管理）。它定义了多个“S”状态，这些是系统可能处于的高级电源状态。

最值得注意的是 S0（计算机完全清醒并运行）、S3（计算机挂起到 RAM）、S4（计算机挂起到磁盘，或休眠）和 S5（计算机关闭）。从根本上说，通过 S3 暂停遵循与 APM 类似的方法，因为“操作系统被告知电源事件，然后决定并准备暂停，最后要求固件实际暂停”的流程得以保持。

但随着我们的硬件进步，我们作为被宠坏的消费者对它的期望越来越高，这种流程，实际上整个“系统要么完全开启要么暂停”的范式，开始显示出其局限性。

随着智能手机和类似设备的出现，我们期望我们的机器即使在盖子关闭时也始终保持连接。我们希望收到来自我们最喜欢的 AI 垃圾内容创作者的新上传通知，来自我们订阅的第 500 个新闻通讯的电子邮件，并唤醒机器以通知我们日历事件。

要做到这一点，我们需要每隔一段时间或在收到局域网唤醒数据包时恢复，只是为了，例如，更深入地查看它，决定我们是否真的想让系统真正唤醒，或者打盹并立即回到睡眠状态。而这是 S3 过于僵化而无法适当适应的用例：S3 在能源消耗和固件重新启动所有内容所需的时间方面非常昂贵。

S3 是全有或全无的；如果我们想从它唤醒，我们几乎每次都必须表现得好像我们完全恢复一样。

要是有解决方案就好了…

## 进入… S0ix

大约在 2018 年，在让计算机尽可能高效、更好、更快地绝对无所事事的永恒追求中，英特尔引入了 S0ix。S0ix 实际上是一组状态（S0i1、S0i2、S0i3，可能还有其他），但关键是它们都将系统保持在 S0 —— 或清醒 —— ACPI 状态。“x”值越大，睡眠越深，节能越多，目标通常是 S0i3。S0i0 有时被称为等同于 S0，但我认为这有点令人困惑，所以我不会使用那个术语。

与过去的 S3 或 APM 相反，操作系统实际上从未明确要求固件进入 S0ix（嗯，实际上在实践中它有点这样做，但我们稍后会谈到）。相反，它只是创建进入 S0ix 状态所需的条件，然后让固件自行决定何时将 CPU 转换到给定的 S0ix 状态。

那些必要条件是：

* 机器上的设备（即 GPU 或 USB 控制器等各种组件）为给定的 S0ix 状态充分断电。
* 所有 CPU 都处于空闲状态（即无所事事）并进入低功耗空闲状态。

让我们看看我们如何为设备供电。

启用 S0ix 的机器公开一个称为 SPMC（系统电源管理控制器，有时也称为 PEP）的 ACPI 设备。在 ACPI 领域中称为 DSM（设备特定方法）的函数可以在这个 SPMC 设备上调用，以向平台提供关于操作系统暂停意图的提示，还可以用于获取关于设备需要处于什么最低电源状态才能让固件想要进入 S0ix 状态的约束。

设备电源状态被称为“D 状态”，它们从 D0（完全开启）到 D3hot（关闭但通电）和 D3cold（关闭但不通电）。我们通常希望将设备转换到 D3cold，即使其 D 状态约束不要求它这样做，如果进行完全暂停，但我们可能希望将设备保持在更清醒的 D 状态以作为唤醒设备 —— 当然，如果我们关闭旨在向系统发出我们想要唤醒的信号的设备，我们将永远无法唤醒。

所以，为了简单起见，SPMC 的这些约束只是告诉我们在暂停期间保持设备开启是否会阻碍 S0ix 进入。至于空闲 CPU，我们通过进入一种称为挂起到空闲（也称为 s2idle）的特殊软件状态来做到这一点，这几乎取代了 S3 的位置。本质上，我们只是通过停止调度器时钟并暂停操作系统正在执行的任何操作，将系统中的所有 CPU 置于低功耗空闲状态。这些低功耗空闲状态被称为 C 状态，它们的进入方法以及其他信息要么从较旧的 `_CST` ACPI 对象获得，要么从其更现代的替代品 `_LPI` 对象获得。

通过中断 CPU 来退出空闲状态，所以在空闲之前，我们禁用所有 CPU 中断，除了我们实际想要用来唤醒 CPU 的那些。这些唤醒中断通常只是 SCI（ACPI 系统控制中断），它让我们知道 GPE（通用事件），这可能是按下电源按钮或打开盖子（通过“GPE 编号”粗略区分）。

SCI 可能会因各种非唤醒原因而触发，因此我们还尽可能配置 ACPI，仅在实际唤醒 GPE 时触发 SCI，这有时说起来容易做起来难。但我们稍后会谈到。

理论上，如果两个条件都满足，固件可以切断 CPU 包的电源并进入 S0ix 状态。但在实践中，事情要复杂一些，并且是硬件特定的。

例如，在 AMD 系统上，电源管理固件（他们称之为 PMFW）在一个称为 SMU（系统管理单元）的小型片上 LatticeMico32 核心上运行，您最近也会看到它被称为“MP1”芯片。这最终负责 CPU 包的时钟和电源门控，从而负责包进出 S0ix 状态的转换。

当操作系统实际想要暂停时，它必须向 PMFW 发送一个小命令，提示它进入 S0i3。除了常规的 S0ix 要求外，SMU 还有自己必须满足的要求。这些要求中最值得注意的是 GPU 和 USB4 控制器：最近版本的 amdgpu 驱动程序在进入 S3 或 S0ix 时使用不同的路径，而 USB4 控制器需要由 NHI 驱动程序显式关闭电源。即使操作系统不支持 USB4，这也是必要的，因为控制器由预 OS 连接管理器（USB4 在 OS 启动前工作所需）激活，并在 OS 启动时保持活动状态。

SMU 还传达调试信息，让用户知道系统是否成功进入 S0i3，如果没有，是什么阻止了它这样做。这些信息通过 FreeBSD 上的 `dev.amdsmu.0.ip_blocks` 和 `dev.amdsmu.0.metrics` sysctl 树公开。

由于 PMFW 纯粹在片上运行，为了让更广泛的平台知道我们进入 S0ix 的意愿，我们必须通过前面提到的“显示关闭”和“进入”通知 DSM 告诉 SPMC 我们想要进入暂停。这通常会提示平台向用户提供系统已暂停的视觉反馈，例如在 Framework 笔记本电脑上缓慢淡出和淡入电源按钮 LED。它还可以做一些更微妙的事情，例如减少一些 GPE 的频率，否则这些 GPE 会不断从空闲状态唤醒 CPU，从而从 S0i3 唤醒。

事实上，在 Framework 笔记本电脑上，EC（嵌入式控制器）在正常操作中每秒发送一次电池设备的 GPE，我们无法在进入暂停前抑制这一点，因为电池 GPE 与盖子 GPE 共享一个编号。所以如果我们想禁用那些嘈杂的电池 GPE，我们就必须牺牲能够用盖子唤醒系统的能力，这不好。

提示 SPMC 进入暂停告诉 EC 仅每分钟发送一次电池 GPE，考虑到我们可以多快退出 S0i3 并重新进入，这完全没问题。让我们的系统每隔一段时间唤醒实际上对于跟踪我们在挂起到空闲期间的功耗和电池状态随时间的变化是相当方便的（尽管我们可能可以用 RTC 警报复制这一点，但我离题了）。

由于 CPU 可能会因各种不一定意味着我们想唤醒整个系统的原因从空闲状态唤醒，我们在 CPU 空闲时将其置于“s2idle 循环”中。这允许我们实际查看从系统获得的 GPE 并决定是否值得唤醒。这个 GPE 可能像嘈杂的电池事件一样简单，也可能像来自 NIC 的局域网唤醒事件一样复杂，如前所述，我们可能想在决定是否唤醒系统之前更详细地检查数据包。

总而言之，在启用 S0ix 的系统上进入暂停的完整流程如下：

* 暂停所有用户空间执行和挂载的文件系统。
* 遍历设备层次结构并暂停所有设备，通常通过将它们设置为 D3 状态之一，并至少将它们设置为 SPMC 报告的 S0ix 所需的最低 D 状态。
* 停止所有 CPU 上的调度器时钟并禁用所有中断，除了 SCI。
* 启动“s2idle 循环”并让 CPU 空闲。

恢复几乎是前三个步骤的 reverse。

现在，亲爱的读者，在这一点上，你可能会对自己说：“好吧，这很好，但我不需要我的笔记本电脑在睡眠时响应通知。为什么我要费心这个新的 S0ix 东西，而我可以坚持使用 S3？”，对此的不幸回答是，现在大多数现代系统根本不再提供 S3 支持，已经完全用 S0ix 取代了它。

对供应商来说，同时支持两者非常复杂，所以大多数人都不费心，尤其是考虑到大多数主要操作系统现在都很好地支持 S0ix。幸运的是，FreeBSD 正在顺利添加对挂起到空闲和 S0ix 的支持。

您可以通过检查 `kern.power.supported_stype` sysctl 来查看您的系统是否支持挂起到空闲，并且您可以通过 sysctl `hw.acpi` 配置 ACPI 在某些事件上进入挂起到空闲。然后，如果您的系统支持它并且 FreeBSD sup- ports 它，S0ix 状态应该会自动进入。

目前，没有统一的方法来检查这一点，但如果您在带有 SMU 芯片的 AMD 系统上，您可以使用前面提到的 sysctl SMU。

## 休眠和 ACPI S4

在所有这些使 S0i3 工作的工作之后，您可能会有点失望地了解到，在 S0i3（以及 S3）中，您甚至没有获得很好的节能效果。在这方面，系统之间存在很大差异，但系统在 S0i3 中只能持续几天并不罕见。这就是休眠（或挂起到磁盘）的用武之地。

当挂起到内存（如 S3 或 S0i3）时，您的机器仍然必须不断刷新 DRAM，这不是一个微不足道的功耗来源，并且随着您向系统添加更多内存，这实际上会增加。通过挂起到磁盘，您将内存复制到硬盘并有效地完全关闭计算机 —— 包括 DRAM —— 就像您正在关闭它一样（好吧，就所有意图和目的而言，您正在关闭它）。这意味着您的机器几乎不消耗任何电力。唯一积极消耗电力的组件是您的机器如果有的话，如 EC，但这与您的机器断电时的情况相同。

在 ACPI S 状态术语中，休眠是 S4。

在最开始，当恐龙还在地球上漫游时，BIOS 供应商通过一种称为 S4BIOS 的东西在固件中实现了这一点，以简化采用。在这种安排中，BIOS 完全负责将 OS 内存复制到磁盘，就像 APM 之前的挂起到 RAM 一样。

虽然 FreeBSD 支持 S4BIOS，但它已经很长时间不相关了，因为现在大多数操作系统只支持常规的 S4 休眠。在 S4 中，操作系统完成了将其内存复制到磁盘的大部分工作，而固件基本上只是关闭机器。事实上，你完全可以在没有固件 S4 支持的情况下实现休眠，只需使用平台的关机功能（这意味着 ACPI 平台上的 S5，或非 ACPI 平台上的其他功能）。

休眠的主要缺点是进入和退出时的延迟。你必须等待操作系统将所有页面复制到磁盘，然后在恢复时，经历大部分启动过程，最后将这些页面从磁盘恢复到内存。但我们实际上可以将 S0ix 和休眠的优点结合成一种混合方法！

通过设置 RTC 警报，我们可以让系统在 S0i3 中停留，例如，一个小时，然后再次唤醒并进入休眠，如果用户在此期间没有唤醒系统。这意味着如果我们在暂停前不到一小时唤醒，我们会从 S0ix 获得超快的恢复时间，但如果我们让系统暂停过夜或多天，我们会获得休眠的大大改善的电池寿命。这意味着如果我们在一小时内没有唤醒它，下次唤醒机器时恢复会花费更长的时间，但考虑到所有因素，这是一个相当好的折衷方案。

我们还可以用这种混合方法做其他很酷的事情，比如配置 `ACPI_BLT`（电池电量阈值）控制方法，当电池达到某个阈值（比如 5%）时唤醒机器，这样我们就可以进入休眠并防止电池耗尽时硬关机，如果我们在 S0ix 中。

## FreeBSD 中的现状

FreeBSD 基金会作为其“笔记本电脑支持和可用性项目”努力的一部分，一直在带头赞助 S0ix 和休眠的工作。大部分挂起到空闲和 S0ix 代码已经落地，休眠最近已经启动，但进入 S0i3 尤其是从它恢复还不完全可靠，并且使 SMU 满意的 USB4 驱动程序的必要部分尚未落地。

此外，大部分焦点都放在 AMD 系统上，而不是英特尔（尽管最近开始了对初始 PMC 驱动程序 —— 他们的 SMU 等效物 —— 的工作）。即使在 AMD 平台中，到目前为止唯一的测试也发生在 Phoenix 上，`amdsmu` 驱动程序理论上只支持 Cezanne、Rembrandt 和 Phoenix 芯片。

唯一经过测试的带有正在开发中的 USB4 驱动程序（`thunderbolt`）的 USB4 控制器是 Pink Sardine（在 Phoenix 系统上发现），尽管任何通用无怪癖的 USB4 控制器也应该得到支持。

在撰写本文时，大部分剩余工作都集中在可靠地进入最深的 C 状态（Phoenix 系统上的 C3），因为 CPU 经常意外唤醒，这反过来导致系统退出 S0i3。但手指交叉，FreeBSD 很快就会支持现代待机和休眠，完全追赶上 Linux 和 Windows 等！

有关 S0ix 及其在 FreeBSD 中的实现的更技术性和重点的介绍，以及一些额外的参考资料，您可以查看我的博客：<https://obiw.ac/s0ix>

***

**Aymeric Wibo** 是一位来自比利时的软件自由职业者，专门从事内核开发和图形编程。他从高中开始就参与 FreeBSD，目前为 FreeBSD 基金会工作，负责笔记本电脑电源管理，并在 Klara Inc. 工作。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://freebsd-journal-cn.bsdcn.org/2026010203-bi-ji-ben-yu-zhuo-mian/let-sleeping-cpus-lie-s0ix.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
