intel_idle CPU 空閒時間管理驅動

版權:

© 2020 英特爾公司

作者:

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

一般資訊

intel_idle 是 Linux 核心中 CPU 空閒時間管理子系統 (CPUIdle) 的一部分。它是 Nehalem 及後續英特爾處理器系列的預設 CPU 空閒時間管理驅動,但其對特定處理器模型的支援程度取決於它是否識別該處理器模型,也可能取決於平臺韌體提供的資訊。[要理解 intel_idle,有必要了解 CPUIdle 的一般工作原理,因此如果您尚未熟悉 CPU 空閒時間管理,現在是時候了。]

intel_idle 使用 MWAIT 指令通知處理器,執行該指令的邏輯 CPU 處於空閒狀態,因此可以將處理器的一些功能模組置於低功耗狀態。該指令接受兩個引數(透過目標 CPU 的 EAXECX 暫存器傳遞),其中第一個引數被稱為 提示,可供處理器用於確定可以執行的操作(詳情請參閱英特爾軟體開發人員手冊 [1])。因此,intel_idle 拒絕在停用了 MWAIT 指令支援(例如,透過平臺韌體配置選單)或根本不支援該指令的處理器上工作。

intel_idle 不是模組化的,因此無法解除安裝,這意味著向其傳遞早期配置引數的唯一方法是透過核心命令列。

Sysfs 介面

intel_idle 驅動在 /sys/devices/system/cpu/cpuidle/ 中暴露以下 sysfs 屬性:

intel_c1_demotion

啟用或停用系統中所有 CPU 的 C1 降級。此檔案僅在支援 C1 降級功能並經過測試的平臺上公開。值 0 表示 C1 降級已停用,值 1 表示已啟用。寫入 0 或 1 可停用或啟用所有 CPU 的 C1 降級。

C1 降級功能涉及平臺韌體將來自作業系統的深度 C 狀態請求(例如,C6 請求)降級到 C1。其思想是韌體監控 CPU 喚醒速率,如果該速率高於平臺特定閾值,韌體會將深度 C 狀態請求降級到 C1。例如,Linux 請求 C6,但韌體檢測到每秒喚醒次數過多,因此將 CPU 保持在 C1 狀態。當 CPU 在 C1 中停留足夠長時間後,平臺會將其提升回 C6。這可能會提高某些工作負載的效能,但也可能增加功耗。

空閒狀態列舉

每個 MWAIT 提示值都被處理器解釋為以某種方式重新配置自身以節省能源的許可。由此產生的處理器配置(功耗降低)被稱為 C 狀態(在 ACPI 術語中)或空閒狀態。有意義的 MWAIT 提示值和與其對應的空閒狀態(即處理器的低功耗配置)列表取決於處理器型號,也可能取決於平臺的配置。

為了建立 CPUIdle 子系統所需的可用空閒狀態列表(參見 CPU 空閒時間管理 中的 空閒狀態表示),intel_idle 可以使用兩種資訊來源:驅動程式本身中包含的不同處理器型號的空閒狀態靜態表,以及系統的 ACPI 表。如果當前處理器型號被 intel_idle 識別,則始終使用前者;如果給定處理器型號需要後者(intel_idle 識別的所有伺服器處理器型號都是這種情況),或者處理器型號不被識別,則使用後者。[有一個模組引數可以用來使驅動程式對任何識別的處理器型號都使用 ACPI 表;參見下文。]

如果使用 ACPI 表來構建可用空閒狀態列表,intel_idle 首先會在系統中對應 CPU 的 ACPI 物件下查詢 _CST 物件(有關 _CST 及其輸出包的描述,請參閱 ACPI 規範 [2])。由於 CPUIdle 子系統期望驅動程式提供的空閒狀態列表適用於它所處理的所有 CPU,並且 intel_idle 被註冊為系統中所有 CPU 的 CPUIdle 驅動,因此驅動程式會查詢第一個返回至少一個有效空閒狀態描述的 _CST 物件,並且其返回包中包含的所有空閒狀態都屬於 FFH(功能固定硬體)型別,這意味著預計將使用 MWAIT 指令告知處理器可以進入其中一個狀態。然後,該 _CST 的返回包被假定適用於系統中所有其他 CPU,並從中提取的空閒狀態描述儲存在來自 ACPI 表的初步空閒狀態列表中。[如果 intel_idle 配置為忽略 ACPI 表,則跳過此步驟;參見下文。]

接下來,可用空閒狀態列表中的第一個(索引 0)條目被初始化為表示“輪詢空閒狀態”(一種偽空閒狀態,其中目標 CPU 不斷地獲取和執行指令),隨後的(真實)空閒狀態條目按如下方式填充。

如果當前處理器型號被 intel_idle 識別,則驅動程式中有一個(靜態)空閒狀態描述表。在這種情況下,“內部”表是空閒狀態資訊的主要來源,其資訊被複制到可用空閒狀態的最終列表中。如果不需要使用 ACPI 表進行空閒狀態列舉(取決於處理器型號),則所有列出的空閒狀態預設啟用(因此它們都將在 CPU 空閒狀態選擇期間被 CPUIdle 管理器考慮)。否則,如果來自 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_idle 將因某種原因被視為“過深”的空閒狀態暴露給 CPUIdle 核心,但它透過使它們在系統關閉並重新啟動之前有效地不可見來做到這一點,這可能並非總是合乎需要的。實際上,只有當相關空閒狀態無法在系統啟動期間啟用時,才真正需要這樣做,因為在系統工作狀態下,CPU 電源管理服務質量 (PM QoS) 功能可以用於阻止 CPUIdle 接觸那些空閒狀態,即使它們已經被列舉(參見 CPU 空閒時間管理 中的 CPU 的電源管理服務質量)。將 max_cstate 設定為 0 會導致 intel_idle 初始化失敗。

如果核心配置了 ACPI 支援,intel_idle 會識別 no_acpiuse_acpino_native 模組引數。在未配置 ACPI 的情況下,這些標誌對功能沒有影響。

no_acpi - 完全不使用 ACPI。只提供原生模式,不提供 ACPI 模式。

use_acpi - 在 ACPI 模式下為空操作,在原生模式下驅動程式將查詢 ACPI 表以獲取 C 狀態的開啟/關閉狀態。

no_native - 僅在 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 的效能產生影響。IBRS 模式在 CPU 進入深度空閒狀態時將預設關閉,但在某些較淺的空閒狀態中則不會。設定 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 的情況下,並且除此之外(在大多數情況下),它還允許處理器將整個封裝(可能包括 GPU 或記憶體控制器等非 CPU 元件)置於稱為“包 C3”(或 PC3)的低功耗狀態,這發生在所有核都已進入 CC3 狀態且(可能)滿足某些附加條件的情況下(例如,如果 GPU 包含在 PC3 中,則可能要求它處於特定的 GPU 特定低功耗狀態才能達到 PC3)。

通常,沒有簡單的方法可以在滿足進入相應包 C 狀態的條件時,強制處理器僅使用核 C 狀態,因此,執行具有非核級專用(例如 C1)提示值的 MWAIT 的邏輯 CPU 必須始終假定這可能導致處理器進入包 C 狀態。[這就是為什麼 intel_idle 中空閒狀態“內部”表中大多數 MWAIT 提示值對應的退出延遲和目標駐留時間值反映的是包 C 狀態的特性。] 如果完全不希望使用包 C 狀態,則必須使用 PM QoS上文所述的 intel_idlemax_cstate 模組引數,以將允許的空閒狀態範圍限制為僅具有核級 MWAIT 提示值(如 C1)的狀態。

參考資料