CPU 空閒時間管理

版權:

© 2018 Intel Corporation

作者:

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

概念

現代處理器通常能夠進入這樣的狀態:程式的執行被掛起,屬於程式的指令不會從記憶體中獲取或執行。 這些狀態是處理器的*空閒*狀態。

由於處理器硬體的一部分在空閒狀態下未使用,因此進入這些狀態通常可以減少處理器消耗的功率,從而有機會節省能源。

CPU 空閒時間管理是一種節能特性,旨在為此目的使用處理器的空閒狀態。

邏輯 CPU

CPU 空閒時間管理在 *CPU 排程程式*(即核心中負責在系統中分配計算工作的部分)看到的 CPU 上執行。 在它看來,CPU 是*邏輯*單元。 也就是說,它們不必是單獨的物理實體,而可能只是軟體看起來像單獨的單核處理器的介面。 換句話說,CPU 是一個看起來從記憶體中獲取屬於一個序列(程式)的指令並執行它們的實體,但它不一定以這種方式物理工作。 通常,這裡可以考慮三種不同的情況。

首先,如果整個處理器一次只能遵循一個指令序列(一個程式),那麼它就是一個 CPU。 在這種情況下,如果要求硬體進入空閒狀態,則適用於整個處理器。

其次,如果處理器是多核的,那麼其中的每個核心一次至少能夠遵循一個程式。 這些核心不必完全彼此獨立(例如,它們可能共享快取),但大多數時候它們仍然彼此物理並行工作,因此如果它們中的每一個只執行一個程式,那麼這些程式就會大部分時間彼此獨立地同時執行。 在這種情況下,整個核心都是 CPU,如果要求硬體進入空閒狀態,則適用於首先要求它的核心,但也可能適用於核心所屬的更大單元(例如“包”或“叢集”)(事實上,它可能適用於包含該核心的整個更大的單元層次結構)。 也就是說,如果除了一個核心之外,更大的單元中的所有核心都已置於“核心級別”的空閒狀態,並且剩餘的核心要求處理器進入空閒狀態,那麼可能會觸發它將整個更大的單元置於空閒狀態,這也會影響該單元中的其他核心。

最後,多核處理器中的每個核心都可能能夠在同一時間段內遵循多個程式(也就是說,每個核心可能能夠從記憶體中的多個位置獲取指令並在同一時間段內執行它們,但不一定完全彼此並行)。 在這種情況下,核心向軟體呈現為“捆綁”,每個捆綁由多個單獨的單核“處理器”組成,這些處理器被稱為*硬體執行緒*(或者在 Intel 硬體上專門稱為超執行緒),每個執行緒都可以遵循一個指令序列。 那麼,硬體執行緒就是 CPU 空閒時間管理角度的 CPU,如果處理器被其中一個要求進入空閒狀態,則要求它的硬體執行緒(或 CPU)會被停止,但不會發生更多的事情,除非同一核心內的所有其他硬體執行緒也要求處理器進入空閒狀態。 在這種情況下,核心可以單獨置於空閒狀態,或者包含它的更大的單元可以作為一個整體置於空閒狀態(如果更大的單元內的其他核心已經處於空閒狀態)。

空閒 CPU

邏輯 CPU,在下文中簡稱為“CPU”,當沒有任務要在其上執行時,除了特殊的“空閒”任務之外,Linux 核心將其視為*空閒*。

任務是 CPU 排程程式對工作的表示。 每個任務都由要執行的指令序列或程式碼、在執行該程式碼時要操作的資料以及每次 CPU 執行該任務的程式碼時需要載入到處理器中的一些上下文資訊組成。 CPU 排程程式透過將任務分配給系統中的 CPU 來執行,從而分配工作。

任務可以處於各種狀態。 特別是,如果沒有特定的條件阻止 CPU 執行其程式碼,只要有可用的 CPU(例如,它們沒有等待任何事件發生或類似情況),它們就是*可執行*的。 當任務變為可執行時,CPU 排程程式會將其分配給其中一個可用的 CPU 來執行,如果沒有更多分配給它的可執行任務,CPU 將載入給定任務的上下文並執行其程式碼(從到目前為止執行的最後一個指令之後的指令開始,可能是由另一個 CPU 執行)。 [如果同時將多個可執行任務分配給一個 CPU,它們將受到優先順序和時間共享的約束,以便它們能夠隨著時間的推移取得一些進展。]

