核心中的 CPU 熱插拔

日期:

2021 年 9 月

作者:

Sebastian Andrzej Siewior <bigeasy@linutronix.de>, Rusty Russell <rusty@rustcorp.com.au>, Srivatsa Vaddagiri <vatsa@in.ibm.com>, Ashok Raj <ashok.raj@intel.com>, Joel Schopp <jschopp@austin.ibm.com>, Thomas Gleixner <tglx@linutronix.de>

簡介

系統架構的現代進步在處理器中引入了高階錯誤報告和糾正功能。有一些 OEM 廠商支援同樣支援熱插拔的 NUMA 硬體,其中物理節點的插入和移除需要 CPU 熱插拔支援。

這些進步要求可以從核心中移除可用的 CPU,無論是出於配置原因,還是為了 RAS 目的,以使故障 CPU 遠離系統執行路徑。因此,Linux 核心中需要 CPU 熱插拔支援。

CPU 熱插拔支援的一個更具新穎性的用途是它目前在 SMP 的掛起恢復支援中的應用。雙核和超執行緒(HT)支援使得即使是筆記型電腦也能執行 SMP 核心,而這些方法以前是不支援的。

命令列開關

maxcpus=n

將啟動時 CPU 限制為 *n* 個。例如,如果您有四個 CPU,使用 maxcpus=2 將只啟動兩個。您可以選擇稍後將其他 CPU 上線。

nr_cpus=n

限制核心將支援的 CPU 總數。如果此處提供的數字低於物理可用 CPU 的數量,那麼這些 CPU 將無法在以後上線。

possible_cpus=n

此選項在 cpu_possible_mask 中設定 possible_cpus 位。

此選項僅限於 X86 和 S390 架構。

cpu0_hotplug

允許關閉 CPU0。

此選項僅限於 X86 架構。

CPU 對映

cpu_possible_mask

系統中可能可用的 CPU 點陣圖。這用於為 per_cpu 變數分配一些啟動時記憶體,這些變數不是為了隨 CPU 的可用或移除而增長/收縮而設計的。一旦在啟動時發現階段設定,該對映就是靜態的,即任何時候都不會新增或移除位。根據您的系統需求提前精確裁剪可以節省一些啟動時記憶體。

cpu_online_mask

所有當前線上 CPU 的點陣圖。在 CPU 可用於核心排程並準備好接收來自裝置的 interrupts 後,它會在 __cpu_up() 中設定。當使用 __cpu_disable() 關閉 CPU 時,它會被清除,在此之前所有作業系統服務(包括 interrupts)都已遷移到另一個目標 CPU。

cpu_present_mask

系統中當前存在的 CPU 點陣圖。並非所有 CPU 都可能線上。當相關子系統(例如 ACPI)處理物理熱插拔時,根據事件是熱新增/熱移除,對映中的新位可以新增或移除。目前沒有鎖定規則。典型用法是在啟動期間初始化拓撲,此時熱插拔被停用。

你實際上不需要操作任何系統 CPU 對映。它們對於大多數用途來說應該是隻讀的。在設定每 CPU 資源時,幾乎總是使用 cpu_possible_maskfor_each_possible_cpu() 進行迭代。宏 for_each_cpu() 可用於迭代自定義 CPU 掩碼。

除了 cpumask_t 之外,不要使用任何其他東西來表示 CPU 點陣圖。

使用 CPU 熱插拔

核心選項 CONFIG_HOTPLUG_CPU 需要啟用。它目前在多種架構上可用,包括 ARM、MIPS、PowerPC 和 X86。配置透過 sysfs 介面完成。

$ ls -lh /sys/devices/system/cpu
total 0
drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu0
drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu1
drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu2
drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu3
drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu4
drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu5
drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu6
drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu7
drwxr-xr-x  2 root root    0 Dec 21 16:33 hotplug
-r--r--r--  1 root root 4.0K Dec 21 16:33 offline
-r--r--r--  1 root root 4.0K Dec 21 16:33 online
-r--r--r--  1 root root 4.0K Dec 21 16:33 possible
-r--r--r--  1 root root 4.0K Dec 21 16:33 present

檔案 offlineonlinepossiblepresent 代表 CPU 掩碼。每個 CPU 資料夾都包含一個 online 檔案,用於控制邏輯開啟 (1) 和關閉 (0) 狀態。要邏輯關閉 CPU4

$ echo 0 > /sys/devices/system/cpu/cpu4/online
 smpboot: CPU 4 is now offline

一旦 CPU 關閉,它將從 /proc/interrupts/proc/cpuinfo 中移除,並且不應由 top 命令顯示。要將 CPU4 重新上線

