intel_idle CPU 空闲时间管理驱动程序

版权:

© 2020 Intel Corporation

作者:

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

常规信息

intel_idle 是 Linux 内核中 CPU 空闲时间管理子系统 (CPUIdle) 的一部分。它是 Nehalem 和更新版本的 Intel 处理器的默认 CPU 空闲时间管理驱动程序,但对特定处理器型号的支持程度取决于它是否识别该处理器型号,并且还可能取决于来自平台固件的信息。[要了解 intel_idle,必须了解 CPUIdle 的一般工作原理,因此如果您尚未熟悉 CPU 空闲时间管理,现在是时候熟悉一下了。]

intel_idle 使用 MWAIT 指令通知处理器,执行该指令的逻辑 CPU 处于空闲状态,因此可以将处理器的某些功能模块置于低功耗状态。该指令采用两个参数(在目标 CPU 的 EAXECX 寄存器中传递),第一个参数被称为提示,处理器可以使用它来确定可以执行哪些操作(有关详细信息,请参阅 Intel 软件开发人员手册 [1])。因此,intel_idle 拒绝与不支持 MWAIT 指令的处理器(例如,通过平台固件配置菜单)或完全不支持该指令的处理器一起工作。

intel_idle 不是模块化的,因此无法卸载,这意味着将早期配置时间参数传递给它的唯一方法是通过内核命令行。

空闲状态的枚举

处理器将每个 MWAIT 提示值解释为以某种方式重新配置自身以节省能源的许可证。由此产生的处理器配置(功耗降低)被称为 C 状态(在 ACPI 术语中)或空闲状态。有意义的 MWAIT 提示值和与之对应的空闲状态(即处理器的低功耗配置)的列表取决于处理器型号,也可能取决于平台的配置。

为了创建 CPUIdle 子系统所需的可用空闲状态列表(请参阅 空闲状态的表示,位于 CPU 空闲时间管理 中),intel_idle 可以使用两个信息来源:驱动程序本身中包含的不同处理器型号的空闲状态的静态表和系统的 ACPI 表。如果 intel_idle 识别了手头的处理器型号,则始终使用前者;如果给定的处理器型号需要,则使用后者(对于 intel_idle 识别的所有服务器处理器型号都是这种情况),或者如果未识别处理器型号。[有一个模块参数可用于使驱动程序将 ACPI 表与它识别的任何处理器型号一起使用;请参阅下面的 intel_idle 参数。]

如果 ACPI 表将用于构建可用空闲状态列表,intel_idle 首先在系统中与 CPU 对应的 ACPI 对象之一(请参阅 ACPI 规范 [2] 获取 _CST 及其输出包的描述)下查找 _CST 对象。由于 CPUIdle 子系统期望驱动程序提供的空闲状态列表适用于它处理的所有 CPU,并且 intel_idle 被注册为系统中所有 CPU 的 CPUIdle 驱动程序,因此该驱动程序将查找第一个返回至少一个有效空闲状态描述并且其返回包中包含的所有空闲状态均为 FFH(功能固定硬件)类型的 _CST 对象,这意味着 MWAIT 指令有望用于告知处理器它可以进入其中一个状态。然后,假设该 _CST 的返回包适用于系统中的所有其他 CPU,并且从中提取的空闲状态描述存储在来自 ACPI 表的空闲状态的初步列表中。[如果将 intel_idle 配置为忽略 ACPI 表,则会跳过此步骤;请参阅下面的 intel_idle 参数。]

接下来,将可用空闲状态列表中的第一个(索引 0)条目初始化为表示“轮询空闲状态”(目标 CPU 不断获取和执行指令的伪空闲状态),随后的(真实)空闲状态条目按如下方式填充。

如果 intel_idle 识别出手头的处理器型号,则该驱动程序中有一个(静态)空闲状态描述表。在这种情况下,“内部”表是关于空闲状态的主要信息来源,并且其中的信息会复制到可用的最终空闲状态列表中。如果不需要使用 ACPI 表来枚举空闲状态(取决于处理器型号),则默认情况下会启用所有列出的空闲状态(因此 CPUIdle 管理器将在 CPU 空闲状态选择期间考虑所有这些状态)。否则,如果来自 ACPI 表的空闲状态的初步列表中没有匹配的条目,则默认情况下可能不会启用某些列出的空闲状态。在这种情况下,用户空间仍然可以在以后(在每个 CPU 的基础上)借助 sysfs 中的 disable 空闲状态属性来启用它们(请参阅 空闲状态的表示,位于 CPU 空闲时间管理 中)。这基本上意味着如果平台固件(通过 ACPI 表)未公开驱动程序“已知”的空闲状态,则默认情况下可能不会启用这些空闲状态。