如果沒有其他可執行任務分配給給定的 CPU,則特殊的“空閒”任務將變為可執行,並且 CPU 隨後被視為空閒。 換句話說,在 Linux 中,空閒 CPU 執行名為*空閒迴圈*的“空閒”任務的程式碼。 如果處理器支援,該程式碼可能會導致處理器進入其空閒狀態之一,以節省能源,但如果處理器不支援任何空閒狀態,或者在下一個喚醒事件之前沒有足夠的時間花費在空閒狀態中,或者存在嚴格的延遲限制阻止使用任何可用的空閒狀態,CPU 將簡單地在一個迴圈中執行或多或少無用的指令,直到它被分配一個新的任務來執行。

空閒迴圈

空閒迴圈程式碼在每次迭代中都採取兩個主要步驟。 首先,它呼叫一個稱為*調速器*的程式碼模組,該模組屬於稱為 CPUIdle 的 CPU 空閒時間管理子系統,以選擇 CPU 要請求硬體進入的空閒狀態。 其次,它從 CPUIdle 子系統呼叫另一個程式碼模組,稱為*驅動程式*,以實際要求處理器硬體進入調速器選擇的空閒狀態。

調速器的作用是找到最適合當前條件的空閒狀態。 為此,邏輯 CPU 可以要求硬體進入的空閒狀態以一種獨立於平臺或處理器架構的抽象方式表示,並組織成一維(線性)陣列。 該陣列必須由與核心在初始化時執行的平臺匹配的 CPUIdle 驅動程式準備和提供。 這允許 CPUIdle 調速器獨立於底層硬體,並與 Linux 核心可以執行的任何平臺一起工作。

該陣列中存在的每個空閒狀態都由調速器要考慮的兩個引數來表徵,即*目標駐留時間*和(最壞情況)*退出延遲*。 目標駐留時間是硬體必須在給定狀態下花費的最短時間,包括進入該狀態所需的時間(這可能相當大),以便比進入較淺的空閒狀態之一節省更多的能量。 [空閒狀態的“深度”大致對應於處理器在該狀態下消耗的功率。] 反過來,退出延遲是要求處理器硬體進入空閒狀態的 CPU 在從該狀態喚醒後開始執行第一條指令所需的最長時間。 請注意,一般來說,退出延遲還必須包括進入給定狀態所需的時間,以防在硬體進入該狀態時發生喚醒,並且必須完全進入該狀態才能以有序的方式退出。

有兩種型別的資訊會影響調速器的決策。 首先,調速器知道直到最近的計時器事件的時間。 該時間是準確已知的,因為核心會程式設計計時器,並且它確切地知道它們何時會觸發,並且它是給定 CPU 依賴的硬體可以處於空閒狀態的最長時間,包括進入和退出它所需的時間。 但是,CPU 可能會隨時被非計時器事件喚醒(特別是,在最近的計時器觸發之前),並且通常不知道何時可能發生這種情況。 調速器只能看到 CPU 在喚醒後實際處於空閒狀態的時間(該時間從現在開始將稱為*空閒持續時間*),並且它可以以某種方式使用該資訊以及直到最近的計時器的時間來估計未來的空閒持續時間。 調速器如何使用該資訊取決於它實現的演算法,這也是在 CPUIdle 子系統中擁有多個調速器的主要原因。

有四個 CPUIdle 調速器可用,分別是 menuTEOladderhaltpoll。 預設使用哪個取決於核心的配置,特別是排程程式節拍是否可以被 空閒迴圈停止。 可用的調速器可以從 available_governors 中讀取,並且可以在執行時更改調速器。 核心當前使用的 CPUIdle 調速器的名稱可以從 /sys/devices/system/cpu/cpuidle/ 中的 current_governor_rocurrent_governor 檔案中讀取,位於 sysfs 中。

另一方面,使用哪個 CPUIdle 驅動程式通常取決於核心執行的平臺,但是有多個匹配驅動程式的平臺。 例如,有兩個驅動程式可以與大多數 Intel 平臺一起工作,分別是 intel_idleacpi_idle,一個具有硬編碼的空閒狀態資訊,另一個能夠分別從系統的 ACPI 表中讀取該資訊。 儘管如此,即使在這些情況下,在系統初始化時選擇的驅動程式也不能稍後更換,因此必須儘早做出使用哪一個的決定(在 Intel 平臺上,如果由於某種原因停用了 intel_idle,或者它無法識別處理器,則將使用 acpi_idle 驅動程式)。 核心當前使用的 CPUIdle 驅動程式的名稱可以從 sysfs/sys/devices/system/cpu/cpuidle/ 下的 current_driver 檔案中讀取。

空閒 CPU 和排程程式節拍

