掛起程式碼 (S3) 與 CPU 熱插拔基礎設施的互動¶
2011 - 2014 Srivatsa S. Bhat <srivatsa.bhat@linux.vnet.ibm.com>
一、CPU 熱插拔和掛起到記憶體之間的差異¶
常規 CPU 熱插拔程式碼與掛起到記憶體基礎設施內部使用它的方式有何不同? 它們在哪裡共享公共程式碼?
好吧,一圖勝千言……所以下面是 ASCII 藝術 :-)
[這描述了核心中的當前設計,並且僅關注涉及 freezer 和 CPU 熱插拔的互動,並且還嘗試解釋所涉及的鎖定。 它概述了所涉及的通知。 但請注意,此處僅說明了呼叫路徑,目的是描述它們在哪裡採用不同的路徑以及在哪裡共享程式碼。 此處未描述常規 CPU 熱插拔和掛起到記憶體彼此競爭時會發生什麼。]
在較高的層面上,掛起恢復週期如下所示
|Freeze| -> |Disable nonboot| -> |Do suspend| -> |Enable nonboot| -> |Thaw |
|tasks | | cpus | | | | cpus | |tasks|
更多細節如下
Suspend call path
-----------------
Write 'mem' to
/sys/power/state
sysfs file
|
v
Acquire system_transition_mutex lock
|
v
Send PM_SUSPEND_PREPARE
notifications
|
v
Freeze tasks
|
|
v
freeze_secondary_cpus()
/* start */
|
v
Acquire cpu_add_remove_lock
|
v
Iterate over CURRENTLY
online CPUs
|
|
| ----------
v | L
======> _cpu_down() |
| [This takes cpuhotplug.lock |
Common | before taking down the CPU |
code | and releases it when done] | O
| While it is at it, notifications |
| are sent when notable events occur, |
======> by running all registered callbacks. |
| | O
| |
| |
v |
Note down these cpus in | P
frozen_cpus mask ----------
|
v
Disable regular cpu hotplug
by increasing cpu_hotplug_disabled
|
v
Release cpu_add_remove_lock
|
v
/* freeze_secondary_cpus() complete */
|
v
Do suspend
恢復也是如此,對應的部分是(按照恢復期間的執行順序)
thaw_secondary_cpus(),涉及
| Acquire cpu_add_remove_lock | Decrease cpu_hotplug_disabled, thereby enabling regular cpu hotplug | Call _cpu_up() [for all those cpus in the frozen_cpus mask, in a loop] | Release cpu_add_remove_lock v
解凍任務
傳送 PM_POST_SUSPEND 通知
釋放 system_transition_mutex 鎖。
需要注意的是,system_transition_mutex 鎖是在一開始就獲取的,即當我們剛開始掛起時,並且只有在整個週期完成(即,掛起 + 恢復)後才會釋放。
Regular CPU hotplug call path
-----------------------------
Write 0 (or 1) to
/sys/devices/system/cpu/cpu*/online
sysfs file
|
|
v
cpu_down()
|
v
Acquire cpu_add_remove_lock
|
v
If cpu_hotplug_disabled > 0
return gracefully
|
|
v
======> _cpu_down()
| [This takes cpuhotplug.lock
Common | before taking down the CPU
code | and releases it when done]
| While it is at it, notifications
| are sent when notable events occur,
======> by running all registered callbacks.
|
|
v
Release cpu_add_remove_lock
[That's it!, for
regular CPU hotplug]
因此,從兩個圖中可以看出(標記為“通用程式碼”的部分),常規 CPU 熱插拔和掛起程式碼路徑在 _cpu_down() 和 _cpu_up() 函式處收斂。 它們傳遞給這些函式的引數不同,即在常規 CPU 熱插拔期間,‘tasks_frozen’ 引數傳遞為 0。但是在掛起期間,由於任務在非啟動 CPU 離線或聯機時已經被凍結,因此呼叫 _cpu_*() 函式時,‘tasks_frozen’ 引數設定為 1。 [請參閱下文了解有關此問題的一些已知問題。]
重要檔案和函式/入口點:¶
kernel/power/process.c : freeze_processes(), thaw_processes()
kernel/power/suspend.c : suspend_prepare(), suspend_enter(), suspend_finish()
kernel/cpu.c: cpu_[up|down](), _cpu_[up|down](), [disable|enable]_nonboot_cpus()
二、CPU 熱插拔涉及哪些問題?¶
CPU 熱插拔和 CPU 上的微程式碼更新涉及一些有趣的情況,如下所述
[請記住,核心使用 request_firmware() 函式(定義在 drivers/base/firmware_loader/main.c 中)從使用者空間請求微程式碼映像。]
當所有 CPU 都相同時
這是最常見的情況,並且非常簡單:我們希望將相同的微程式碼修訂應用於每個 CPU。 以 x86 為例,定義在 arch/x86/kernel/microcode_core.c 中的 collect_cpu_info() 函式有助於發現 CPU 的型別,從而將正確的微程式碼修訂應用於它。 但請注意,核心不維護所有 CPU 的通用微程式碼映像,以便處理下面描述的“b”情況。
當某些 CPU 與其餘 CPU 不同時
在這種情況下,由於我們可能需要將不同的微程式碼修訂應用於不同的 CPU,因此核心為每個 CPU 維護正確的微程式碼映像的副本(在使用 collect_cpu_info() 等函式進行適當的 CPU 型別/型號發現之後)。
當 CPU 被物理熱插拔並且一個新的(並且可能是不同型別的)CPU 熱插拔到系統中時
在核心的當前設計中,每當 CPU 在常規 CPU 熱插拔操作期間離線時,在收到 CPU 熱插拔程式碼傳送的 CPU_DEAD 通知後,微程式碼更新驅動程式針對該事件的回撥透過釋放核心中該 CPU 的微程式碼映像的副本做出反應。
因此,當一個新的 CPU 上線時,由於核心發現它沒有微程式碼映像,因此它會重新進行 CPU 型別/型號發現,然後向用戶空間請求該 CPU 的適當微程式碼映像,然後將其應用。
例如,在 x86 中,mc_cpu_callback() 函式(它是為 CPU 熱插拔事件註冊的微程式碼更新驅動程式的回撥)呼叫 microcode_update_cpu(),在這種情況下,它將呼叫 microcode_init_cpu(),而不是 microcode_resume_cpu(),因為它發現核心沒有有效的微程式碼映像。 這確保在從使用者空間獲取 CPU 後,執行 CPU 型別/型號發現並將正確的微程式碼應用於 CPU。
處理掛起/休眠期間的微程式碼更新
嚴格來說,在不涉及物理移除或插入 CPU 的 CPU 熱插拔操作期間,CPU 在 CPU 離線期間實際上並未斷電。 它們只是被置於儘可能低的 C 狀態。 因此,在這種情況下,當 CPU 重新聯機時,實際上沒有必要重新應用微程式碼,因為它們在 CPU 離線操作期間不會丟失映像。
這是在掛起後恢復期間遇到的常見情況。 但是,在休眠的情況下,由於所有 CPU 都完全斷電,因此在還原期間,必須將微程式碼映像應用於所有 CPU。
[請注意,我們不希望有人在掛起恢復或休眠/還原週期之間物理拔出節點並插入具有不同型別 CPU 的節點。]
但是,在核心的當前設計中,在作為掛起/休眠週期的一部分的 CPU 離線操作期間(cpuhp_tasks_frozen 設定為 true),核心中現有微程式碼映像的副本不會被釋放。 並且在 CPU 聯機操作期間(在恢復/還原期間),由於核心發現它已經擁有所有 CPU 的微程式碼映像的副本,因此它只是將它們應用於 CPU,避免了任何 CPU 型別/型號的重新發現,也避免了驗證微程式碼修訂是否適合 CPU 的需要(由於上述假設,即物理 CPU 熱插拔不會在掛起/恢復或休眠/還原週期之間完成)。
三、已知問題¶
當常規 CPU 熱插拔和掛起相互競爭時,是否存在任何已知問題?
是的,它們在下面列出
在呼叫常規 CPU 熱插拔時,傳遞給 _cpu_down() 和 _cpu_up() 函式的 ‘tasks_frozen’ 引數始終為 0。 這可能無法反映系統的真實當前狀態,因為任務可能已被帶外事件(例如正在進行的掛起操作)凍結。 因此,cpuhp_tasks_frozen 變數將不會反映凍結狀態,並且評估該變數的 CPU 熱插拔回調可能會執行錯誤的程式碼路徑。
如果常規 CPU 熱插拔壓力測試恰好與 freezer 競爭,因為同時正在進行掛起操作,那麼我們可能會遇到以下情況
常規 CPU 聯機操作繼續從使用者空間進入核心,因為凍結尚未開始。
然後 freezer 開始工作並凍結使用者空間。
如果 CPU 聯機現在尚未完成微程式碼更新,它現在將開始在 TASK_UNINTERRUPTIBLE 狀態下等待凍結的使用者空間,以便獲取微程式碼映像。
現在 freezer 繼續並嘗試凍結剩餘的任務。 但是由於上面提到的等待,freezer 將無法凍結 CPU 聯機熱插拔任務,因此任務凍結失敗。
由於此任務凍結失敗,因此掛起操作被中止。