如果给定的处理器型号无法被 intel_idle 识别,但它支持 MWAIT,则来自 ACPI 表的初步空闲状态列表将用于构建最终列表,该列表将在驱动程序注册期间提供给 CPUIdle 核心。对于该列表中的每个空闲状态,描述、MWAIT 提示和退出延迟将被复制到最终空闲状态列表中的相应条目。它所代表的空闲状态的名称(将由 sysfs 中的 name 空闲状态属性返回)为“CX_ACPI”,其中 X 是该空闲状态在最终列表中的索引(请注意,X 的最小值为 1,因为 0 保留用于“轮询”状态),其目标驻留时间基于退出延迟值。具体而言,对于 C1 类型的空闲状态,退出延迟值也用作目标驻留时间(为了与 intel_idle 识别的各种处理器型号的大多数“内部”空闲状态表兼容),而对于其他空闲状态类型(C2 和 C3),目标驻留时间值是退出延迟的 3 倍(同样,这是因为它反映了 intel_idle 识别的处理器型号的大多数情况下目标驻留时间与退出延迟的比率)。在这种情况下,最终列表中的所有空闲状态默认启用。

初始化

intel_idle 的初始化首先检查内核命令行选项是否禁止使用 MWAIT 指令。如果是这种情况,则会立即返回错误代码。

下一步是检查驱动程序是否已知处理器型号,这决定了空闲状态枚举方法(请参见上文),以及处理器是否支持 MWAIT(如果不是这种情况,则初始化失败)。然后,通过 CPUID 枚举处理器中的 MWAIT 支持,如果支持级别不符合预期(例如,如果返回的 MWAIT 子状态总数为 0),则驱动程序初始化失败。

接下来,如果驱动程序未配置为忽略 ACPI 表(请参见下文),则从平台固件中提取其提供的空闲状态信息。

然后,为所有 CPU 分配 CPUIdle 设备对象,并按照上文所述创建可用空闲状态列表。

最后,借助 cpuidle_register_driver() 将 intel_idle 注册为系统中所有 CPU 的 CPUIdle 驱动程序,并通过 cpuhp_setup_state() 注册用于配置各个 CPU 的 CPU 在线回调,该回调(除其他事项外)会导致为当时系统中存在的所有 CPU 调用回调例程(每个 CPU 执行其自身的回调例程实例)。该例程为运行它的 CPU 注册一个 CPUIdle 设备(这使得 CPUIdle 子系统能够操作该 CPU),并可选择执行给定处理器型号可能需要的一些特定于 CPU 的初始化操作。

内核命令行选项和模块参数

x86 架构支持代码识别三个与 CPU 空闲时间管理相关的内核命令行选项:idle=pollidle=haltidle=nomwait。如果内核命令行中存在任何一个,则不允许使用 MWAIT 指令,因此 intel_idle 的初始化将失败。

除此之外,intel_idle 本身识别五个模块参数,可以通过内核命令行设置这些参数(它们不能通过 sysfs 更新,因此这是更改其值的唯一方法)。

max_cstate 参数值是在驱动程序注册期间提供给 CPUIdle 核心的空闲状态列表中允许的最大空闲状态索引。它也是 intel_idle 可以使用的最大常规(非轮询)空闲状态数,因此在找到该数量的可用空闲状态后,空闲状态的枚举将终止(如果 max_cstate 更大,则可能使用的其他空闲状态根本不被考虑)。设置 max_cstate 可以阻止 intel_idleCPUIdle 核心公开由于某种原因被认为“过深”的空闲状态,但它是通过使它们在系统关闭并再次启动之前有效地不可见来实现的,这可能并不总是理想的。实际上,只有当在系统启动期间无法启用有问题的空闲状态时,才有必要这样做,因为在系统的工作状态下,可以使用 CPU 电源管理服务质量 (PM QoS) 功能来阻止 CPUIdle 触及这些空闲状态,即使它们已被枚举(请参见CPU 的电源管理服务质量,位于CPU 空闲时间管理中)。将 max_cstate 设置为 0 会导致 intel_idle 初始化失败。

