英语

内核中的 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_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 已被逻辑关闭,将调用注册的热插拔状态的拆卸回调,从 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。

这些函数在处理已安装的回调的方式上有所不同

状态设置和回调调用针对 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 的指针。该指针被传递给多实例状态回调,回调可以使用该指针通过 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

此单个状态的节点。

描述

移除实例,而不调用拆卸回调函数。