CPU 空闲时间管理

版权:

© 2019 英特尔公司

作者:

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

CPU 空闲时间管理子系统

每次系统中一个逻辑 CPU(即负责提取和执行指令的实体:如果存在,则为硬件线程或处理器内核)在中断或等效的唤醒事件后处于空闲状态,这意味着除了与其关联的特殊“空闲”任务外,没有任务在其上运行,这时就有机会为它所属的处理器节省能量。这可以通过使空闲的逻辑 CPU 停止从内存中提取指令,并将它所依赖的处理器的一些功能单元置于空闲状态来实现,在空闲状态下,它们将消耗更少的功率。

然而,原则上在这种情况下可以使用多种不同的空闲状态,因此可能需要找到最合适的空闲状态(从内核的角度来看),并要求处理器使用(或“进入”)该特定的空闲状态。这就是内核中 CPU 空闲时间管理子系统(称为 CPUIdle)的作用。

CPUIdle 的设计是模块化的,基于避免代码重复的原则,因此原则上无需依赖其中的硬件或平台设计细节的通用代码与和硬件交互的代码是分开的。它通常分为三个功能单元:调速器,负责选择要让处理器进入的空闲状态;驱动程序,将调速器的决策传递给硬件;以及核心,为它们提供一个通用框架。

CPU 空闲时间调速器

CPU 空闲时间 (CPUIdle) 调速器是一组策略代码,当系统中某个逻辑 CPU 处于空闲状态时会调用它。它的作用是选择一个空闲状态,以便让处理器进入以节省一些能量。

CPUIdle 调速器是通用的,并且它们中的每一个都可以在 Linux 内核可以运行的任何硬件平台上使用。因此,它们操作的数据结构也不能依赖于任何硬件架构或平台设计细节。

调速器本身由一个 struct cpuidle_governor 对象表示,该对象包含四个回调指针,enabledisableselectreflect,一个如下所述的 rating 字段以及一个用于标识它的名称(字符串)。

为了使调速器可用,需要通过调用 cpuidle_register_governor() 并传入指向该对象的指针作为参数来将该对象注册到 CPUIdle 核心。如果成功,这会导致核心将该调速器添加到可用调速器的全局列表中,并且如果它是列表中的唯一一个(即,列表之前是空的)或者它的 rating 字段的值大于当前正在使用的调速器的该字段的值,或者新调速器的名称作为 cpuidle.governor= 命令行参数的值传递给内核,则将从那时起使用新的调速器(一次只能使用一个 CPUIdle 调速器)。此外,用户空间可以通过 sysfs 在运行时选择要使用的 CPUIdle 调速器。

一旦注册,CPUIdle 调速器就不能被注销,因此将它们放入可加载的内核模块是不切实际的。

CPUIdle 调速器和核心之间的接口由四个回调组成

enable
int (*enable) (struct cpuidle_driver *drv, struct cpuidle_device *dev);

此回调的作用是准备调速器以处理由 dev 参数指向的 struct cpuidle_device 对象表示的(逻辑)CPU。由 drv 参数指向的 struct cpuidle_driver 对象表示要与该 CPU 一起使用的 CPUIdle 驱动程序(除其他外,它应包含 struct cpuidle_state 对象的列表,这些对象表示可以要求持有给定 CPU 的处理器进入的空闲状态)。

它可能会失败,在这种情况下,它应该返回一个负的错误代码,这会导致内核在该 CPU 上运行架构特定的默认空闲 CPU 代码,而不是 CPUIdle,直到再次为该 CPU 调用 ->enable() 调速器回调。

disable
void (*disable) (struct cpuidle_driver *drv, struct cpuidle_device *dev);

调用此回调以使调速器停止处理由 dev 参数指向的 struct cpuidle_device 对象表示的(逻辑)CPU。

它应该反转上次为目标 CPU 调用 ->enable() 回调时所做的任何更改,释放该回调分配的所有内存等。

select
int (*select) (struct cpuidle_driver *drv, struct cpuidle_device *dev,
               bool *stop_tick);

调用此回调以选择处理器持有由 dev 参数指向的 struct cpuidle_device 对象表示的(逻辑)CPU 的空闲状态。

要考虑的空闲状态列表由 drv 参数指向的 struct cpuidle_driver 对象(表示要与手头的 CPU 一起使用的 CPUIdle 驱动程序)所持有的 struct cpuidle_state 对象的 states 数组表示。此回调返回的值被解释为该数组的索引(除非它是负的错误代码)。

stop_tick 参数用于指示在要求处理器进入所选的空闲状态之前是否停止调度器节拍。当它指向的 bool 变量(在调用此回调之前设置为 true)被清除为 false 时,将要求处理器进入所选的空闲状态,而不会在该 CPU 上停止调度器节拍(但是,如果该 CPU 上的节拍已经停止,则不会在要求处理器进入空闲状态之前重新启动)。

此回调是强制性的(即,为了使调速器的注册成功,struct cpuidle_governor 中的 select 回调指针不能为 NULL)。

reflect
void (*reflect) (struct cpuidle_device *dev, int index);

调用此回调,以允许调速器评估由 ->select() 回调(上次调用时)所做的空闲状态选择的准确性,并可能使用该结果来提高将来空闲状态选择的准确性。

此外,在选择空闲状态时,要求 CPUIdle 调速器考虑处理器唤醒延迟的电源管理服务质量 (PM QoS) 约束。为了获得给定 CPU 的当前有效 PM QoS 唤醒延迟约束,CPUIdle 调速器应将 CPU 的编号传递给 cpuidle_governor_latency_req()。然后,调速器的 ->select() 回调不能返回 exit_latency 值大于该函数返回的数字的空闲状态的索引。