$ echo 1 > /sys/devices/system/cpu/cpu4/online
smpboot: Booting Node 0 Processor 4 APIC 0x1

CPU 再次可用。這應該適用於所有 CPU,但 CPU0 通常是特殊的,並被排除在 CPU 熱插拔之外。

CPU 熱插拔協調

離線情況

一旦 CPU 被邏輯關閉,已註冊熱插拔狀態的 teardown 回撥將被呼叫,從 CPUHP_ONLINE 開始,到狀態 CPUHP_OFFLINE 結束。這包括:

  • 如果任務因掛起操作而凍結,則 cpuhp_tasks_frozen 將設定為 true。

  • 所有程序都從這個即將離線的 CPU 遷移到新的 CPU。新 CPU 從每個程序的當前 cpuset 中選擇,該 cpuset 可能是所有線上 CPU 的子集。

  • 針對此 CPU 的所有中斷都遷移到新的 CPU

  • 定時器也遷移到新的 CPU

  • 一旦所有服務都遷移完成,核心會呼叫一個特定於架構的例程 __cpu_disable() 來執行架構特定的清理。

CPU 熱插拔 API

CPU 熱插拔狀態機

CPU 熱插拔使用一個簡單的狀態機,其狀態空間從 CPUHP_OFFLINE 到 CPUHP_ONLINE 呈線性。每個狀態都有一個啟動回撥和一個關閉回撥。

當 CPU 上線時,啟動回撥會按順序呼叫,直到達到狀態 CPUHP_ONLINE。當設定狀態回撥或將例項新增到多例項狀態時,也可以呼叫它們。

當 CPU 離線時,關閉回撥會按相反順序呼叫,直到達到狀態 CPUHP_OFFLINE。當移除狀態回撥或從多例項狀態中移除例項時,也可以呼叫它們。

如果使用方只需要熱插拔操作的單向回撥(CPU 上線或 CPU 離線),那麼在設定狀態時,可以將不需要的回撥設定為 NULL。

狀態空間分為三個部分

  • PREPARE 階段

    PREPARE 階段覆蓋從 CPUHP_OFFLINE 到 CPUHP_BRINGUP_CPU 的狀態空間。

    此階段的啟動回撥在 CPU 上線操作期間,CPU 啟動之前呼叫。關閉回撥在 CPU 離線操作期間,CPU 失效後呼叫。

    回撥在控制 CPU 上呼叫,因為它們顯然不能在尚未啟動或已經失效的熱插拔 CPU 上執行。

    啟動回撥用於設定成功使 CPU 上線所需的資源。關閉回撥用於釋放資源,或在熱插拔 CPU 失效後將待處理的工作移動到線上 CPU。

    啟動回撥允許失敗。如果回撥失敗,CPU 上線操作將被中止,並且 CPU 將再次回到先前的狀態(通常是 CPUHP_OFFLINE)。

    此階段的關閉回撥不允許失敗。

  • STARTING 階段

    STARTING 階段覆蓋 CPUHP_BRINGUP_CPU + 1 和 CPUHP_AP_ONLINE 之間的狀態空間。

    此階段的啟動回撥在 CPU 上線操作期間,在早期 CPU 設定程式碼中,在熱插拔 CPU 上且中斷停用時呼叫。關閉回撥在 CPU 離線操作期間,在 CPU 完全關閉前不久,在熱插拔 CPU 上且中斷停用時呼叫。

    此階段的回撥不允許失敗。

    回撥用於底層硬體初始化/關閉和核心子系統。

  • ONLINE 階段

    ONLINE 階段覆蓋 CPUHP_AP_ONLINE + 1 和 CPUHP_ONLINE 之間的狀態空間。

    此階段的啟動回撥在 CPU 上線操作期間在熱插拔 CPU 上呼叫。關閉回撥在 CPU 離線操作期間在熱插拔 CPU 上呼叫。

    回撥在每 CPU 熱插拔執行緒的上下文中呼叫,該執行緒被固定在熱插拔 CPU 上。回撥在中斷和搶佔啟用時呼叫。

    回撥允許失敗。當回撥失敗時,熱插拔操作中止,CPU 返回到先前的狀態。

CPU 上線/離線操作

成功的上線操作如下所示