排程程式節拍是一個定期觸發的計時器,以便實現 CPU 排程程式的時間共享策略。 當然,如果同時將多個可執行任務分配給一個 CPU,那麼在給定的時間範圍內允許它們取得合理進展的唯一方法是讓它們共享可用的 CPU 時間。 也就是說,粗略地說,每個任務都被賦予一個 CPU 時間片來執行其程式碼,這取決於排程類、優先順序等,當該時間片用完時,CPU 應該切換到執行(程式碼)另一個任務。 然而,當前執行的任務可能不想自願放棄 CPU,並且排程程式節拍在那裡是為了無論如何都要進行切換。 這不是節拍的唯一作用,但它是使用它的主要原因。

從 CPU 空閒時間管理的角度來看,排程程式節拍是有問題的,因為它定期且相對頻繁地觸發(根據核心配置,節拍週期的長度在 1 毫秒到 10 毫秒之間)。 因此,如果允許節拍在空閒 CPU 上觸發,那麼它們要求硬體進入目標駐留時間高於節拍週期長度的空閒狀態就沒有意義了。 此外,在這種情況下,任何 CPU 的空閒持續時間都不會超過節拍週期長度,並且由於空閒 CPU 上節拍喚醒而進入和退出空閒狀態所使用的能量將被浪費。

幸運的是,允許節拍在空閒 CPU 上觸發並不是真正必要的,因為(根據定義)除了特殊的“空閒”任務之外,它們沒有任務要執行。 換句話說,從 CPU 排程程式的角度來看,它們上的 CPU 時間的唯一使用者是空閒迴圈。 由於空閒 CPU 的時間不需要在多個可執行任務之間共享,因此如果給定的 CPU 處於空閒狀態,則使用節拍的主要原因就會消失。 因此,原則上可以完全停止空閒 CPU 上的排程程式節拍,即使這並不總是值得付出努力。

在空閒迴圈中停止排程程式節拍是否有意義取決於調速器的期望。 首先,如果在節拍範圍內有另一個(非節拍)計時器將要觸發,那麼停止節拍顯然是浪費時間,即使在這種情況下可能不需要重新程式設計計時器硬體。 其次,如果調速器期望在節拍範圍內發生非計時器喚醒,則無需停止節拍,甚至可能有害。 也就是說,在這種情況下,調速器將選擇一個空閒狀態,該空閒狀態的目標駐留時間在直到預期喚醒的時間範圍內,因此該狀態將相對較淺。 然後,調速器真的不能選擇更深的空閒狀態,因為這將與其自身對短期內喚醒的期望相矛盾。 現在,如果喚醒確實很快發生,停止節拍將是浪費時間,並且在這種情況下需要重新程式設計計時器硬體,這是昂貴的。 另一方面,如果停止了節拍並且喚醒並沒有很快發生,硬體可能會在調速器選擇的淺空閒狀態下花費無限的時間,這將浪費能源。 因此,如果調速器期望在節拍範圍內發生任何型別的喚醒,最好允許節拍觸發。 但是,否則,調速器將選擇一個相對較深的空閒狀態,因此應停止節拍,以免它過早喚醒 CPU。

在任何情況下,調速器都知道它的期望,並且是否停止排程程式節拍的決定取決於它。 儘管如此,如果節拍已經停止(在迴圈的先前迭代之一中),最好保持原樣,並且調速器需要考慮到這一點。

可以配置核心以完全停用在空閒迴圈中停止排程程式節拍。 這可以透過它的構建時配置來完成(透過取消設定 CONFIG_NO_HZ_IDLE 配置選項),或者透過在命令列中傳遞 nohz=off 來完成。 在這兩種情況下,由於停用了排程程式節拍的停止,空閒迴圈程式碼只會忽略調速器關於它的決策,並且永遠不會停止節拍。

執行配置為允許在空閒 CPU 上停止排程程式節拍的核心的系統被稱為*無節拍*系統,並且通常認為它們比執行無法停止節拍的核心的系統更節能。 如果給定的系統是無節拍的,它將預設使用 menu 調速器,如果它不是無節拍的,那麼它上面的預設 CPUIdle 調速器將是 ladder

menu 調速器

menu 調速器是無節拍系統的預設 CPUIdle 調速器。 它非常複雜,但其設計的基本原則很簡單。 也就是說,當被呼叫為 CPU 選擇空閒狀態時(即 CPU 將要求處理器硬體進入的空閒狀態),它會嘗試預測空閒持續時間,並使用預測值來選擇空閒狀態。

