Linux 内核 CPU 空闲时间管理(CPUIdle)完全指南:Governor、Driver 与 PM QoS
2026/9/10 5:25:42 网站建设 项目流程

Linux 内核 CPU 空闲时间管理(CPUIdle)完全指南:Governor、Driver 与 PM QoS

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

导读

本文基于 Linux 内核官方文档 Documentation/admin-guide/pm/cpuidle.rst,系统讲解内核的CPU 空闲时间管理(CPU Idle Time Management,即 CPUIdle 子系统):处理器如何通过进入空闲状态(C-states)降低功耗、idle loop 如何运作、menu/TEO/ladder/haltpoll四个 governor 的选型算法、idle state 在sysfs中的完整表示,以及 PM QoS 与内核命令行参数对空闲状态选择的控制方式。读完本文,你将掌握如何通过sysfs与内核启动参数诊断、调整和优化 Linux 系统的 CPU 空闲行为,并理解其背后的内核源码实现(drivers/cpuidle/drivers/idle/intel_idle.cdrivers/acpi/processor_idle.c等)。

基本概念:处理器空闲状态与逻辑 CPU

现代处理器通常能够进入一种"程序执行被挂起"的状态:不再从内存取指、不再执行指令。这些状态就是处理器的空闲状态(idle states)。由于空闲状态下部分处理器硬件不被使用,进入空闲状态可以降低处理器功耗,从而节约能量。CPU 空闲时间管理正是一类以利用处理器空闲状态来节能为目的的能效特性。

逻辑 CPU(Logical CPUs)

CPU 空闲时间管理所操作的"CPU",是CPU 调度器视角下的逻辑单元——它们不一定是独立的物理实体,而可能是对软件呈现为单个单核处理器的接口。概括起来有三种情形:

  1. 单处理器:整个处理器同一时刻只能跟随一条指令序列(一个程序),此时处理器本身就是一个 CPU。请求进入空闲状态会作用于整个处理器。
  2. 多核处理器:每个核至少能同时运行一个程序,核之间可能共享缓存,但多数时间物理上并行工作。此时每个核是一个 CPU,请求进入空闲状态作用于发出请求的核;若该核所属的更大单元(如 package、cluster)中其他核都已处于空闲状态,那么这一请求还可能触发整个更大单元进入空闲状态(可能涉及一整套包含该核的层级单元)。
  3. 硬件线程(hardware threads):多核处理器中的每个核可能在同一时间帧内跟随多条指令序列(如 Intel 的 hyper-threads)。此时每个硬件线程被视为一个 CPU。某个硬件线程请求进入空闲状态,只会停止它自己;只有当同一核内其他硬件线程也都请求进入空闲状态时,该核才可能单独进入空闲状态,或连同其所属更大单元整体进入。

空闲 CPU(Idle CPUs)

在 Linux 内核中,当一个逻辑 CPU 上除了特殊的 "idle" 任务外没有任何可运行任务时,该 CPU 就被视为空闲。任务(task)是 CPU 调度器对"工作"的表示:由一段代码、要操作的数据以及每次运行前需要加载到处理器中的上下文信息组成。任务处于runnable状态时,只要有可用的 CPU 就可以运行其代码;当 CPU 上不再有可运行任务时,特殊的 "idle" 任务变为 runnable,该 CPU 即被看作空闲。

换句话说,Linux 中空闲 CPU 运行的是 "idle" 任务的代码,称为idle loop。这段代码可能让处理器进入其支持的空闲状态以节能;但如果处理器不支持任何空闲状态、距离下一次唤醒事件的时间不足以进入空闲状态、或存在严格延迟约束不允许使用任何空闲状态,CPU 就只能在循环中执行或多或少"无用的指令",直到被分配新任务。

The Idle Loop:Governor 与 Driver 的分工

idle loop 的每次迭代包含两个主要步骤:

  1. 调用 CPUIdle 子系统中的governor(调节器),为 CPU 选择一个希望硬件进入的空闲状态;
  2. 调用 CPUIdle 子系统中的driver(驱动),实际请求处理器硬件进入 governor 选出的空闲状态。

