系统挂起代码流程

版权:

© 2020 Intel Corporation

作者:

Rafael J. Wysocki <rafael.j.wysocki@intel.com>

为了使系统从工作状态进入某个支持的睡眠状态,至少需要进行一次全局系统级转换。休眠需要多次转换才能达到此目的,但其他睡眠状态,通常称为系统级挂起(或简称系统挂起)状态,只需要一次转换。

对于这些睡眠状态,系统从工作状态到目标睡眠状态的转换也称为系统挂起(在大多数情况下,这表示转换还是系统睡眠状态应该从上下文中明确),而从睡眠状态回到工作状态的转换则称为系统恢复

与系统不同睡眠状态的挂起和恢复转换相关的内核代码流程非常相似,但在挂起到空闲的代码流程与挂起到内存待机睡眠状态相关的代码流程之间存在一些显著差异。

挂起到内存待机睡眠状态的实现离不开平台支持,它们之间的差异归结为平台驱动程序为使其可用而必须提供的挂起和恢复钩子所执行的平台特定操作。除此之外,这些睡眠状态的挂起和恢复代码流程大致相同,因此在下文中它们都被统称为平台依赖挂起状态。

挂起到空闲挂起代码流程

为了将系统从工作状态转换为挂起到空闲睡眠状态,将采取以下步骤

  1. 调用系统级挂起通知器。

    内核子系统可以注册回调函数,以便在即将发生挂起转换和恢复转换完成后调用。

    这允许它们为系统状态的改变做准备,并在返回工作状态后进行清理。

  2. 冻结任务。

    冻结任务主要是为了避免用户空间通过直接暴露给它的 MMIO 区域或 I/O 寄存器进行未经检查的硬件访问,并防止用户空间在转换的下一步进行中进入内核(这可能由于各种原因而出现问题)。

    所有用户空间任务都被拦截,如同它们收到了信号,并进入不可中断睡眠,直到随后的系统恢复转换结束。

    出于特定原因选择在系统挂起期间冻结的内核线程随后被冻结,但它们不会被拦截。相反,它们被期望定期检查是否需要冻结,如果需要则将自己置于不可中断睡眠状态。[但请注意,内核线程可以使用内核空间中可用的锁定和其他并发控制机制来与系统挂起和恢复同步,这比冻结要精确得多,因此后者不推荐用于内核线程。]

  3. 挂起设备并重新配置 IRQ。

    设备分四个阶段挂起,分别称为准备挂起后期挂起无 IRQ 挂起(有关每个阶段具体发生的情况的更多信息,请参阅设备电源管理基础)。

    每个设备在每个阶段都会被访问,但通常在其中不超过两个阶段会进行物理访问。

    后期挂起阶段,每个设备的运行时 PM API 都被禁用,并且在无 IRQ挂起阶段之前,高级(“动作”)中断处理程序被阻止调用。

    之后中断仍然被处理,但它们只被中断控制器确认,而不执行任何在系统工作状态下会触发的设备特定动作(这些动作会推迟到随后的系统恢复转换,如下文所述)。

    与系统唤醒设备相关的 IRQ 被“武装”,以便当其中一个信号事件时启动系统的恢复转换。

  4. 冻结调度器时钟并挂起时间保持。

    当所有设备都已挂起时,CPU 进入空闲循环并进入最深的可用空闲状态。在此过程中,每个 CPU 都“冻结”自己的调度器时钟,以便在 CPU 被另一个中断源唤醒之前,与时钟相关的定时器事件不会发生。

    最后一个进入空闲状态的 CPU 也会停止时间保持,这(除其他外)会阻止高精度定时器在未来的触发,直到第一个被唤醒的 CPU 重新启动时间保持。这使得 CPU 能够一次在深度空闲状态中保持相对较长的时间。

    从这一点开始,CPU 只能被非定时器硬件中断唤醒。如果发生这种情况,它们会回到空闲状态,除非唤醒其中一个 CPU 的中断来自已武装用于系统唤醒的 IRQ,在这种情况下,系统恢复转换会启动。

挂起到空闲恢复代码流程