它首先使用一個簡單的模式識別演算法來獲得空閒持續時間的初步預測。 也就是說,它儲存最後 8 個觀察到的空閒持續時間值,並且在下次預測空閒持續時間時,它會計算它們的平均值和方差。 如果方差很小(小於 400 平方毫秒),或者相對於平均值很小(平均值大於標準差的 6 倍),則該平均值被視為“典型間隔”值。 否則,放棄儲存的觀察到的空閒持續時間值中最長或最短的(取決於哪個離平均值更遠),並對剩餘的值重複計算。

同樣,如果它們的方差很小(在上述意義上),則將平均值作為“典型間隔”值,依此類推,直到確定“典型間隔”或放棄的資料點太多。 在後一種情況下,如果仍在考慮的資料點集的大小足夠大,則下一個空閒持續時間不太可能高於仍在該集中的最大空閒持續時間值,因此該值被視為預測的下一個空閒持續時間。 最後,如果仍在考慮的資料點集太小,則不會進行預測。

如果以這種方式計算出的下一個空閒持續時間的初步預測足夠長,則調速器將在假設排程程式節拍將被停止的情況下,獲得直到最近的計時器事件的時間。 在下文中,該時間稱為*睡眠長度*,它是下一次 CPU 喚醒之前的時間上限。 它用於確定睡眠長度範圍,而睡眠長度範圍又需要用於獲得睡眠長度校正因子。

menu 調速器維護一個包含多個校正因子值的陣列,這些校正因子值對應於不同的睡眠長度範圍,這些範圍的組織方式使得陣列中表示的每個範圍都比前一個範圍寬約 10 倍。

在 CPU 喚醒後更新給定睡眠長度範圍的校正因子(在為 CPU 選擇空閒狀態之前確定),並且睡眠長度越接近觀察到的空閒持續時間,校正因子越接近 1(它必須在 0 和 1 之間,包括 0 和 1)。 睡眠長度乘以它所屬範圍的校正因子,以獲得預測的空閒持續時間的近似值,該近似值與先前確定的“典型間隔”進行比較,並將兩者的最小值作為最終的空閒持續時間預測。

如果“典型間隔”值很小,這意味著 CPU 可能會很快被喚醒,則跳過睡眠長度計算,因為它可能很昂貴,並且簡單地預測空閒持續時間等於“典型間隔”值。

現在,調速器已準備好遍歷空閒狀態列表並選擇其中一個。 為此,它會將每個狀態的目標駐留時間與預測的空閒持續時間進行比較,並將其退出延遲與來自電源管理服務質量或 PM QoS 框架的延遲限制進行比較。 它選擇目標駐留時間最接近預測的空閒持續時間但仍低於它的狀態,並且退出延遲不超過限制。

在最後一步中,如果調速器尚未決定停止排程程式節拍,則它可能仍然需要最佳化空閒狀態選擇。 如果它預測的空閒持續時間小於節拍週期,並且節拍尚未停止(在空閒迴圈的先前迭代中),則會發生這種情況。 那麼,先前計算中使用的睡眠長度可能無法反映直到最近的計時器事件的真即時間,並且如果它真的大於該時間,則調速器可能需要選擇具有合適目標駐留時間的較淺狀態。

面向計時器事件 (TEO) 的調速器

面向計時器事件 (TEO) 的調速器是無節拍系統的替代 CPUIdle 調速器。 它遵循與 menu 調速器 相同的基本策略:它始終嘗試找到適合給定條件的最深空閒狀態。 然而,它對該問題應用了不同的方法。

該調速器的想法是基於觀察到在許多系統中,計時器中斷的頻率比任何其他中斷型別高兩個或多個數量級,因此它們可能會主導 CPU 喚醒模式。 此外,原則上,可以在空閒狀態選擇時確定下一個計時器事件將發生的時間,儘管這樣做可能很昂貴,因此可以將其視為空閒狀態選擇最可靠的資訊來源。

當然,在某些用例中,非計時器喚醒源更重要,但即使如此,通常也沒有必要考慮大於直到下一個計時器事件的時間的空閒持續時間值,這在下文中稱為睡眠長度,因為最近的計時器最終會喚醒 CPU,除非它更早被喚醒。

然而,由於獲得睡眠長度可能很昂貴,因此調速器首先檢查它是否可以使用最近的喚醒模式資訊來選擇淺空閒狀態,在這種情況下,它可以完全不知道睡眠長度。 為此,它會計算 CPU 喚醒事件的數量,並查詢其目標駐留時間在大多數相關最近情況下沒有超過空閒持續時間(在喚醒後測量)的空閒狀態。 如果該狀態的目標駐留時間足夠小,則可以立即使用它,而無需確定睡眠長度。