Governor 的角色与 idle state 的抽象表示

governor 的职责是找到最适合当前条件的状态。为此,所有逻辑 CPU 可请求硬件进入的空闲状态被以与平台/架构无关的抽象方式表示,并组织成一个一维(线性)数组。该数组由与平台匹配的 CPUIdle driver 在内核初始化时构建并提供。这样 governor 就能与底层硬件解耦,适用于 Linux 内核可运行的所有平台。

每个空闲状态对象由两个 governor 决策时需要考虑的参数刻画:

  • 目标驻留时间(target residency):硬件必须在给定状态下驻留(含进入该状态的时间,该时间可能相当可观)的最小时间,才能比进入更浅的空闲状态节省更多能量(状态"深度"大致对应处理器在该状态下的功耗);
  • 退出延迟(exit latency,最坏情况):CPU 从被唤醒到开始执行第一条指令所需的最大时间。通常退出延迟还必须涵盖进入该状态的耗时——因为若唤醒发生在硬件正在进入该状态时,状态必须完整进入后才能有序退出。

影响 governor 决策的两类信息

  1. 最近定时器事件时间:内核知道距离最近定时器事件还有多久,这个时间可以被精确获知。它是 CPU 所依赖的硬件能够停留在空闲状态的最大时间(含进出状态耗时)。然而 CPU 随时可能被非定时器事件唤醒(且通常无法预知),因此 governor 只能在实际被唤醒后看到 CPU 真正的空闲时长(下文称为idle duration),并结合距离最近定时器的时间来估计未来的空闲时长。如何使用这些信息取决于 governor 实现的具体算法——这正是 CPUIdle 子系统存在多个 governor 的根本原因。

可用的 Governor 与 Driver

内核提供四个 CPUIdle governor:menuTEO(Timer Events Oriented)、ladderhaltpoll。默认使用哪个取决于内核配置,特别是调度器 tick 能否被idle loop 停止 <#idle-cpus-and-tick>_。可通过/sys/devices/system/cpu/cpuidle/available_governors查看可用 governor,并可在运行时切换;当前使用的 governor 名称可从/sys/devices/system/cpu/cpuidle/current_governor_ro/sys/devices/system/cpu/cpuidle/current_governor读取。

CPUIdle driver 的选择通常取决于平台,但有的平台存在多个匹配驱动。例如 Intel 平台常见两个可用的驱动:intel_idle(内含硬编码的空闲状态信息)与acpi_idle(从系统 ACPI 表读取信息)。即使如此,初始化时选定的 driver 之后无法更换,因此必须在早期做出决策(Intel 平台上,若intel_idle被禁用或无法识别处理器,则使用acpi_idle)。当前使用的 driver 名称可从/sys/devices/system/cpu/cpuidle/current_driver读取。

在源码层面,idle loop 正是通过 drivers/cpuidle/cpuidle.c 中的cpuidle_select()(调用 governor 选择状态)与cpuidle_enter()(调用 driver 进入状态)完成上述两步;cpuidle_enter_state()则负责进入具体状态并更新统计信息。四个 governor 的实现分别位于 drivers/cpuidle/governors/menu.c、teo.c、ladder.c 和 haltpoll.c。

空闲 CPU 与调度器 tick(Scheduler Tick)

调度器 tick是一个周期性触发的定时器,用于实现 CPU 调度器的时间共享策略:多个可运行任务共享一个 CPU 时,每个任务获得一段 CPU 时间片,用完后 CPU 应切换运行另一个任务。tick 的作用就是强制这种切换发生(尽管当前运行任务可能不愿主动让出 CPU)。这不是 tick 的唯一作用,但却是其存在的主要原因。

从 CPU 空闲时间管理的角度看,tick 是有问题的:它周期性地、相对频繁地触发(取决于内核配置,tick 周期长度在 1ms 到 10ms 之间)。如果允许 tick 在空闲 CPU 上触发,那么请求硬件进入目标驻留时间超过 tick 周期的空闲状态就没有意义;同时任何 CPU 的 idle duration 都不会超过 tick 周期长度,因 tick 唤醒而进出空闲状态消耗的能量也会被浪费。

