CPU 空闲时间管理

版权:

© 2019 Intel Corporation

作者:

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

CPU 空闲时间管理子系统

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

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

CPUIdle 的设计是模块化的,并且基于避免代码重复的原则,因此原则上不需要依赖于硬件或平台设计细节的通用代码与和硬件交互的代码是分开的。它通常分为三个功能单元:governor 负责选择要请求处理器进入的空闲状态,drivers 将 governor 的决策传递给硬件,core 为它们提供一个通用框架。

CPU 空闲时间 Governor

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

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

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

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

注册后,CPUIdle governor 无法注销,因此将它们放入可加载内核模块是不切实际的。

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

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

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

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

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

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

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

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,governor 的注册才能成功)。

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

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

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

CPU 空闲时间管理驱动程序

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

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

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

现有 CPUIdle governor 使用 struct cpuidle_state 中的三个字段进行与空闲状态选择相关的计算

target_residency

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

exit_latency

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

flags

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

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

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

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

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

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

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

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

然而,在注册驱动程序之后,还需要借助 cpuidle_register_device() 为要由给定 CPUIdle 驱动程序处理的所有逻辑 CPU 注册 struct cpuidle_device 对象,这与 cpuidle_register() 不同,cpuidle_register_driver() 不会自动执行此操作。因此,使用 cpuidle_register_driver() 注册自身的驱动程序还必须注意根据需要注册 struct cpuidle_device 对象,因此通常建议在所有情况下都使用 cpuidle_register() 进行 CPUIdle 驱动程序注册。

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

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

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