該調速器執行的計算基於使用其邊界與 CPUIdle 驅動程式按升序提供的 CPU 空閒狀態的目標駐留時間引數值對齊的儲存桶。 也就是說,第一個儲存桶從 0 跨越到第二個空閒狀態(空閒狀態 1)的目標駐留時間,但不包括它,第二個儲存桶從空閒狀態 1 的目標駐留時間跨越到空閒狀態 2 的目標駐留時間,但不包括它,第三個儲存桶從空閒狀態 2 的目標駐留時間跨越到空閒狀態 3 的目標駐留時間,但不包括它,依此類推。 最後一個儲存桶從驅動程式提供的最深空閒狀態的目標駐留時間跨越到無窮大。

兩個名為“命中”和“截獲”的指標與每個儲存桶相關聯。 每次在為給定的 CPU 選擇空閒狀態之前,都會根據上次發生的情況來更新它們。

“命中”指標反映了睡眠長度和 CPU 喚醒後測量的空閒持續時間落入同一儲存桶中的情況的相對頻率(也就是說,相對於睡眠長度,CPU 似乎“按時”喚醒)。 反過來,“截獲”指標反映了非計時器喚醒事件的相對頻率,對於這些事件,測量的空閒持續時間落入與睡眠長度落入的儲存桶對應的空閒狀態更淺的儲存桶中(這些事件也在下面稱為“截獲”)。

調速器還會計算其測量的空閒持續時間低於節拍週期長度的“截獲”,並在決定是否停止排程程式節拍時使用此資訊。

為了為 CPU 選擇空閒狀態,調速器會採取以下步驟(考慮到必須考慮的可能的延遲約束):

  1. 找到最深的已啟用 CPU 空閒狀態(候選空閒狀態),並按如下方式計算 2 個總和:

    • 比候選狀態淺的所有空閒狀態的“命中”指標的總和(它表示 CPU 可能被計時器喚醒的情況)。

    • 比候選狀態淺的所有空閒狀態的“截獲”指標的總和(它表示 CPU 可能被非計時器喚醒源喚醒的情況)。

  2. 如果在步驟 1 中計算的第二個總和大於候選狀態儲存桶和所有後續儲存桶(如果有)的兩個指標的總和的一半,則較淺的空閒狀態可能更合適,因此請尋找它。

    • 按降序遍歷比候選狀態淺的已啟用空閒狀態。

    • 對於它們中的每一個,計算從它到候選狀態(包括前者但不包括後者)的所有空閒狀態上的“截獲”指標的總和。

    • 如果該總和大於在步驟 1 中計算的第二個總和的一半,則使用給定的空閒狀態作為新的候選狀態。

  3. 如果當前候選狀態是狀態 0 或其目標駐留時間足夠短,則返回它並阻止排程程式節拍停止。

  4. 獲得睡眠長度值並檢查它是否低於當前候選狀態的目標駐留時間,在這種情況下,需要找到一個新的較淺的候選狀態,因此請尋找它。

空閒狀態的表示

出於 CPU 空閒時間管理的目的,處理器支援的所有物理空閒狀態都必須表示為 struct cpuidle_state 物件的一維陣列,每個物件允許一個單獨的(邏輯)CPU 請求處理器硬體進入具有某些屬性的空閒狀態。 如果處理器中存在單元層次結構,則一個 struct cpuidle_state 物件可以覆蓋層次結構中不同級別的單元支援的空閒狀態的組合。 在這種情況下,它的目標駐留時間和退出延遲引數必須反映最深級別的空閒狀態的屬性(即包含所有其他單元的單元的空閒狀態)。

例如,假設一個處理器在稱為“模組”的更大單元中有兩個核心,並且假設透過一個核心要求硬體在“核心”級別進入特定的空閒狀態(例如“X”)將觸發模組嘗試進入其自身的特定空閒狀態(例如“MX”),如果另一個核心已經處於空閒狀態“X”。 換句話說,在“核心”級別請求空閒狀態“X”會授予硬體許可證以深入到“模組”級別的空閒狀態“MX”,但不能保證會發生這種情況(請求空閒狀態“X”的核心可能最終會自行進入該狀態)。 那麼,表示空閒狀態“X”的 struct cpuidle_state 物件的目標駐留時間必須反映模組在空閒狀態“MX”中花費的最短時間(包括進入該狀態所需的時間),因為這是 CPU 需要空閒的最短時間,以便在硬體進入該狀態的情況下節省任何能量。 類似地,該物件的退出延遲引數必須包括模組空閒狀態“MX”的退出時間(並且通常也包括其進入時間),因為這是喚醒訊號與 CPU 將開始執行第一個新指令的時間之間的最大延遲(假設模組中的兩個核心都將準備好在模組作為一個整體變得可操作後立即執行指令)。