幸运的是,空闲 CPU(按定义)除了特殊的 "idle" 任务外没有其他任务,从调度器角度看其 CPU 时间的唯一使用者就是 idle loop,无需在多个可运行任务间共享时间,因此 tick 的首要存在理由在空闲 CPU 上消失——原则上可以完全停止空闲 CPU 上的调度器 tick,尽管这不一定总是值得。

是否停止 tick 的决策

是否在 idle loop 中停止调度器 tick,取决于 governor 的预期:

  • 若 tick 范围内已有另一个(非 tick 的)定时器即将触发,停止 tick 显然浪费(尽管此时定时器硬件可能无需重编程);
  • 若 governor 预期 tick 范围内会有非定时器唤醒,停止 tick 既不必要甚至有害:此时 governor 会选择目标驻留时间在预期唤醒时间之内的较浅状态。若唤醒确实很快发生,停止 tick 是浪费,且定时器硬件需要重编程(代价高昂);反之若 tick 被停止而唤醒迟迟不来,硬件会在较浅状态中驻留无限长时间,浪费能量。因此,只要 governor 预期 tick 范围内有任何唤醒,最好允许 tick 触发;
  • 反之,governor 会选相对较深的状态,此时应停止 tick,避免它过早唤醒 CPU。

无论如何,governor 知道自己预期什么,是否停止调度器 tick 的决定权属于它;若 tick 已在之前某次迭代中被停止,则最好维持现状。

禁用 tickless 的途径

内核可配置为完全禁止在 idle loop 中停止调度器 tick:

  • 构建时取消CONFIG_NO_HZ_IDLE配置选项。该选项在 kernel/time/Kconfig 中定义为 "Idle dynticks system (tickless idle)",开启后定时器中断只在系统空闲时按需触发,主要用于节能;
  • 或在命令行传入nohz=off

两种情况下,governor 关于 tick 的决策会被 idle loop 代码忽略,tick 永不停止。

允许在空闲 CPU 上停止调度器 tick 的内核所运行的系统称为tickless 系统,通常被认为比不能停止 tick 的系统更节能。tickless 系统默认使用menugovernor;非 tickless 系统默认使用ladder

menu Governor:基于模式识别与睡眠长度校正的预测算法

menugovernor 是 tickless 系统的默认 governor。它设计复杂,但基本思路直截了当:被调用选择空闲状态时,先预测 idle duration,再用预测值选择状态。在源码中,menu 的预测基于每个 CPU 的menu_device结构(见 drivers/cpuidle/governors/menu.c),其中保存了最近 8 个间隔(intervals[INTERVALS]INTERVALS为 8)、6 个独立校正因子(correction_factor[BUCKETS]BUCKETS为 6)等数据。

第一步:模式识别得到初步预测

menu 使用简单的模式识别算法获得初步的 idle duration 预测:保存最近 8 次观察到的 idle duration 值,预测时计算它们的平均值与方差。

  • 若方差较小(小于 400 平方毫秒),或方差相对于平均值较小(平均值大于 6 倍标准差),则将平均值作为 "typical interval"(典型间隔)值;
  • 否则,舍弃保存值中离平均值最远(最长或最短)的那个,对剩余值重新计算;
  • 重复上述过程,直到确定 "typical interval",或舍弃过多数据点。若后者发生:剩余数据点集合仍足够大时,下一次 idle duration 不太可能超过集合中最大的 idle duration 值,于是取该最大值作为预测值;若剩余集合过小,则不作出预测。

这段逻辑对应源码中的get_typical_interval()函数(menu.c 起),它遍历INTERVALS个历史间隔计算平均值与方差,并在方差超标时剔除异常点后goto again重试。

第二步:睡眠长度与校正因子