no_acpiuse_acpi 模块参数(如果内核配置了 ACPI 支持,则由 intel_idle 识别)可以设置为使驱动程序完全忽略系统的 ACPI 表,或者分别将它们用于所有已识别的处理器型号(它们默认都未设置,如果设置了 no_acpi,则 use_acpi 无效)。

states_off 模块参数的值(默认为 0)表示要默认禁用的空闲状态列表,其形式为位掩码。

也就是说,在 states_off 值中设置的位的位位置是要默认禁用的空闲状态的索引(如 sysfs 中相应空闲状态目录的名称所反映的那样,state0state1 ... state<i> ...,其中 <i> 是给定空闲状态的索引;请参见空闲状态的表示,位于CPU 空闲时间管理中)。

例如,如果 states_off 等于 3,则驱动程序将默认禁用空闲状态 0 和 1,如果它等于 8,则默认禁用空闲状态 3,依此类推(忽略超出最大空闲状态索引的位位置)。

可以通过用户空间通过 sysfs 启用以这种方式禁用的空闲状态(按每个 CPU)。

ibrs_off 模块参数是一个布尔标志(默认为 false)。如果设置,则用于控制当 CPU 进入空闲状态时是否应关闭 IBRS(间接分支限制推测)。此标志不影响使用增强型 IBRS 的 CPU,后者可以保持开启状态且性能影响很小。

对于某些 CPU,默认情况下将选择 IBRS 作为 Spectre v2 和 Retbleed 安全漏洞的缓解措施。在空闲时保持 IBRS 模式开启可能会对其同级 CPU 产生性能影响。当 CPU 进入深度空闲状态时,默认情况下将关闭 IBRS 模式,但在某些较浅的状态下不会关闭。设置 ibrs_off 模块参数将强制在 CPU 处于任何一个可用的空闲状态时关闭 IBRS 模式。这可能有助于同级 CPU 的性能,但代价是空闲 CPU 的唤醒延迟略高。

核心和封装级别的空闲状态

通常,在支持 MWAIT 指令的处理器中,存在(至少)两个级别的空闲状态(或 C 状态)。一个级别称为“核心 C 状态”,涵盖处理器中的各个核心,而另一个级别称为“封装 C 状态”,涵盖整个处理器封装,并且还可能涉及系统的其他组件(GPU、内存控制器、I/O 集线器等)。

一些 MWAIT 提示值允许处理器仅使用核心 C 状态(最重要的是,对应于 C1 空闲状态的 MWAIT 提示值就是这种情况),但它们中的大多数都允许目标核心(即包含执行带有给定提示值的 MWAIT 的逻辑 CPU 的核心)进入特定的核心 C 状态,然后(如果可能)进入更深层次的特定封装 C 状态。例如,表示 C3 空闲状态的 MWAIT 提示值允许处理器将目标核心置于称为“核心 C3”(或 CC3)的低功耗状态,如果该核心中的所有逻辑 CPU(SMT 兄弟)都执行了带有 C3 提示值(或带有表示更深层次空闲状态的提示值)的 MWAIT,则会发生这种情况。此外(在大多数情况下),它允许处理器将整个封装(可能包括一些非 CPU 组件,如 GPU 或内存控制器)置于称为“封装 C3”(或 PC3)的低功耗状态,如果所有核心都进入了 CC3 状态,并且(可能)满足了一些附加条件,则会发生这种情况(例如,如果 GPU 包含在 PC3 中,则可能需要它处于某种 GPU 特定的低功耗状态才能使 PC3 可达)。

通常,如果没有满足进入相应封装 C 状态的条件,就没有简单的方法使处理器仅使用核心 C 状态,因此,执行带有非核心级提示值(如 C1)的 MWAIT 的逻辑 CPU 必须始终假定这可能会导致处理器进入封装 C 状态。[这就是为什么 intel_idle 中空闲状态“内部”表中与大多数 MWAIT 提示值对应的退出延迟和目标驻留值反映了封装 C 状态的属性。] 如果完全不希望使用封装 C 状态,则必须使用 PM QoSintel_idlemax_cstate 模块参数(如上面所述)来限制允许的空闲状态的范围,使其仅包含核心级的 MWAIT 提示值(如 C1)。

参考