为了将系统从挂起到空闲睡眠状态转换为工作状态,将采取以下步骤

  1. 恢复时间保持并解冻调度器时钟。

    当其中一个 CPU 被唤醒(通过非定时器硬件中断)时,它离开在先前挂起转换的最后一步中进入的空闲状态,重新启动时间保持(除非它已经被另一个更早醒来的 CPU 重新启动),并且该 CPU 上的调度器时钟被解冻。

    如果唤醒 CPU 的中断被武装用于系统唤醒,则系统恢复转换开始。

  2. 恢复设备并恢复 IRQ 的工作状态配置。

    设备分四个阶段恢复,分别称为无 IRQ 恢复早期恢复恢复完成(有关每个阶段具体发生的情况的更多信息,请参阅设备电源管理基础)。

    每个设备在每个阶段都会被访问,但通常在其中不超过两个阶段会进行物理访问。

    IRQ 的工作状态配置在无 IRQ恢复阶段之后恢复,并且在早期恢复阶段,每个支持运行时 PM API 的设备的运行时 PM API 都被重新启用。

  3. 解冻任务。

    在先前挂起转换的第 2 步中冻结的任务被“解冻”,这意味着它们从当时进入的不可中断睡眠中被唤醒,并且允许用户空间任务退出内核。

  4. 调用系统级恢复通知器。

    这类似于挂起转换的第 1 步,此时调用相同的回调函数集,但传递给它们的是不同的“通知类型”参数值。

平台依赖挂起代码流程

为了将系统从工作状态转换为平台依赖挂起状态,将采取以下步骤

  1. 调用系统级挂起通知器。

    此步骤与上述挂起到空闲挂起转换的第 1 步相同。

  2. 冻结任务。

    此步骤与上述挂起到空闲挂起转换的第 2 步相同。

  3. 挂起设备并重新配置 IRQ。

    此步骤类似于上述挂起到空闲挂起转换的第 3 步,但武装 IRQ 以进行系统唤醒通常对平台没有任何影响。

    有些平台当所有 CPU 都处于足够深的空闲状态且所有 I/O 设备都已进入低功耗状态时,可以在内部进入非常深的低功耗状态。在这些平台上,挂起到空闲可以非常有效地降低系统功耗。

    然而,在其他平台上,低级组件(如中断控制器)需要以平台特定的方式关闭(在平台驱动程序提供的钩子中实现),以实现可比的功耗降低。

    这通常会阻止带内硬件中断唤醒系统,这必须以特殊的平台依赖方式完成。然后,系统唤醒源的配置通常在系统唤醒设备挂起时开始,并由平台挂起钩子在稍后完成。

  4. 禁用非引导 CPU。

    在某些平台上,上述挂起钩子必须在单 CPU 配置的系统上运行(特别是,硬件不能被与平台挂起钩子并行运行的任何代码访问,这些钩子可能会,并且经常会,陷入平台固件以完成挂起转换)。

    出于此原因,CPU 离线/在线(CPU 热插拔)框架用于将系统中除一个 CPU(引导 CPU)之外的所有 CPU 离线(通常,已离线的 CPU 进入深度空闲状态)。

    这意味着所有任务都从这些 CPU 迁移开,并且所有 IRQ 都重新路由到唯一保持在线的 CPU。

  5. 挂起核心系统组件。

    这为核心系统组件(可能)在未来断电做准备,并挂起时间保持。

  6. 平台特定电源移除。

    这预计会切断除内存控制器和 RAM(为了保留后者内容)以及一些指定用于系统唤醒的设备之外的所有系统组件的电源。

    在许多情况下,控制权会传递给平台固件,平台固件应根据需要完成挂起转换。

平台依赖恢复代码流程

为了将系统从平台依赖挂起状态转换为工作状态,将采取以下步骤

  1. 平台特定系统唤醒。

    平台由一个指定系统唤醒设备的信号唤醒(这不一定是带内硬件中断),并且控制权交还给内核(平台固件可能需要在内核重新获得控制权之前恢复平台的工作配置)。

  2. 恢复核心系统组件。

    核心系统组件的挂起时配置被恢复,并且时间保持被恢复。

  3. 重新启用非引导 CPU。

    在先前挂起转换的第 4 步中禁用的 CPU 重新上线,并恢复其挂起时配置。

  4. 恢复设备并恢复 IRQ 的工作状态配置。

    此步骤与上述挂起到空闲恢复转换的第 2 步相同。

  5. 解冻任务。

    此步骤与上述挂起到空闲恢复转换的第 3 步相同。

  6. 调用系统级恢复通知器。

    此步骤与上述挂起到空闲恢复转换的第 4 步相同。