若上述初步预测足够长,governor 在"假设调度器 tick 被停止"的前提下获取距离最近定时器事件的时间,这个时间称为sleep length(睡眠长度),是下次 CPU 唤醒前的上界。它用于确定睡眠长度区间,进而取得睡眠长度校正因子。

menu 维护一个包含多个校正因子值的数组,各因子对应不同的睡眠长度区间,且相邻区间宽度相差约 10 倍。源码中which_bucket()将时长按 10µs、100µs、1ms、10ms、100ms 分档映射到 6 个桶(menu.c),每个桶对应一个独立校正因子——这源于"比例与预期时长的数量级相关"的观察(预期 500ms 空闲时很早来中断的概率,远高于预期 50µs 空闲时)。

在 CPU 被唤醒后,会针对本次睡眠长度所在区间更新其校正因子:睡眠长度越接近观察到的 idle duration,校正因子越接近 1(取值必须落在 0 到 1 之间)。预测的 idle duration 近似值为 睡眠长度 × 该区间校正因子,再与之前确定的 "typical interval" 比较,取两者较小者作为最终预测。

若 "typical interval" 值很小(CPU 很可能很快被唤醒),则跳过睡眠长度计算(该计算可能代价高昂),直接以 "typical interval" 作为 idle duration 预测。

第三步:选择空闲状态与最终修正

governor 遍历空闲状态数组:

  • 将每个状态的目标驻留时间与预测的 idle duration 比较;
  • 将每个状态的退出延迟与来自 PM QoS 框架的延迟上限比较;
  • 选择"目标驻留时间最接近预测 idle duration 但不超过它、且退出延迟不超过上限"的状态。

最后一步,若 governor 尚未决定停止调度器 tick,则可能需要精炼选择:当预测的 idle duration 小于 tick 周期且 tick 尚未(在前一次迭代中)被停止时,之前计算用的 sleep length 可能无法反映真实的最接近定时器事件时间;若真实时间确实大于该 sleep length,governor 可能需要改选一个目标驻留时间合适的更浅状态。

TEO Governor:面向定时器事件的选择算法

TEO(Timer Events Oriented,面向定时器事件)governor 是 tickless 系统的备选 governor。它的基本策略与 menu 相同——总是试图为当前条件找到最深的合适空闲状态——但采用了不同的方法。

其核心设计思想记录在 drivers/cpuidle/governors/teo.c 的teo-description内核文档中:

  • 观察基础:在许多系统上,定时器中断比其他类型中断频繁两个数量级以上,很可能主导 CPU 唤醒模式;而下一次定时器事件的时间在选择空闲状态时原则上可以确定(尽管可能代价高昂),因此可视为最可靠的信息来源。非定时器唤醒源在某些场景更重要,但 idle duration 超过 sleep length(到最近定时器的时间)的值通常无需考虑——除非 CPU 被提前唤醒,否则最近的定时器最终总会唤醒 CPU。
  • 由于获取 sleep length 可能代价高昂,governor 首先利用近期唤醒模式信息检查能否直接选一个浅状态(此时无需知道 sleep length)。为此它统计 CPU 唤醒事件,寻找"在多数相关近期案例中目标驻留时间未超过(唤醒后测得的)idle duration"的状态;若该状态的目标驻留时间足够小,可直接采用并跳过 sleep length 计算。
  • 计算基于bin(桶)进行,bin 的边界与 CPUIdle driver 提供的各空闲状态目标驻留时间(按升序)对齐:第 1 个 bin 从 0 到状态 1 的目标驻留时间(不含),第 2 个 bin 从状态 1 到状态 2 的目标驻留时间,依此类推;最后一个 bin 从最深状态的目标驻留时间到无穷。
  • 每个 bin 关联两个指标:"hits"(命中)反映"睡眠长度与唤醒后实测 idle duration 足够接近(CPU 相对睡眠长度'准时'唤醒,通常为定时器唤醒)"的相对频率;"intercepts"(拦截)反映"非定时器唤醒事件导致实测 idle duration 与睡眠长度显著不同"的相对频率。governor 还会统计 idle duration 低于 tick 周期长度的 intercepts,用于决定是否停止调度器 tick。
  • 选择过程(在考虑延迟约束的前提下):先找最深已启用状态(候选状态),分别累加所有比它浅的状态的 hits 之和与 intercepts 之和,并找出 intercepts 指标最大的状态(多个取索引最大者);若第二步中"候选状态及其之后所有 bin 的 hits+intercepts 总和"的一半还小于浅层 intercepts 之和,则更浅的状态可能更合适——从最大 intercepts 状态开始按降序遍历浅层状态,逐一计算"从该状态到候选状态之间"的 intercepts 总和,若其大于第二步计算出的浅层 intercepts 总和的一半,就以该状态为新候选;若当前候选是状态 0 或其目标驻留时间足够短,直接返回并阻止停止调度器 tick;否则获取 sleep length,若它低于当前候选状态的目标驻留时间,则需寻找更浅的候选状态。