[CPUHP_OFFLINE]
[CPUHP_OFFLINE + 1]->startup()       -> success
[CPUHP_OFFLINE + 2]->startup()       -> success
[CPUHP_OFFLINE + 3]                  -> skipped because startup == NULL
...
[CPUHP_BRINGUP_CPU]->startup()       -> success
=== End of PREPARE section
[CPUHP_BRINGUP_CPU + 1]->startup()   -> success
...
[CPUHP_AP_ONLINE]->startup()         -> success
=== End of STARTUP section
[CPUHP_AP_ONLINE + 1]->startup()     -> success
...
[CPUHP_ONLINE - 1]->startup()        -> success
[CPUHP_ONLINE]

成功的離線操作如下所示

[CPUHP_ONLINE]
[CPUHP_ONLINE - 1]->teardown()       -> success
...
[CPUHP_AP_ONLINE + 1]->teardown()    -> success
=== Start of STARTUP section
[CPUHP_AP_ONLINE]->teardown()        -> success
...
[CPUHP_BRINGUP_ONLINE - 1]->teardown()
...
=== Start of PREPARE section
[CPUHP_BRINGUP_CPU]->teardown()
[CPUHP_OFFLINE + 3]->teardown()
[CPUHP_OFFLINE + 2]                  -> skipped because teardown == NULL
[CPUHP_OFFLINE + 1]->teardown()
[CPUHP_OFFLINE]

失敗的上線操作如下所示

[CPUHP_OFFLINE]
[CPUHP_OFFLINE + 1]->startup()       -> success
[CPUHP_OFFLINE + 2]->startup()       -> success
[CPUHP_OFFLINE + 3]                  -> skipped because startup == NULL
...
[CPUHP_BRINGUP_CPU]->startup()       -> success
=== End of PREPARE section
[CPUHP_BRINGUP_CPU + 1]->startup()   -> success
...
[CPUHP_AP_ONLINE]->startup()         -> success
=== End of STARTUP section
[CPUHP_AP_ONLINE + 1]->startup()     -> success
---
[CPUHP_AP_ONLINE + N]->startup()     -> fail
[CPUHP_AP_ONLINE + (N - 1)]->teardown()
...
[CPUHP_AP_ONLINE + 1]->teardown()
=== Start of STARTUP section
[CPUHP_AP_ONLINE]->teardown()
...
[CPUHP_BRINGUP_ONLINE - 1]->teardown()
...
=== Start of PREPARE section
[CPUHP_BRINGUP_CPU]->teardown()
[CPUHP_OFFLINE + 3]->teardown()
[CPUHP_OFFLINE + 2]                  -> skipped because teardown == NULL
[CPUHP_OFFLINE + 1]->teardown()
[CPUHP_OFFLINE]

失敗的離線操作如下所示

[CPUHP_ONLINE]
[CPUHP_ONLINE - 1]->teardown()       -> success
...
[CPUHP_ONLINE - N]->teardown()       -> fail
[CPUHP_ONLINE - (N - 1)]->startup()
...
[CPUHP_ONLINE - 1]->startup()
[CPUHP_ONLINE]

遞迴失敗無法合理處理。請看以下由於離線操作失敗導致的遞迴失敗示例

[CPUHP_ONLINE]
[CPUHP_ONLINE - 1]->teardown()       -> success
...
[CPUHP_ONLINE - N]->teardown()       -> fail
[CPUHP_ONLINE - (N - 1)]->startup()  -> success
[CPUHP_ONLINE - (N - 2)]->startup()  -> fail

CPU 熱插拔狀態機在此停止,不再嘗試再次下線,因為那很可能導致無限迴圈

[CPUHP_ONLINE - (N - 1)]->teardown() -> success
[CPUHP_ONLINE - N]->teardown()       -> fail
[CPUHP_ONLINE - (N - 1)]->startup()  -> success
[CPUHP_ONLINE - (N - 2)]->startup()  -> fail
[CPUHP_ONLINE - (N - 1)]->teardown() -> success
[CPUHP_ONLINE - N]->teardown()       -> fail

反覆操作。在這種情況下,CPU 停留在狀態

[CPUHP_ONLINE - (N - 1)]

這至少能讓系統繼續執行,併為使用者提供除錯甚至解決問題的機會。

分配狀態

有兩種方法可以分配 CPU 熱插拔狀態

  • 靜態分配

    當子系統或驅動程式對其他 CPU 熱插拔狀態有排序要求時,必須使用靜態分配。例如,在 CPU 上線操作期間,PERF 核心啟動回撥必須在 PERF 驅動程式啟動回撥之前呼叫。在 CPU 離線操作期間,驅動程式關閉回撥必須在核心關閉回撥之前呼叫。靜態分配的狀態由 `cpuhp_state` 列舉中的常量描述,該列舉可在 `include/linux/cpuhotplug.h` 中找到。

    將狀態插入列舉的適當位置,以滿足排序要求。狀態常量必須用於狀態設定和移除。

    當狀態回撥不是在執行時設定,並且是 `kernel/cpu.c` 中 CPU 熱插拔狀態陣列初始化器的一部分時,也需要靜態分配。

  • 動態分配

    當狀態回撥沒有排序要求時,動態分配是首選方法。狀態號由設定函式分配,並在成功時返回給呼叫者。

    只有 PREPARE 和 ONLINE 部分提供動態分配範圍。STARTING 部分不提供動態分配範圍,因為該部分中的大多數回撥都有明確的排序要求。