然而,存在處理器,它們的內部單元層次結構的不同級別之間沒有直接協調。 在這些情況下,例如,在“核心”級別請求空閒狀態不會自動以任何方式影響“模組”級別,並且 CPUIdle 驅動程式負責層次結構的整個處理。 那麼,空閒狀態物件的定義完全由驅動程式決定,但處理器硬體最終進入的空閒狀態的物理屬性必須始終遵循調速器用於空閒狀態選擇的引數(例如,該空閒狀態的實際退出延遲不得超過調速器選擇的空閒狀態物件的退出延遲引數)。

除了上面討論的目標駐留時間和退出延遲空閒狀態引數之外,表示空閒狀態的物件還包含一些描述空閒狀態的其他引數和一個指向要執行的函式的指標,以便請求硬體進入該狀態。 此外,對於每個 struct cpuidle_state 物件,都有一個對應的 struct cpuidle_state_usage 物件,其中包含給定空閒狀態的使用統計資訊。 該資訊由核心透過 sysfs 公開。

對於系統中的每個 CPU,在 sysfs 中都有一個 /sys/devices/system/cpu/cpu<N>/cpuidle/ 目錄,其中數字 <N> 在初始化時分配給給定的 CPU。 該目錄包含一組名為 state0state1 等的子目錄,直到為給定 CPU 定義的空閒狀態物件的數量減一。 這些目錄中的每一個都對應於一個空閒狀態物件,並且其名稱中的數字越大,它表示的(有效)空閒狀態越深。 它們中的每一個都包含許多檔案(屬性),這些檔案表示與其對應的空閒狀態物件的屬性,如下所示:

上面

請求該空閒狀態的總次數,但觀察到的空閒持續時間肯定太短,無法與其目標駐留時間匹配。

以下

此空閒狀態被請求的總次數,但顯然更深層次的空閒狀態更適合觀察到的空閒持續時間。

描述

空閒狀態的描述。

停用

此空閒狀態是否被停用。

預設狀態

此狀態的預設狀態,“啟用”或“停用”。

延遲

空閒狀態的退出延遲,單位為微秒。

名稱

空閒狀態的名稱。

功耗

此空閒狀態下硬體消耗的功率,單位為毫瓦(如果指定,則為 0)。

駐留時間

空閒狀態的目標駐留時間,單位為微秒。

時間

給定 CPU 在此空閒狀態下花費的總時間(由核心測量),單位為微秒。

使用次數

給定 CPU 請求硬體進入此空閒狀態的總次數。

拒絕次數

在給定的 CPU 上,進入此空閒狀態的請求被拒絕的總次數。

descname 檔案都包含字串。它們之間的區別在於,name 期望更簡潔,而 description 可能更長,並且可能包含空格或特殊字元。上面列出的其他檔案包含整數。

disable 屬性是唯一可寫的屬性。如果它包含 1,則給定的空閒狀態對於此特定 CPU 被停用,這意味著調控器永遠不會為此特定 CPU 選擇它,並且 CPUIdle 驅動程式也永遠不會因此要求硬體進入它。但是,為一個 CPU 停用空閒狀態並不能阻止其他 CPU 請求它,因此必須為所有 CPU 停用它,以使其永遠不會被任何 CPU 請求。[請注意,由於 ladder 調控器的實現方式,停用空閒狀態也會阻止該調控器選擇比停用狀態更深層次的任何空閒狀態。]

如果 disable 屬性包含 0,則給定的空閒狀態對於此特定 CPU 啟用,但它仍然可能對於系統中的某些或所有其他 CPU 停用。 將 1 寫入其中會導致此特定 CPU 的空閒狀態被停用,將 0 寫入其中允許調控器將其納入給定 CPU 的考慮範圍,並允許驅動程式請求它,除非該狀態已在驅動程式中全域性停用(在這種情況下,它根本無法使用)。

power 屬性沒有明確定義,特別是對於代表處理器中不同級別單元層次結構中空閒狀態組合的空閒狀態物件而言,並且通常很難獲得複雜硬體的空閒狀態功耗數字,因此 power 通常包含 0(不可用),如果它包含非零數字,則該數字可能不是很準確,不應依賴於任何有意義的事情。