空闲状态的表示(Representation of Idle States)

出于 CPU 空闲时间管理的目的,处理器支持的所有物理空闲状态都必须表示为struct cpuidle_state对象的一维数组,每个对象允许单个(逻辑)CPU 请求硬件进入具有特定属性的空闲状态。

层级(hierarchy)的处理

若处理器内部存在单元层级,一个struct cpuidle_state对象可以覆盖不同层级单元的多个空闲状态的组合。此时该对象的 target residency 与 exit latency 参数必须反映最深层级(即包含所有其他单元的单元)的空闲状态属性。

文档给出了一个典型例子:处理器包含两个核,位于称为 "module" 的更大单元中。假设一个核在 "core" 层请求特定空闲状态 "X" 时,若另一个核已在空闲状态 "X",硬件将尝试让 module 进入其自身的特定状态 "MX"。也就是说,"core" 层请求状态 "X" 相当于给硬件"最深可到 module 层 'MX'"的许可,但并不保证一定发生(请求的核可能只停留在 "X")。此时,表示状态 "X" 的对象的目标驻留时间必须反映 module 进入 "MX" 的最小时间(含进入时间)——因为这是硬件进入该状态时 CPU 需要空闲以节省任何能量的最小时间;其退出延迟参数必须覆盖 module 从 "MX" 退出的时间(通常还包括进入时间)——因为这是唤醒信号到 CPU 执行第一条新指令之间的最大延迟。

也有些处理器不同层级单元之间没有直接协调。此时在 "core" 层请求空闲状态不会自动影响 "module" 层,CPUIdle driver 负责层级的全部处理,状态对象的定义完全由 driver 决定。但无论何种情况,处理器硬件最终进入的空闲状态的物理属性必须遵循 governor 选择所依据的参数(例如实际退出延迟不得超过所选状态对象的退出延迟参数)。

sysfs 中的状态属性

除了 target residency 与 exit latency,状态对象还包含若干描述性参数和一个"请求硬件进入该状态"的函数指针。每个struct cpuidle_state对象还对应一个包含该状态使用统计信息的struct cpuidle_state_usage对象,这些信息通过sysfs暴露。

系统中每个 CPU 在/sys/devices/system/cpu/cpu<N>/cpuidle/目录下(<N>为初始化时分配的 CPU 编号)有一组子目录:state0state1……直到该 CPU 定义的状态对象数减一。编号越大,表示的有效空闲状态越深。每个子目录包含以下属性文件:

属性含义可写
above该状态曾被请求、但观察到的 idle duration 确实太短而无法匹配其目标驻留时间的总次数只读
below该状态曾被请求、但显然更深的状态才与观察到的 idle duration 更匹配的总次数只读
desc空闲状态的描述(可较长、可含空白与特殊字符)只读
disable该空闲状态是否被禁用(唯一可写的属性,写 1 禁用、写 0 启用)读写
default_status该状态的默认状态:"enabled" 或 "disabled"只读
latency空闲状态的退出延迟(微秒)只读
name空闲状态名称(比 desc 更简洁)只读
power硬件在该空闲状态下的功耗(毫瓦;未指定则为 0)只读
residency空闲状态的目标驻留时间(微秒)只读
time该 CPU 在此空闲状态中花费的总时间(内核测量,微秒)只读
usage该 CPU 请求硬件进入此状态的总次数只读
rejected该 CPU 上进入此状态的请求被拒绝的总次数只读