CPU 空闲时间管理驱动程序

CPU 空闲时间管理 (CPUIdle) 驱动程序在 CPUIdle 的其他部分和硬件之间提供了一个接口。

首先,CPUIdle 驱动程序必须填充结构体 cpuidle_driver 对象中的 states 数组,该结构体包含表示驱动程序的 cpuidle_state 对象。接下来,此数组将表示处理器硬件可以进入的可用空闲状态列表,该列表由给定驱动程序处理的所有逻辑 CPU 共享。

states 数组中的条目应按照 cpuidle_state 结构体中 target_residency 字段的值升序排序(即,索引 0 应对应于 target_residency 值最小的空闲状态)。[由于 target_residency 值应反映持有它的 cpuidle_state 对象所代表的空闲状态的“深度”,因此此排序顺序应与按空闲状态“深度”升序排序相同。]

现有的 CPUIdle 调速器使用结构体 cpuidle_state 中的三个字段来进行与空闲状态选择相关的计算

target_residency

在此空闲状态中花费的最短时间(包括进入此状态所需的时间,这可能相当长),以节省比在相同时间内停留在较浅的空闲状态下更多的能量,以微秒为单位。

exit_latency

CPU 请求处理器进入此空闲状态后,从唤醒到开始执行第一条指令所需的最大时间,以微秒为单位。

flags

表示空闲状态属性的标志。目前,调速器仅使用 CPUIDLE_FLAG_POLLING 标志,如果给定对象不代表真正的空闲状态,而是代表一个软件“循环”的接口,可以使用该接口来避免请求处理器进入任何空闲状态。[在特殊情况下,CPUIdle 核心还会使用其他标志。]

cpuidle_state 结构体中的 enter 回调指针(不得为 NULL)指向要执行的例程,以便请求处理器进入此特定空闲状态

void (*enter) (struct cpuidle_device *dev, struct cpuidle_driver *drv,
               int index);

它的前两个参数分别指向表示运行此回调的逻辑 CPU 的 cpuidle_device 结构体对象和表示驱动程序本身的 cpuidle_driver 结构体对象,最后一个参数是驱动程序的 states 数组中表示要请求处理器进入的空闲状态的 cpuidle_state 条目的索引。

cpuidle_state 结构体中类似的 ->enter_s2idle() 回调仅用于实现系统范围的暂停到空闲电源管理功能。它与 ->enter() 的区别在于,它在任何时候都不得重新启用中断(即使是临时的),也不得尝试更改时钟事件设备的状态,而 ->enter() 回调有时可能会这样做。

一旦 states 数组被填充,则必须将其中有效条目的数量存储在表示驱动程序的 cpuidle_driver 结构体对象的 state_count 字段中。此外,如果 states 数组中的任何条目表示“耦合”的空闲状态(即,只有当多个相关的逻辑 CPU 都处于空闲状态时才能请求的空闲状态),则 cpuidle_driver 结构体中的 safe_state_index 字段必须是一个非“耦合”空闲状态的索引(即,只有一个逻辑 CPU 处于空闲状态时也可以请求的状态)。

除此之外,如果给定的 CPUIdle 驱动程序仅处理系统中逻辑 CPU 的子集,则其 cpuidle_driver 结构体对象中的 cpumask 字段必须指向将由其处理的 CPU 集(掩码)。

CPUIdle 驱动程序只有在注册后才能使用。如果驱动程序的 states 数组中没有“耦合”的空闲状态条目,则可以通过将驱动程序的 cpuidle_driver 结构体对象传递给 cpuidle_register_driver() 来完成注册。否则,应使用 cpuidle_register() 来实现此目的。

但是,还需要在驱动程序注册后,借助 cpuidle_register_device() 为给定 CPUIdle 驱动程序处理的所有逻辑 CPU 注册 cpuidle_device 结构体对象,并且 cpuidle_register_driver()cpuidle_register() 不同,它不会自动执行此操作。因此,使用 cpuidle_register_driver() 注册自身的驱动程序还必须负责根据需要注册 cpuidle_device 结构体对象,因此通常建议在所有情况下都使用 cpuidle_register() 来注册 CPUIdle 驱动程序。

注册 cpuidle_device 结构体对象会导致创建 CPUIdle sysfs 接口,并为它所代表的逻辑 CPU 调用调速器的 ->enable() 回调,因此必须在注册将处理相关 CPU 的驱动程序之后进行。

当不再需要 CPUIdle 驱动程序和 cpuidle_device 结构体对象时,可以将其注销,这允许释放与其关联的一些资源。由于它们之间的依赖关系,在调用 cpuidle_unregister_driver() 注销驱动程序之前,必须使用 cpuidle_unregister_device() 注销给定 CPUIdle 驱动程序处理的所有代表 CPU 的 cpuidle_device 结构体对象。或者,可以调用 cpuidle_unregister() 来注销 CPUIdle 驱动程序以及所有代表由其处理的 CPU 的 cpuidle_device 结构体对象。

CPUIdle 驱动程序可以响应导致可用处理器空闲状态列表发生修改的运行时系统配置更改(例如,当系统的电源从交流电切换到电池或反之亦然时,可能会发生这种情况)。收到此类更改的通知后,CPUIdle 驱动程序应调用 cpuidle_pause_and_lock() 以暂时关闭 CPUIdle,然后对所有受该更改影响的代表 CPU 的 cpuidle_device 结构体对象调用 cpuidle_disable_device()。接下来,它可以根据系统的新配置更新其 states 数组,对所有相关的 cpuidle_device 结构体对象调用 cpuidle_enable_device(),并调用 cpuidle_resume_and_unlock() 以允许再次使用 CPUIdle