設定 CPU 熱插拔狀態

核心程式碼提供了以下函式來設定狀態

  • cpuhp_setup_state(state, name, startup, teardown)

  • cpuhp_setup_state_nocalls(state, name, startup, teardown)

  • cpuhp_setup_state_cpuslocked(state, name, startup, teardown)

  • cpuhp_setup_state_nocalls_cpuslocked(state, name, startup, teardown)

對於驅動程式或子系統有多個例項且需要為每個例項呼叫相同的 CPU 熱插拔狀態回撥的情況,CPU 熱插拔核心提供了多例項支援。相對於驅動程式特定的例項列表,其優勢在於例項相關函式與 CPU 熱插拔操作完全序列化,並提供在新增和移除時自動呼叫狀態回撥的功能。要設定這樣的多例項狀態,可以使用以下函式

  • cpuhp_setup_state_multi(state, name, startup, teardown)

@state 引數是靜態分配的狀態,或者是動態分配狀態的常量之一——CPUHP_BP_PREPARE_DYN、CPUHP_AP_ONLINE_DYN——這取決於應為其分配動態狀態的狀態部分(PREPARE、ONLINE)。

@name 引數用於 sysfs 輸出和檢測。命名約定是 “subsys:mode” 或 “subsys/driver:mode”,例如 “perf:mode” 或 “perf/x86:mode”。常見的模式名稱是

prepare

用於 PREPARE 階段的狀態

dead

用於 PREPARE 階段中不提供啟動回撥的狀態

starting

用於 STARTING 階段的狀態

dying

用於 STARTING 階段中不提供啟動回撥的狀態

online

用於 ONLINE 階段的狀態

offline

用於 ONLINE 階段中不提供啟動回撥的狀態

由於 @name 引數僅用於 sysfs 和檢測,如果其他模式描述符能比通用模式更好地描述狀態的性質,也可以使用。

@name 引數示例:“perf/online”、“perf/x86:prepare”、“RCU/tree:dying”、“sched/waitempty”

@startup 引數是指向回撥函式的函式指標,該回調函式應在 CPU 上線操作期間呼叫。如果使用方不需要啟動回撥,則將指標設定為 NULL。

@teardown 引數是指向回撥函式的函式指標,該回調函式應在 CPU 離線操作期間呼叫。如果使用方不需要關閉回撥,則將指標設定為 NULL。

這些函式在處理已安裝回調的方式上有所不同

狀態設定和回撥呼叫與 CPU 熱插拔操作序列化。如果設定函式必須從 CPU 熱插拔讀鎖定區域呼叫,則必須使用 _cpuslocked() 變體。這些函式不能在 CPU 熱插拔回調內部使用。

函式返回值

0

靜態分配的狀態已成功設定

>0

動態分配的狀態已成功設定。

返回的數字是已分配的狀態號。如果稍後需要移除狀態回撥(例如模組移除),則呼叫者必須儲存此數字,並將其用作狀態移除函式的 @state 引數。對於多例項狀態,動態分配的狀態號也需要作為例項新增/移除操作的 @state 引數。

<0

操作失敗

移除 CPU 熱插拔狀態

為了移除先前設定的狀態,提供了以下函式

  • cpuhp_remove_state(state)

  • cpuhp_remove_state_nocalls(state)

  • cpuhp_remove_state_nocalls_cpuslocked(state)

  • cpuhp_remove_multi_state(state)

@state 引數是靜態分配的狀態,或由 cpuhp_setup_state*() 在動態範圍內分配的狀態號。如果狀態在動態範圍內,則狀態號將被釋放並再次可用於動態分配。

這些函式在處理已安裝回調的方式上有所不同

狀態移除和回撥呼叫與 CPU 熱插拔操作序列化。如果移除函式必須從 CPU 熱插拔讀鎖定區域呼叫,則必須使用 _cpuslocked() 變體。這些函式不能在 CPU 熱插拔回調內部使用。

如果移除了多例項狀態,則呼叫者必須首先移除所有例項。

多例項狀態管理