descname都是字符串,其余为整数。这些属性的内核实现位于 drivers/cpuidle/sysfs.c:above/below/rejected等通过define_show_state_ull_function()生成只读接口,disable通过define_one_state_rw()生成读写接口(show_state_disable/store_state_disable)。

disable 属性的语义

disable是唯一可写属性。若为 1,则该空闲状态对此特定 CPU 被禁用:governor 永远不会为它选择该状态,driver 也因此永远不会请求硬件为它进入该状态。但禁用一个 CPU 的状态不影响其他 CPU 请求它,因此要让该状态对任何 CPU 都不被请求,必须在所有 CPU 上禁用。注意:由于laddergovernor 的实现方式,禁用某状态还会阻止ladder选择比它更深的任何状态。

disable为 0,该状态对此 CPU 启用,但同时可能对系统中部分或全部其他 CPU 禁用。写入 1 会为当前 CPU 禁用该状态;写入 0 允许 governor 为该 CPU 考虑它、driver 请求它——除非该状态已在 driver 中被全局禁用(此时完全无法使用)。

关于 power 与 time 的可靠性说明

  • power属性定义不明确,尤其对表示层级组合的状态对象;复杂硬件很难获得空闲功耗数据,因此power常为 0(不可用),即便非零也可能不准确,不应依赖。
  • time中的数值通常可能大于该 CPU 真正驻留在该状态的时间:内核只能测量"请求进入空闲状态"到"随后 CPU 唤醒"之间的时间跨度,无法知道硬件层真正发生了什么;若硬件拒绝进入该状态而进入更浅状态(甚至未进入任何状态),内核无从得知;对层级组合状态,内核也无法判断硬件沿层级下行多深。因此要获知硬件在不同空闲状态的真实驻留时间,唯一可靠的方式是使用硬件提供的空闲状态驻留计数器(residency counters,如有)。
  • 一般地,尝试进入空闲状态时收到中断会导致进入请求被拒绝,此时 CPUIdle driver 可能返回错误码表示这种情况。usagerejected分别报告该状态成功进入与请求被拒绝的次数。

面向 CPU 的电源管理服务质量(PM QoS)

Linux 内核的PM QoS(Power Management Quality of Service)框架允许内核代码与用户空间进程对各种内核能效特性设置约束,防止性能低于所需水平。

CPU 空闲时间管理受 PM QoS 影响有两个途径:全局 CPU 延迟上限(global CPU latency limit)单个 CPU 的恢复延迟约束(resume latency constraints)

用户空间接口

  • 内核代码(如设备驱动)可通过 PM QoS 框架提供的专用内部接口设置两者;
  • 用户空间可打开/dev/cpu_dma_latency特殊设备文件并向其写入一个二进制值(解释为有符号 32 位整数),来修改全局 CPU 延迟上限;
  • 用户空间可通过向/sys/devices/system/cpu/cpu<N>/power/pm_qos_resume_latency_us文件写入字符串(表示有符号 32 位整数)修改指定 CPU 的恢复延迟约束(<N>为系统初始化时分配的 CPU 编号)。

两种情况均拒绝负值,写入的整数被解释为以微秒为单位的 PM QoS 约束请求。

请求的聚合与生效机制

请求的值不会自动成为新约束:它可能比他人先前请求的约束更宽松(此场景中即数值更大)。因此 PM QoS 框架为全局 CPU 延迟上限和每个 CPU 分别维护请求列表,聚合后应用有效值(此处为列表中的最小值)作为新约束。

