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