一旦設定了多例項狀態,就可以向該狀態新增例項

  • cpuhp_state_add_instance(state, node)

  • cpuhp_state_add_instance_nocalls(state, node)

@state 引數是靜態分配的狀態,或由 cpuhp_setup_state_multi() 在動態範圍內分配的狀態號。

@node 引數是指向 hlist_node 的指標,該 hlist_node 嵌入在例項的資料結構中。該指標會傳遞給多例項狀態回撥,回撥可以使用它透過 container_of() 獲取例項。

這些函式在處理已安裝回調的方式上有所不同

  • cpuhp_state_add_instance_nocalls() 只將例項新增到多例項狀態的節點列表中。

  • cpuhp_state_add_instance() 新增例項併為所有當前狀態大於 @state 的線上 CPU 呼叫與 @state 關聯的啟動回撥(如果非 NULL)。回撥僅為待新增的例項呼叫。根據狀態部分,回撥要麼在當前 CPU 上呼叫(PREPARE 部分),要麼在每個線上 CPU 上呼叫(ONLINE 部分),在 CPU 的熱插拔執行緒上下文中。

    如果 CPU N 的回撥失敗,則呼叫 CPU 0 .. N-1 的關閉回撥以回滾操作,函式失敗且例項未新增到多例項狀態的節點列表中。

要從狀態的節點列表中移除例項,可以使用以下函式

  • cpuhp_state_remove_instance(state, node)

  • cpuhp_state_remove_instance_nocalls(state, node)

引數與上述 cpuhp_state_add_instance*() 變體相同。

這些函式在處理已安裝回調的方式上有所不同

  • cpuhp_state_remove_instance_nocalls() 只從狀態的節點列表中移除例項。

  • cpuhp_state_remove_instance() 移除例項併為所有當前狀態大於 @state 的線上 CPU 呼叫與 @state 關聯的關閉回撥(如果非 NULL)。回撥僅為待移除的例項呼叫。根據狀態部分,回撥要麼在當前 CPU 上呼叫(PREPARE 部分),要麼在每個線上 CPU 上呼叫(ONLINE 部分),在 CPU 的熱插拔執行緒上下文中。

    為了完成移除,關閉回撥不應失敗。

節點列表的新增/移除操作和回撥呼叫與 CPU 熱插拔操作序列化。這些函式不能在 CPU 熱插拔回調和 CPU 熱插拔讀鎖定區域內部使用。

示例

在 STARTING 階段設定和關閉一個靜態分配的狀態,用於線上和離線操作的通知

ret = cpuhp_setup_state(CPUHP_SUBSYS_STARTING, "subsys:starting", subsys_cpu_starting, subsys_cpu_dying);
if (ret < 0)
     return ret;
....
cpuhp_remove_state(CPUHP_SUBSYS_STARTING);

在 ONLINE 階段設定和關閉一個動態分配的狀態,用於離線操作的通知

state = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "subsys:offline", NULL, subsys_cpu_offline);
if (state < 0)
    return state;
....
cpuhp_remove_state(state);

在 ONLINE 階段設定和關閉一個動態分配的狀態,用於線上操作的通知,而不呼叫回撥

state = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, "subsys:online", subsys_cpu_online, NULL);
if (state < 0)
    return state;
....
cpuhp_remove_state_nocalls(state);

在 ONLINE 階段設定、使用和關閉一個動態分配的多例項狀態,用於線上和離線操作的通知

state = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "subsys:online", subsys_cpu_online, subsys_cpu_offline);
if (state < 0)
    return state;
....
ret = cpuhp_state_add_instance(state, &inst1->node);
if (ret)
     return ret;
....
ret = cpuhp_state_add_instance(state, &inst2->node);
if (ret)
     return ret;
....
cpuhp_remove_instance(state, &inst1->node);
....
cpuhp_remove_instance(state, &inst2->node);
....
cpuhp_remove_multi_state(state);

熱插拔狀態測試

驗證自定義狀態是否按預期工作的一種方法是關閉 CPU,然後再次將其上線。也可以將 CPU 置於特定狀態(例如 CPUHP_AP_ONLINE),然後再返回到 CPUHP_ONLINE。這將模擬 CPUHP_AP_ONLINE 之後一個狀態的錯誤,從而導致回滾到線上狀態。

所有已註冊的狀態都列舉在 /sys/devices/system/cpu/hotplug/states

$ tail /sys/devices/system/cpu/hotplug/states
138: mm/vmscan:online
139: mm/vmstat:online
140: lib/percpu_cnt:online
141: acpi/cpu-drv:online
142: base/cacheinfo:online
143: virtio/net:online
144: x86/mce:online
145: printk:online
168: sched:active
169: online