/dev/cpu_dma_latency的语义值得注意:

  • 打开该文件会创建一个新的 PM QoS 请求并加入全局 CPU 延迟上限请求优先级列表,open 返回的文件描述符即代表该请求;
  • 对该文件描述符写入的数值会关联到它代表的 PM QoS 请求,成为新的请求值;随后通过优先级列表机制确定整个列表的新有效值,并设为新的 CPU 延迟上限。因此,只有请求值影响列表有效值(即它是列表中的最小值)时,真实上限才会改变;
  • 持有该文件描述符的进程只控制与之关联的这一个 PM QoS 请求;
  • 关闭该文件描述符会使关联请求从全局列表移除并销毁,随后重新计算列表有效值并成为新上限。

每个 CPU 的恢复延迟请求

每个 CPU 有一个与power/pm_qos_resume_latency_us文件关联的恢复延迟 PM QoS 请求:无论哪个用户空间进程写入,都更新这同一个请求——它由整个用户空间共享,因此对它的访问需要仲裁以避免混乱。文档指出,实践中这一机制唯一合理的用途是把某进程绑定到该 CPU,由它通过sysfs接口控制恢复延迟约束。它仍然只是一个请求(列表中的一个条目),每次列表以任何方式更新时用于确定该 CPU 的恢复延迟约束有效值(列表中还可能有来自内核代码的其他请求)。

对 governor 的约束

CPU 空闲时间 governor 应将全局(有效)CPU 延迟上限该 CPU 的有效恢复延迟约束最小值,视为它们可为该 CPU 选择状态的退出延迟上限。governor 绝不应选择退出延迟超过该上限的任何空闲状态。

此外,用户空间还可以通过cpu_wakeup_latency文件请求 CPU 系统唤醒延迟 QoS 上限。该约束在选择 CPU 空闲状态时被尊重(在进入系统级 suspend-to-idle 睡眠状态时,也适用于常规 CPU 空闲时间管理)。cpu_wakeup_latency文件从用户空间角度的管理与cpu_dma_latency一致,单位同样是微秒。

通过内核命令行控制空闲状态

sysfs接口(可为单个 CPU 禁用单个状态)外,内核命令行参数也能影响 CPU 空闲时间管理。

通用参数

  • cpuidle.off=1:完全禁用 CPU 空闲时间管理。它不阻止 idle loop 在空闲 CPU 上运行,但阻止 CPUIdle governor 与 driver 被调用。加入该参数后,idle loop 将通过 CPU 架构支持代码请求硬件进入空闲状态;该默认机制通常是该架构(指令集)所有处理器的"最小公分母",比较粗糙、能效不佳,不推荐用于生产环境
  • cpuidle.governor=:指定要使用的 CPUIdle governor,需追加可用 governor 的名称字符串(如cpuidle.governor=menu),将替代默认 governor。例如可用它强制默认使用ladder的系统改用menu

x86 架构专用参数

以下参数仅对 x86 架构相关,且对intel_idle的引用只影响 Intel 处理器。

x86 架构支持代码识别三个与 CPU 空闲时间管理相关的命令行选项:idle=pollidle=haltidle=nomwait。前两者会彻底禁用acpi_idleintel_idle驱动,等效于禁用整个 CPUIdle 子系统,使 idle loop 调用架构支持代码处理空闲 CPU:

  • idle=halt:架构支持代码使用 CPU 的HLT指令(通常会挂起程序执行并让硬件尝试进入最浅可用空闲状态)处理空闲 CPU;
  • idle=poll:空闲 CPU 在一个紧密循环中执行或多或少"轻量级"的指令序列。文档特别警告:使用idle=poll在很多情况下相当激进——阻止空闲 CPU 节省几乎任何能量可能不是唯一影响,例如在 Intel 硬件上它会阻止 CPU 使用需要包内一定数量 CPU 处于空闲状态的 P-states(见 CPU Performance Scaling),很可能同时损害单线程计算性能与能效,因此出于性能目的使用它可能根本不是好主意。