time 檔案中的數字通常可能大於給定 CPU 在給定空閒狀態下實際花費的總時間,因為它是由核心測量的,並且可能不包括硬體拒絕進入此空閒狀態並進入較淺的空閒狀態(甚至根本沒有進入任何空閒狀態)的情況。核心只能測量請求硬體進入空閒狀態到 CPU 隨後喚醒之間的時間跨度,並且它無法說明在此期間硬體級別上實際發生了什麼。此外,如果所討論的空閒狀態物件代表處理器中不同級別單元層次結構中空閒狀態的組合,則核心永遠無法說明硬體在任何特定情況下在該層次結構中下降了多深。出於這些原因,找出硬體在支援的不同空閒狀態下花費了多少時間的唯一可靠方法是使用硬體中的空閒狀態駐留計數器(如果可用)。

通常,在嘗試進入空閒狀態時收到的中斷會導致空閒狀態進入請求被拒絕,在這種情況下,CPUIdle 驅動程式可能會返回錯誤程式碼以指示這種情況。usagerejected 檔案分別報告給定空閒狀態成功進入或被拒絕的次數。

CPU 的電源管理服務質量

Linux 核心中的電源管理服務質量 (PM QoS) 框架允許核心程式碼和使用者空間程序設定核心各種節能功能的約束,以防止效能下降到低於要求的水平。

CPU 空閒時間管理可能受到 PM QoS 的兩種影響,一種是透過全域性 CPU 延遲限制,另一種是透過各個 CPU 的恢復延遲約束。核心程式碼(例如裝置驅動程式)可以使用 PM QoS 框架提供的特殊內部介面來設定這兩者。使用者空間可以透過開啟 /dev/ 下的 cpu_dma_latency 特殊裝置檔案並將二進位制值(解釋為帶符號的 32 位整數)寫入其中來修改前者。反過來,可以透過將字串(表示帶符號的 32 位整數)寫入 sysfs/sys/devices/system/cpu/cpu<N>/ 下的 power/pm_qos_resume_latency_us 檔案來從使用者空間修改 CPU 的恢復延遲約束,其中 CPU 編號 <N> 是在系統初始化時分配的。在兩種情況下,都會拒絕負值,並且在兩種情況下,寫入的整數將被解釋為以微秒為單位的請求的 PM QoS 約束。

但是,請求的值不會自動應用為新約束,因為它可能不如之前其他人請求的約束嚴格(在此特定情況下更大)。因此,PM QoS 框架維護一個迄今為止針對全域性 CPU 延遲限制和每個單獨的 CPU 發出的請求列表,聚合它們並將有效(在這種特定情況下最小)值作為新約束應用。

實際上,開啟 cpu_dma_latency 特殊裝置檔案會導致建立一個新的 PM QoS 請求,並將其新增到 CPU 延遲限制請求的全域性優先順序列表中,並且來自“開啟”操作的檔案描述符代表該請求。如果該檔案描述符隨後用於寫入,則寫入其中的數字將與由其表示的 PM QoS 請求相關聯,作為新的請求限制值。接下來,將使用優先順序列表機制來確定整個請求列表的新的有效值,並將該有效值設定為新的 CPU 延遲限制。因此,請求新的限制值只會更改實際限制,如果有效“列表”值受到它的影響,如果它是列表中請求的最小值,則會發生這種情況。

持有透過開啟 cpu_dma_latency 特殊裝置檔案獲得的檔案描述符的程序控制與該檔案描述符關聯的 PM QoS 請求,但它只控制此特定 PM QoS 請求。

關閉 cpu_dma_latency 特殊裝置檔案,或者更準確地說,關閉開啟它時獲得的檔案描述符,會導致與該檔案描述符關聯的 PM QoS 請求從 CPU 延遲限制請求的全域性優先順序列表中刪除並銷燬。如果發生這種情況,將再次使用優先順序列表機制來確定整個列表的新的有效值,並且該值將成為新的限制。

反過來,對於每個 CPU,都有一個與 sysfs/sys/devices/system/cpu/cpu<N>/ 下的 power/pm_qos_resume_latency_us 檔案關聯的恢復延遲 PM QoS 請求,並且寫入該檔案會導致此單個 PM QoS 請求被更新,無論哪個使用者空間程序執行此操作。換句話說,此 PM QoS 請求由整個使用者空間共享,因此需要仲裁對與之關聯的檔案的訪問,以避免混淆。[可以說,實際上使用此機制的唯一合法方法是將程序固定到所討論的 CPU,並讓它使用 sysfs 介面來控制它的恢復延遲約束。] 然而,這仍然只是一個請求。 它是優先順序列表中的一個條目,用於確定要設定為 CPU 的恢復延遲約束的有效值,每次以這種或其他方式更新請求列表時(該列表中可能還有來自核心程式碼的其他請求)。