要將 CPU4 回滾到 lib/percpu_cnt:online 並重新上線,只需執行

$ cat /sys/devices/system/cpu/cpu4/hotplug/state
169
$ echo 140 > /sys/devices/system/cpu/cpu4/hotplug/target
$ cat /sys/devices/system/cpu/cpu4/hotplug/state
140

重要的是要注意,狀態 140 的關閉回撥已被呼叫。現在重新上線

$ echo 169 > /sys/devices/system/cpu/cpu4/hotplug/target
$ cat /sys/devices/system/cpu/cpu4/hotplug/state
169

啟用跟蹤事件後,各個步驟也可見

#  TASK-PID   CPU#    TIMESTAMP  FUNCTION
#     | |       |        |         |
    bash-394  [001]  22.976: cpuhp_enter: cpu: 0004 target: 140 step: 169 (cpuhp_kick_ap_work)
 cpuhp/4-31   [004]  22.977: cpuhp_enter: cpu: 0004 target: 140 step: 168 (sched_cpu_deactivate)
 cpuhp/4-31   [004]  22.990: cpuhp_exit:  cpu: 0004  state: 168 step: 168 ret: 0
 cpuhp/4-31   [004]  22.991: cpuhp_enter: cpu: 0004 target: 140 step: 144 (mce_cpu_pre_down)
 cpuhp/4-31   [004]  22.992: cpuhp_exit:  cpu: 0004  state: 144 step: 144 ret: 0
 cpuhp/4-31   [004]  22.993: cpuhp_multi_enter: cpu: 0004 target: 140 step: 143 (virtnet_cpu_down_prep)
 cpuhp/4-31   [004]  22.994: cpuhp_exit:  cpu: 0004  state: 143 step: 143 ret: 0
 cpuhp/4-31   [004]  22.995: cpuhp_enter: cpu: 0004 target: 140 step: 142 (cacheinfo_cpu_pre_down)
 cpuhp/4-31   [004]  22.996: cpuhp_exit:  cpu: 0004  state: 142 step: 142 ret: 0
    bash-394  [001]  22.997: cpuhp_exit:  cpu: 0004  state: 140 step: 169 ret: 0
    bash-394  [005]  95.540: cpuhp_enter: cpu: 0004 target: 169 step: 140 (cpuhp_kick_ap_work)
 cpuhp/4-31   [004]  95.541: cpuhp_enter: cpu: 0004 target: 169 step: 141 (acpi_soft_cpu_online)
 cpuhp/4-31   [004]  95.542: cpuhp_exit:  cpu: 0004  state: 141 step: 141 ret: 0
 cpuhp/4-31   [004]  95.543: cpuhp_enter: cpu: 0004 target: 169 step: 142 (cacheinfo_cpu_online)
 cpuhp/4-31   [004]  95.544: cpuhp_exit:  cpu: 0004  state: 142 step: 142 ret: 0
 cpuhp/4-31   [004]  95.545: cpuhp_multi_enter: cpu: 0004 target: 169 step: 143 (virtnet_cpu_online)
 cpuhp/4-31   [004]  95.546: cpuhp_exit:  cpu: 0004  state: 143 step: 143 ret: 0
 cpuhp/4-31   [004]  95.547: cpuhp_enter: cpu: 0004 target: 169 step: 144 (mce_cpu_online)
 cpuhp/4-31   [004]  95.548: cpuhp_exit:  cpu: 0004  state: 144 step: 144 ret: 0
 cpuhp/4-31   [004]  95.549: cpuhp_enter: cpu: 0004 target: 169 step: 145 (console_cpu_notify)
 cpuhp/4-31   [004]  95.550: cpuhp_exit:  cpu: 0004  state: 145 step: 145 ret: 0
 cpuhp/4-31   [004]  95.551: cpuhp_enter: cpu: 0004 target: 169 step: 168 (sched_cpu_activate)
 cpuhp/4-31   [004]  95.552: cpuhp_exit:  cpu: 0004  state: 168 step: 168 ret: 0
    bash-394  [005]  95.553: cpuhp_exit:  cpu: 0004  state: 169 step: 140 ret: 0

可以看到,CPU4 在時間戳 22.996 之前下線,然後又在 95.552 之前上線。所有呼叫的回撥,包括它們的返回碼,都可以在跟蹤中看到。

架構要求

需要以下函式和配置

CONFIG_HOTPLUG_CPU

此條目需要在 Kconfig 中啟用

__cpu_up()

啟動 CPU 的架構介面