在源码中,arch/x86/kernel/process.c的 idle 处理代码对这两个参数有明确注释('idle=halt' HALT for idle. C-states are disabled;'idle=nomwait' disables MWAIT for idle),相关选项也在 arch/x86/Kconfig 中作为引导选项被提及。

  • idle=nomwait:阻止使用 CPU 的MWAIT指令进入空闲状态。使用时,acpi_idle驱动将用HLT指令替代MWAIT;在 Intel 处理器上,该选项禁用intel_idle驱动并强制改用acpi_idle。无论哪种情况,acpi_idle驱动仅在系统 ACPI 表包含其所需的全部信息时才能工作。

影响单个 driver 的参数

除架构级选项外,还有可通过命令行传给单个 CPUIdle driver 的参数:

  • intel_idle.max_cstate=<n>:使intel_idle驱动丢弃所有比空闲状态<n>更深的状态(<n>sysfs中该状态目录名所用的索引一致)。这些状态永远不会被请求,也不会暴露给 governor。<n>为 0 时行为特殊:intel_idle.max_cstate=0禁用intel_idle驱动,允许使用acpi_idle。相关实现在 drivers/idle/intel_idle.c(max_cstate默认CPUIDLE_STATE_MAX - 1,注释明确 "intel_idle.max_cstate=0 disables driver")及intel_idle_max_cstate_reached()检查中。
  • processor.max_cstate=<n>:对acpi_idle驱动起类似作用,丢弃比<n>更深的状态;processor.max_cstate=0等价于processor.max_cstate=1acpi_idle驱动是processor内核模块的一部分,可单独加载,加载时也可将max_cstate=<n>作为模块参数传入——见 drivers/acpi/processor_idle.c 中的module_param(max_cstate, uint, 0400)

实践指引:如何查看与调整 CPU 空闲行为

结合上文,在实际 Linux 系统上可这样操作(以当前内核源码实现为准,适用于配置了 CPUIdle 与sysfs的常规 x86/ACPI 平台):

  1. 查看当前使用的 governor 与 driver
    cat /sys/devices/system/cpu/cpuidle/current_governor cat /sys/devices/system/cpu/cpuidle/current_driver cat /sys/devices/system/cpu/cpuidle/available_governors
  2. 查看某个 CPU 的空闲状态及统计
    ls /sys/devices/system/cpu/cpu0/cpuidle/ cat /sys/devices/system/cpu/cpu0/cpuidle/state0/name cat /sys/devices/system/cpu/cpu0/cpuidle/state0/latency cat /sys/devices/system/cpu/cpu0/cpuidle/state0/residency cat /sys/devices/system/cpu/cpu0/cpuidle/state0/usage cat /sys/devices/system/cpu/cpu0/cpuidle/state0/rejected
  3. 禁用某个状态(例如调试深状态唤醒延迟问题)
    echo 1 | sudo tee /sys/devices/system/cpu/cpu0/cpuidle/stateN/disable

    注意需对所有 CPU 重复操作才能全局禁用。

  4. 通过内核命令行调优:在引导配置中追加cpuidle.governor=menuintel_idle.max_cstate=4processor.max_cstate=4等参数(重启后生效)。
  5. 通过 PM QoS 限制延迟:使用如devmem/专用工具向/dev/cpu_dma_latency写入微秒级延迟上限值,防止 governor 选择退出延迟过高的深状态。

总结

CPU 空闲时间管理是 Linux 内核一项核心能效特性。本文完整梳理了其架构脉络:idle loop 中 governor 负责"选状态"、driver 负责"进状态"的两步分工;menuTEOladderhaltpoll四个 governor 中前两者面向 tickless 系统的预测算法差异;idle state 在sysfs中的 12 个属性及其可靠性与局限性;PM QoS 通过全局延迟上限与每 CPU 恢复延迟约束对 governor 的约束机制;以及cpuidle.offcpuidle.governoridle=poll/halt/nomwaitintel_idle.max_cstateprocessor.max_cstate等内核命令行参数的完整语义。所有关键机制均可在本仓库 drivers/cpuidle/、drivers/idle/intel_idle.c、drivers/acpi/processor_idle.c 与 kernel/time/Kconfig 等源码中找到对应实现,读者可据此进一步深入钻研。

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询