内核中的 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_mask
或 for_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
文件 offline、online、possible、present 代表 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。
这些函数在处理已安装回调的方式上有所不同
cpuhp_setup_state_nocalls()
、cpuhp_setup_state_nocalls_cpuslocked()
和cpuhp_setup_state_multi()
只安装回调
cpuhp_setup_state()
和cpuhp_setup_state_cpuslocked()
安装回调并为所有当前状态大于新安装状态的在线 CPU 调用 @startup 回调(如果非 NULL)。根据状态部分,回调要么在当前 CPU 上调用(PREPARE 部分),要么在每个在线 CPU 上调用(ONLINE 部分),在 CPU 的热插拔线程上下文中。如果 CPU N 的回调失败,则调用 CPU 0 .. N-1 的关闭回调以回滚操作。状态设置失败,状态回调未安装,并且在动态分配的情况下,已分配的状态被释放。
状态设置和回调调用与 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*() 在动态范围内分配的状态号。如果状态在动态范围内,则状态号将被释放并再次可用于动态分配。
这些函数在处理已安装回调的方式上有所不同
cpuhp_remove_state_nocalls()
、cpuhp_remove_state_nocalls_cpuslocked()
和cpuhp_remove_multi_state()
只移除回调。
cpuhp_remove_state()
移除回调并为所有当前状态大于已移除状态的在线 CPU 调用关闭回调(如果非 NULL)。根据状态部分,回调要么在当前 CPU 上调用(PREPARE 部分),要么在每个在线 CPU 上调用(ONLINE 部分),在 CPU 的热插拔线程上下文中。为了完成移除,关闭回调不应失败。
状态移除和回调调用与 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
此单个状态的节点。
描述
移除实例,但不调用关闭回调。