内核中的 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_up()
中设置,在 CPU 可用于内核调度并准备好接收来自设备的 interrupts 后。当使用__cpu_disable()
关闭 CPU 时,它会被清除,在此之前,包括 interrupts 在内的所有 OS 服务都会迁移到另一个目标 CPU。cpu_present_mask
当前系统中存在的 CPU 的位图。并非所有 CPU 都可能在线。当相关子系统(例如 ACPI)处理物理热插拔时,可以更改,并且根据事件是热添加/热移除,可以从映射中添加或删除新的位。目前没有锁定规则。典型的用法是在启动期间初始化拓扑,此时禁用热插拔。
您实际上不需要操作任何系统 CPU 映射。它们对于大多数用途应该是只读的。在设置 per-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 已被逻辑关闭,将调用注册的热插拔状态的拆卸回调,从 CPUHP_ONLINE
开始,并在状态 CPUHP_OFFLINE
处终止。这包括
如果由于挂起操作而冻结任务,则 cpuhp_tasks_frozen 将设置为 true。
所有进程都从这个即将离开的 CPU 迁移到新的 CPU。新的 CPU 是从每个进程的当前 cpuset 中选择的,该 cpuset 可能是所有在线 CPU 的一个子集。
定向到此 CPU 的所有中断都会迁移到新的 CPU
定时器也会迁移到新的 CPU
一旦所有服务都迁移完毕,内核会调用一个特定于 arch 的例程
__cpu_disable()
来执行特定于 arch 的清理。
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 上线操作将中止,并且 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 中找到。
将状态插入到枚举中的适当位置,以满足顺序要求。状态常量必须用于状态设置和删除。
当状态回调不是在运行时设置,而是内核/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 输出和检测。命名约定为“子系统:模式”或“子系统/驱动程序:模式”,例如“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 的指针。该指针被传递给多实例状态回调,回调可以使用该指针通过 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 的 Arch 接口
__cpu_disable()
用于关闭 CPU 的 Arch 接口,在该例程返回后,内核不能再处理中断。这包括关闭计时器。
__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 发生更改时,如果内核本身(通过 elfcorehdr 和其他相关的 kexec 段)更新 kdump 捕获内核的 CPU 列表,则 sysfs 文件 /sys/devices/system/cpu/crash_hotplug 包含“1”,否则包含“0”,表示用户空间必须更新 kdump 捕获内核的 CPU 列表。
可用性取决于 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
描述
安装回调函数,并在已达到 state 的在线 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,则为 NOP。
-
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
此单个状态的节点。
描述
为 state 安装实例,并在已达到 state 的在线 CPU 上调用注册的启动回调函数。 state 必须先前通过 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
此单个状态的节点。
描述
为 state 安装实例。 state 必须先前通过 cpuhp_setup_state_multi 标记为多实例。 如果 SMP=n 或 HOTPLUG_CPU=n,则为 NOP。
-
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
移除回调函数的状态
描述
移除回调函数,并在已达到 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
此单个状态的节点。
描述
移除实例,并在已达到 state 的在线 CPU 上调用拆卸回调函数。
-
int cpuhp_state_remove_instance_nocalls(enum cpuhp_state state, struct hlist_node *node)¶
从状态中移除热插拔实例,而不调用拆卸回调函数
参数
enum cpuhp_state state
从中移除实例的状态
struct hlist_node *node
此单个状态的节点。
描述
移除实例,而不调用拆卸回调函数。