内核中的 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

此单个状态的节点。

描述

移除实例,但不调用关闭回调。