CPU 空閒時間調控器應將全域性(有效)CPU 延遲限制和給定 CPU 的有效恢復延遲約束的最小值視為允許它們為該 CPU 選擇的空閒狀態的退出延遲的上限。它們絕不應選擇任何退出延遲超過該限制的空閒狀態。

透過核心命令列控制空閒狀態

除了允許 為各個 CPU 停用各個空閒狀態的 sysfs 介面之外,還有影響 CPU 空閒時間管理的核心命令列引數。

可以使用 cpuidle.off=1 核心命令列選項完全停用 CPU 空閒時間管理。它不會阻止空閒迴圈在空閒 CPU 上執行,但會阻止呼叫 CPU 空閒時間調控器和驅動程式。如果將其新增到核心命令列,則空閒迴圈將透過 CPU 架構支援程式碼要求硬體在空閒 CPU 上進入空閒狀態,該程式碼應為此目的提供預設機制。但是,該預設機制通常是實現所討論架構(即 CPU 指令集)的所有處理器的最小公分母,因此它相當粗糙且效率不高。因此,不建議用於生產用途。

cpuidle.governor= 核心命令列開關允許指定要使用的 CPUIdle 調控器。必須在後面附加一個與可用調控器的名稱匹配的字串(例如 cpuidle.governor=menu),並且將使用該調控器而不是預設調控器。例如,可以強制在預設情況下使用 ladder 調控器的系統上使用 menu 調控器。

下面描述的控制 CPU 空閒時間管理的其他核心命令列引數僅與 *x86* 架構相關,並且對 intel_idle 的引用僅影響 Intel 處理器。

*x86* 架構支援程式碼識別與 CPU 空閒時間管理相關的三個核心命令列選項:idle=pollidle=haltidle=nomwait。前兩個選項完全停用 acpi_idleintel_idle 驅動程式,這有效地導致整個 CPUIdle 子系統被停用,並使空閒迴圈呼叫架構支援程式碼來處理空閒 CPU。它如何做到這一點取決於哪個引數新增到核心命令列中。在 idle=halt 情況下,架構支援程式碼將使用 CPU 的 HLT 指令(通常,它會暫停程式的執行並導致硬體嘗試進入最淺的可用空閒狀態)為此目的,如果使用 idle=poll,則空閒 CPU 將在緊密迴圈中執行或多或少“輕量級”的指令序列。[請注意,在許多情況下使用 idle=poll 有點極端,因為阻止空閒 CPU 節省幾乎任何能量可能不是它唯一的後果。例如,在 Intel 硬體上,它可以有效地阻止 CPU 使用需要程式包中任意數量的 CPU 處於空閒狀態的 P 狀態(參見 CPU 效能調節),因此它很可能會損害單執行緒計算的效能以及能源效率。因此,出於效能原因使用它可能根本不是一個好主意。]

idle=nomwait 選項阻止使用 CPU 的 MWAIT 指令進入空閒狀態。 使用此選項時,acpi_idle 驅動程式將使用 HLT 指令而不是 MWAIT。在執行 Intel 處理器的系統上,此選項停用 intel_idle 驅動程式,並強制使用 acpi_idle 驅動程式。請注意,在任何一種情況下,acpi_idle 驅動程式只有在系統 ACPI 表中包含它所需的所有資訊時才能正常工作。

除了影響 CPU 空閒時間管理的架構級核心命令列選項之外,還有影響各個 CPUIdle 驅動程式的引數,這些引數可以透過核心命令列傳遞給它們。具體來說,intel_idle.max_cstate=<n>processor.max_cstate=<n> 引數,其中 <n> 是空閒狀態索引,也用於 sysfs 中給定狀態目錄的名稱(參見 空閒狀態的表示),導致 intel_idleacpi_idle 驅動程式分別丟棄所有比空閒狀態 <n> 更深層次的空閒狀態。在這種情況下,它們永遠不會請求任何這些空閒狀態或將它們暴露給調控器。[對於等於 0<n>,這兩個驅動程式的行為不同。將 intel_idle.max_cstate=0 新增到核心命令列會停用 intel_idle 驅動程式並允許使用 acpi_idle,而 processor.max_cstate=0 等效於 processor.max_cstate=1。此外,acpi_idle 驅動程式是 processor 核心模組的一部分,可以單獨載入,並且可以在載入時將 max_cstate=<n> 作為模組引數傳遞給它。]