__cpu_disable()

關閉 CPU 的架構介面,例程返回後核心不再處理中斷。這包括定時器的關閉。

__cpu_die()

這實際上是為了確保 CPU 的死亡。實際上可以看看其他實現 CPU 熱插拔的架構中的一些示例程式碼。處理器從該特定架構的 idle() 迴圈中被移除。__cpu_die() 通常等待設定某些 `per_cpu` 狀態,以確保處理器死亡例程被呼叫,從而確鑿無疑。

使用者空間通知

CPU 成功上線或離線後,會發送 udev 事件。類似於這樣的 udev 規則

SUBSYSTEM=="cpu", DRIVERS=="processor", DEVPATH=="/devices/system/cpu/*", RUN+="the_hotplug_receiver.sh"

將接收所有事件。類似於這樣的指令碼

#!/bin/sh

if [ "${ACTION}" = "offline" ]
then
    echo "CPU ${DEVPATH##*/} offline"

elif [ "${ACTION}" = "online" ]
then
    echo "CPU ${DEVPATH##*/} online"

fi

可以進一步處理事件。

當系統中 CPU 發生變化時,如果核心自身更新 kdump 捕獲核心的 CPU 列表(透過 elfcorehdr 和其他相關的 kexec 段),則 sysfs 檔案 /sys/devices/system/cpu/crash_hotplug 包含 '1';如果使用者空間必須更新 kdump 捕獲核心的 CPU 列表,則包含 '0'。

可用性取決於 CONFIG_HOTPLUG_CPU 核心配置選項。

為了跳過使用者空間對 kdump 的 CPU 熱插拔/拔事件的處理(即透過解除安裝再重新載入獲取當前 CPU 列表),可以在 udev 規則中按如下方式使用此 sysfs 檔案

SUBSYSTEM==”cpu”, ATTRS{crash_hotplug}==”1”, GOTO=”kdump_reload_end”

對於 CPU 熱插拔/拔事件,如果架構支援核心更新 elfcorehdr(其中包含 CPU 列表)和其他相關的 kexec 段,則該規則會跳過 kdump 捕獲核心的解除安裝再重新載入。

核心內聯文件參考

int cpuhp_setup_state(enum cpuhp_state state, const char *name, int (*startup)(unsigned int cpu), int (*teardown)(unsigned int cpu))

設定熱插拔狀態回撥,並呼叫 **startup** 回撥

引數

enum cpuhp_state state

安裝回調的狀態

const char *name

回撥的名稱(將用於除錯輸出)

int (*startup)(unsigned int cpu)

啟動回撥函式,如果不需要則為 NULL

int (*teardown)(unsigned int cpu)

關閉回撥函式,如果不需要則為 NULL

描述

安裝回調函式,並在已達到該**狀態**的線上 CPU 上呼叫 **startup** 回撥。

int cpuhp_setup_state_cpuslocked(enum cpuhp_state state, const char *name, int (*startup)(unsigned int cpu), int (*teardown)(unsigned int cpu))

從 `cpus_read_lock()` 保持區域呼叫 **startup** 回撥來設定熱插拔狀態回撥

引數

enum cpuhp_state state

安裝回調的狀態

const char *name

回撥的名稱(將用於除錯輸出)

int (*startup)(unsigned int cpu)

啟動回撥函式,如果不需要則為 NULL

int (*teardown)(unsigned int cpu)

關閉回撥函式,如果不需要則為 NULL

描述

cpuhp_setup_state() 相同,但必須在 `cpus_read_lock()` 保持區域內呼叫。

int cpuhp_setup_state_nocalls(enum cpuhp_state state, const char *name, int (*startup)(unsigned int cpu), int (*teardown)(unsigned int cpu))

設定熱插拔狀態回撥,但不呼叫 **startup** 回撥

引數

enum cpuhp_state state

安裝回調的狀態

const char *name

回撥的名稱。

int (*startup)(unsigned int cpu)

啟動回撥函式,如果不需要則為 NULL

int (*teardown)(unsigned int cpu)

關閉回撥函式,如果不需要則為 NULL

描述

cpuhp_setup_state() 相同,但安裝期間不呼叫 **startup** 回撥。如果 SMP=n 或 HOTPLUG_CPU=n 則為無操作。

int cpuhp_setup_state_nocalls_cpuslocked(enum cpuhp_state state, const char *name, int (*startup)(unsigned int cpu), int (*teardown)(unsigned int cpu))

從 `cpus_read_lock()` 保持區域回撥設定熱插拔狀態回撥,但不呼叫 **startup** 回撥

引數

enum cpuhp_state state

安裝回調的狀態

const char *name

回撥的名稱。

int (*startup)(unsigned int cpu)

啟動回撥函式,如果不需要則為 NULL

int (*teardown)(unsigned int cpu)

關閉回撥函式,如果不需要則為 NULL

描述

cpuhp_setup_state_nocalls() 相同,但必須在 `cpus_read_lock()` 保持區域內呼叫。

int cpuhp_setup_state_multi(enum cpuhp_state state, const char *name, int (*startup)(unsigned int cpu, struct hlist_node *node), int (*teardown)(unsigned int cpu, struct hlist_node *node))

為多狀態添加回調

引數

enum cpuhp_state state

安裝回調的狀態

const char *name

回撥的名稱。

int (*startup)(unsigned int cpu, struct hlist_node *node)

啟動回撥函式,如果不需要則為 NULL

int (*teardown)(unsigned int cpu, struct hlist_node *node)

關閉回撥函式,如果不需要則為 NULL

描述

設定內部 `multi_instance` 標誌,並準備一個狀態作為多例項回撥工作。此時不呼叫任何回撥。一旦透過 cpuhp_state_add_instance()cpuhp_state_add_instance_nocalls() 註冊了此狀態的例項,就會呼叫回撥。

int cpuhp_state_add_instance(enum cpuhp_state state, struct hlist_node *node)

為狀態新增例項並呼叫啟動回撥。

引數

enum cpuhp_state state

安裝例項的狀態

struct hlist_node *node

此單個狀態的節點。

描述

安裝**狀態**的例項,並在已達到該**狀態**的線上 CPU 上呼叫已註冊的啟動回撥。該**狀態**必須之前已被 cpuhp_setup_state_multi() 標記為多例項。

int cpuhp_state_add_instance_nocalls(enum cpuhp_state state, struct hlist_node *node)

為狀態新增例項,但不呼叫啟動回撥。

引數

enum cpuhp_state state

安裝例項的狀態

struct hlist_node *node

此單個狀態的節點。

描述

安裝**狀態**的例項。該**狀態**必須之前已被 `cpuhp_setup_state_multi` 標記為多例項。如果 SMP=n 或 HOTPLUG_CPU=n 則為無操作。

int cpuhp_state_add_instance_nocalls_cpuslocked(enum cpuhp_state state, struct hlist_node *node)

從 `cpus_read_lock()` 保持區域為狀態新增例項,但不呼叫啟動回撥。

引數

enum cpuhp_state state

安裝例項的狀態

struct hlist_node *node

此單個狀態的節點。

描述

cpuhp_state_add_instance_nocalls() 相同,但必須在 `cpus_read_lock()` 保持區域內呼叫。

void cpuhp_remove_state(enum cpuhp_state state)

移除熱插拔狀態回撥並呼叫關閉

引數

enum cpuhp_state state

移除回撥的狀態

描述

移除回撥函式,並在已達到該**狀態**的線上 CPU 上呼叫關閉回撥。

void cpuhp_remove_state_nocalls(enum cpuhp_state state)

移除熱插拔狀態回撥,但不呼叫關閉回撥

引數

enum cpuhp_state state

移除回撥的狀態

void cpuhp_remove_state_nocalls_cpuslocked(enum cpuhp_state state)

從 `cpus_read_lock()` 保持區域移除熱插拔狀態回撥,但不呼叫關閉。

引數

enum cpuhp_state state

移除回撥的狀態

描述

與 cpuhp_remove_state_nocalls() 相同,但必須在 `cpus_read_lock()` 保持區域內呼叫。

void cpuhp_remove_multi_state(enum cpuhp_state state)

移除熱插拔多狀態回撥

引數

enum cpuhp_state state

移除回撥的狀態

描述

從多狀態中移除回撥函式。這是 cpuhp_setup_state_multi() 的反向操作。在呼叫此函式之前,所有例項都應已移除。

int cpuhp_state_remove_instance(enum cpuhp_state state, struct hlist_node *node)

從狀態中移除熱插拔例項並呼叫關閉回撥

引數

enum cpuhp_state state

移除例項的狀態

struct hlist_node *node

此單個狀態的節點。

描述

移除例項,並在已達到**狀態**的線上 CPU 上呼叫關閉回撥。

int cpuhp_state_remove_instance_nocalls(enum cpuhp_state state, struct hlist_node *node)

從狀態中移除熱插拔例項,但不呼叫關閉回撥

引數

enum cpuhp_state state

移除例項的狀態

struct hlist_node *node

此單個狀態的節點。

描述

移除例項,但不呼叫關閉回撥。