Back to home page

OSCL-LXR

 
 

    


0001 .. include:: ../disclaimer-zh_CN.rst
0002 
0003 :Original: Documentation/core-api/cpu_hotplug.rst
0004 :翻译:
0005 
0006  司延腾 Yanteng Si <siyanteng@loongson.cn>
0007  周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>
0008 
0009 :校译:
0010 
0011  吴想成 Wu XiangCheng <bobwxc@email.cn>
0012 
0013 .. _cn_core_api_cpu_hotplug:
0014 
0015 =================
0016 内核中的CPU热拔插
0017 =================
0018 
0019 :时间: 2021年9月
0020 :作者: Sebastian Andrzej Siewior <bigeasy@linutronix.de>,
0021        Rusty Russell <rusty@rustcorp.com.au>,
0022        Srivatsa Vaddagiri <vatsa@in.ibm.com>,
0023        Ashok Raj <ashok.raj@intel.com>,
0024        Joel Schopp <jschopp@austin.ibm.com>,
0025        Thomas Gleixner <tglx@linutronix.de>
0026 
0027 简介
0028 ====
0029 
0030 现代系统架构的演进已经在处理器中引入了先进的错误报告和纠正能力。有一些OEM也支
0031 持可热拔插的NUMA(Non Uniform Memory Access,非统一内存访问)硬件,其中物理
0032 节点的插入和移除需要支持CPU热插拔。
0033 
0034 这样的进步要求内核可用的CPU被移除,要么是出于配置的原因,要么是出于RAS的目的,
0035 以保持一个不需要的CPU不在系统执行路径。因此需要在Linux内核中支持CPU热拔插。
0036 
0037 CPU热拔插支持的一个更新颖的用途是它在SMP的暂停恢复支持中的应用。双核和超线程支
0038 持使得即使是笔记本电脑也能运行不支持这些方法的SMP内核。
0039 
0040 
0041 命令行开关
0042 ==========
0043 
0044 ``maxcpus=n``
0045   限制启动时的CPU为 *n* 个。例如,如果你有四个CPU,使用 ``maxcpus=2`` 将只能启
0046   动两个。你可以选择稍后让其他CPU上线。
0047 
0048 ``nr_cpus=n``
0049   限制内核将支持的CPU总量。如果这里提供的数量低于实际可用的CPU数量,那么其他CPU
0050   以后就不能上线了。
0051 
0052 ``additional_cpus=n``
0053   使用它来限制可热插拔的CPU。该选项设置
0054   ``cpu_possible_mask = cpu_present_mask + additional_cpus``
0055 
0056   这个选项只限于IA64架构。
0057 
0058 ``possible_cpus=n``
0059   这个选项设置 ``cpu_possible_mask`` 中的 ``possible_cpus`` 位。
0060 
0061   这个选项只限于X86和S390架构。
0062 
0063 ``cpu0_hotplug``
0064   允许关闭CPU0。
0065 
0066   这个选项只限于X86架构。
0067 
0068 CPU位图
0069 =======
0070 
0071 ``cpu_possible_mask``
0072   系统中可能可用CPU的位图。这是用来为per_cpu变量分配一些启动时的内存,这些变量
0073   不会随着CPU的可用或移除而增加/减少。一旦在启动时的发现阶段被设置,该映射就是静态
0074   的,也就是说,任何时候都不会增加或删除任何位。根据你的系统需求提前准确地调整它
0075   可以节省一些启动时的内存。
0076 
0077 ``cpu_online_mask``
0078   当前在线的所有CPU的位图。在一个CPU可用于内核调度并准备接收设备的中断后,它被
0079   设置在 ``__cpu_up()`` 中。当使用 ``__cpu_disable()`` 关闭一个CPU时,它被清
0080   空,在此之前,所有的操作系统服务包括中断都被迁移到另一个目标CPU。
0081 
0082 ``cpu_present_mask``
0083   系统中当前存在的CPU的位图。它们并非全部在线。当物理热拔插被相关的子系统
0084   (如ACPI)处理时,可以改变和添加新的位或从位图中删除,这取决于事件是
0085   hot-add/hot-remove。目前还没有定死规定。典型的用法是在启动时启动拓扑结构,这时
0086   热插拔被禁用。
0087 
0088 你真的不需要操作任何系统的CPU映射。在大多数情况下,它们应该是只读的。当设置每个
0089 CPU资源时,几乎总是使用 ``cpu_possible_mask`` 或 ``for_each_possible_cpu()``
0090 来进行迭代。宏 ``for_each_cpu()`` 可以用来迭代一个自定义的CPU掩码。
0091 
0092 不要使用 ``cpumask_t`` 以外的任何东西来表示CPU的位图。
0093 
0094 
0095 使用CPU热拔插
0096 =============
0097 
0098 内核选项 *CONFIG_HOTPLUG_CPU* 需要被启用。它目前可用于多种架构,包括ARM、MIPS、
0099 PowerPC和X86。配置是通过sysfs接口完成的::
0100 
0101  $ ls -lh /sys/devices/system/cpu
0102  total 0
0103  drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu0
0104  drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu1
0105  drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu2
0106  drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu3
0107  drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu4
0108  drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu5
0109  drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu6
0110  drwxr-xr-x  9 root root    0 Dec 21 16:33 cpu7
0111  drwxr-xr-x  2 root root    0 Dec 21 16:33 hotplug
0112  -r--r--r--  1 root root 4.0K Dec 21 16:33 offline
0113  -r--r--r--  1 root root 4.0K Dec 21 16:33 online
0114  -r--r--r--  1 root root 4.0K Dec 21 16:33 possible
0115  -r--r--r--  1 root root 4.0K Dec 21 16:33 present
0116 
0117 文件 *offline* 、 *online* 、*possible* 、*present* 代表CPU掩码。每个CPU文件
0118 夹包含一个 *online* 文件,控制逻辑上的开(1)和关(0)状态。要在逻辑上关闭CPU4::
0119 
0120  $ echo 0 > /sys/devices/system/cpu/cpu4/online
0121   smpboot: CPU 4 is now offline
0122 
0123 一旦CPU被关闭,它将从 */proc/interrupts* 、*/proc/cpuinfo* 中被删除,也不应该
0124 被 *top* 命令显示出来。要让CPU4重新上线::
0125 
0126  $ echo 1 > /sys/devices/system/cpu/cpu4/online
0127  smpboot: Booting Node 0 Processor 4 APIC 0x1
0128 
0129 CPU又可以使用了。这应该对所有的CPU都有效。CPU0通常比较特殊,被排除在CPU热拔插之外。
0130 在X86上,内核选项 *CONFIG_BOOTPARAM_HOTPLUG_CPU0* 必须被启用,以便能够关闭CPU0。
0131 或者,可以使用内核命令选项 *cpu0_hotplug* 。CPU0的一些已知的依赖性:
0132 
0133 * 从休眠/暂停中恢复。如果CPU0处于离线状态,休眠/暂停将失败。
0134 * PIC中断。如果检测到PIC中断,CPU0就不能被移除。
0135 
0136 如果你发现CPU0上有任何依赖性,请告知Fenghua Yu <fenghua.yu@intel.com>0137 
0138 CPU的热拔插协作
0139 ===============
0140 
0141 下线情况
0142 --------
0143 
0144 一旦CPU被逻辑关闭,注册的热插拔状态的清除回调将被调用,从 ``CPUHP_ONLINE`` 开始,到
0145 ``CPUHP_OFFLINE`` 状态结束。这包括:
0146 
0147 * 如果任务因暂停操作而被冻结,那么 *cpuhp_tasks_frozen* 将被设置为true。
0148 
0149 * 所有进程都会从这个将要离线的CPU迁移到新的CPU上。新的CPU是从每个进程的当前cpuset中
0150   选择的,它可能是所有在线CPU的一个子集。
0151 
0152 * 所有针对这个CPU的中断都被迁移到新的CPU上。
0153 
0154 * 计时器也会被迁移到新的CPU上。
0155 
0156 * 一旦所有的服务被迁移,内核会调用一个特定的例程 ``__cpu_disable()`` 来进行特定的清
0157   理。
0158 
0159 CPU热插拔API
0160 ============
0161 
0162 CPU热拔插状态机
0163 ---------------
0164 
0165 CPU热插拔使用一个从CPUHP_OFFLINE到CPUHP_ONLINE的线性状态空间的普通状态机。每个状态都
0166 有一个startup和teardown的回调。
0167 
0168 当一个CPU上线时,将按顺序调用startup回调,直到达到CPUHP_ONLINE状态。当设置状态的回调
0169 或将实例添加到多实例状态时,也可以调用它们。
0170 
0171 当一个CPU下线时,将按相反的顺序依次调用teardown回调,直到达到CPUHP_OFFLINE状态。当删
0172 除状态的回调或从多实例状态中删除实例时,也可以调用它们。
0173 
0174 如果某个使用场景只需要一个方向的热插拔操作回调(CPU上线或CPU下线),则在设置状态时,
0175 可以将另一个不需要的回调设置为NULL。
0176 
0177 状态空间被划分成三个阶段:
0178 
0179 * PREPARE阶段
0180 
0181   PREPARE阶段涵盖了从CPUHP_OFFLINE到CPUHP_BRINGUP_CPU之间的状态空间。
0182 
0183   在该阶段中,startup回调在CPU上线操作启动CPU之前被调用,teardown回调在CPU下线操作使
0184   CPU功能失效之后被调用。
0185 
0186   这些回调是在控制CPU上调用的,因为它们显然不能在热插拔的CPU上运行,此时热插拔的CPU要
0187   么还没有启动,要么已经功能失效。
0188 
0189   startup回调用于设置CPU成功上线所需要的资源。teardown回调用于释放资源或在热插拔的CPU
0190   功能失效后,将待处理的工作转移到在线的CPU上。
0191 
0192   允许startup回调失败。如果回调失败,CPU上线操作被中止,CPU将再次被降到之前的状态(通
0193   常是CPUHP_OFFLINE)。
0194 
0195   本阶段中的teardown回调不允许失败。
0196 
0197 * STARTING阶段
0198 
0199   STARTING阶段涵盖了CPUHP_BRINGUP_CPU + 1到CPUHP_AP_ONLINE之间的状态空间。
0200 
0201   该阶段中的startup回调是在早期CPU设置代码中的CPU上线操作期间,禁用中断的情况下在热拔
0202   插的CPU上被调用。teardown回调是在CPU完全关闭前不久的CPU下线操作期间,禁用中断的情况
0203   下在热拔插的CPU上被调用。
0204 
0205   该阶段中的回调不允许失败。
0206 
0207   回调用于低级别的硬件初始化/关机和核心子系统。
0208 
0209 * ONLINE阶段
0210 
0211   ONLINE阶段涵盖了CPUHP_AP_ONLINE + 1到CPUHP_ONLINE之间的状态空间。
0212 
0213   该阶段中的startup回调是在CPU上线时在热插拔的CPU上调用的。teardown回调是在CPU下线操
0214   作时在热插拔CPU上调用的。
0215 
0216   回调是在每个CPU热插拔线程的上下文中调用的,该线程绑定在热插拔的CPU上。回调是在启用
0217   中断和抢占的情况下调用的。
0218 
0219   允许回调失败。如果回调失败,CPU热插拔操作被中止,CPU将恢复到之前的状态。
0220 
0221 CPU 上线/下线操作
0222 -----------------
0223 
0224 一个成功的上线操作如下::
0225 
0226   [CPUHP_OFFLINE]
0227   [CPUHP_OFFLINE + 1]->startup()       -> 成功
0228   [CPUHP_OFFLINE + 2]->startup()       -> 成功
0229   [CPUHP_OFFLINE + 3]                  -> 略过,因为startup == NULL
0230   ...
0231   [CPUHP_BRINGUP_CPU]->startup()       -> 成功
0232   === PREPARE阶段结束
0233   [CPUHP_BRINGUP_CPU + 1]->startup()   -> 成功
0234   ...
0235   [CPUHP_AP_ONLINE]->startup()         -> 成功
0236   === STARTUP阶段结束
0237   [CPUHP_AP_ONLINE + 1]->startup()     -> 成功
0238   ...
0239   [CPUHP_ONLINE - 1]->startup()        -> 成功
0240   [CPUHP_ONLINE]
0241 
0242 一个成功的下线操作如下::
0243 
0244   [CPUHP_ONLINE]
0245   [CPUHP_ONLINE - 1]->teardown()       -> 成功
0246   ...
0247   [CPUHP_AP_ONLINE + 1]->teardown()    -> 成功
0248   === STARTUP阶段开始
0249   [CPUHP_AP_ONLINE]->teardown()        -> 成功
0250   ...
0251   [CPUHP_BRINGUP_ONLINE - 1]->teardown()
0252   ...
0253   === PREPARE阶段开始
0254   [CPUHP_BRINGUP_CPU]->teardown()
0255   [CPUHP_OFFLINE + 3]->teardown()
0256   [CPUHP_OFFLINE + 2]                  -> 略过,因为teardown == NULL
0257   [CPUHP_OFFLINE + 1]->teardown()
0258   [CPUHP_OFFLINE]
0259 
0260 一个失败的上线操作如下::
0261 
0262   [CPUHP_OFFLINE]
0263   [CPUHP_OFFLINE + 1]->startup()       -> 成功
0264   [CPUHP_OFFLINE + 2]->startup()       -> 成功
0265   [CPUHP_OFFLINE + 3]                  -> 略过,因为startup == NULL
0266   ...
0267   [CPUHP_BRINGUP_CPU]->startup()       -> 成功
0268   === PREPARE阶段结束
0269   [CPUHP_BRINGUP_CPU + 1]->startup()   -> 成功
0270   ...
0271   [CPUHP_AP_ONLINE]->startup()         -> 成功
0272   === STARTUP阶段结束
0273   [CPUHP_AP_ONLINE + 1]->startup()     -> 成功
0274   ---
0275   [CPUHP_AP_ONLINE + N]->startup()     -> 失败
0276   [CPUHP_AP_ONLINE + (N - 1)]->teardown()
0277   ...
0278   [CPUHP_AP_ONLINE + 1]->teardown()
0279   === STARTUP阶段开始
0280   [CPUHP_AP_ONLINE]->teardown()
0281   ...
0282   [CPUHP_BRINGUP_ONLINE - 1]->teardown()
0283   ...
0284   === PREPARE阶段开始
0285   [CPUHP_BRINGUP_CPU]->teardown()
0286   [CPUHP_OFFLINE + 3]->teardown()
0287   [CPUHP_OFFLINE + 2]                  -> 略过,因为teardown == NULL
0288   [CPUHP_OFFLINE + 1]->teardown()
0289   [CPUHP_OFFLINE]
0290 
0291 一个失败的下线操作如下::
0292 
0293   [CPUHP_ONLINE]
0294   [CPUHP_ONLINE - 1]->teardown()       -> 成功
0295   ...
0296   [CPUHP_ONLINE - N]->teardown()       -> 失败
0297   [CPUHP_ONLINE - (N - 1)]->startup()
0298   ...
0299   [CPUHP_ONLINE - 1]->startup()
0300   [CPUHP_ONLINE]
0301 
0302 递归失败不能被合理地处理。
0303 请看下面的例子,由于下线操作失败而导致的递归失败::
0304 
0305   [CPUHP_ONLINE]
0306   [CPUHP_ONLINE - 1]->teardown()       -> 成功
0307   ...
0308   [CPUHP_ONLINE - N]->teardown()       -> 失败
0309   [CPUHP_ONLINE - (N - 1)]->startup()  -> 成功
0310   [CPUHP_ONLINE - (N - 2)]->startup()  -> 失败
0311 
0312 CPU热插拔状态机在此停止,且不再尝试回滚,因为这可能会导致死循环::
0313 
0314   [CPUHP_ONLINE - (N - 1)]->teardown() -> 成功
0315   [CPUHP_ONLINE - N]->teardown()       -> 失败
0316   [CPUHP_ONLINE - (N - 1)]->startup()  -> 成功
0317   [CPUHP_ONLINE - (N - 2)]->startup()  -> 失败
0318   [CPUHP_ONLINE - (N - 1)]->teardown() -> 成功
0319   [CPUHP_ONLINE - N]->teardown()       -> 失败
0320 
0321 周而复始,不断重复。在这种情况下,CPU留在该状态中::
0322 
0323   [CPUHP_ONLINE - (N - 1)]
0324 
0325 这至少可以让系统取得进展,让用户有机会进行调试,甚至解决这个问题。
0326 
0327 分配一个状态
0328 ------------
0329 
0330 有两种方式分配一个CPU热插拔状态:
0331 
0332 * 静态分配
0333 
0334   当子系统或驱动程序有相对于其他CPU热插拔状态的排序要求时,必须使用静态分配。例如,
0335   在CPU上线操作期间,PERF核心startup回调必须在PERF驱动startup回调之前被调用。在CPU
0336   下线操作中,驱动teardown回调必须在核心teardown回调之前调用。静态分配的状态由
0337   cpuhp_state枚举中的常量描述,可以在include/linux/cpuhotplug.h中找到。
0338 
0339   在适当的位置将状态插入枚举中,这样就满足了排序要求。状态常量必须被用于状态的设置
0340   和移除。
0341 
0342   当状态回调不是在运行时设置的,并且是kernel/cpu.c中CPU热插拔状态数组初始化的一部分
0343   时,也需要静态分配。
0344 
0345 * 动态分配
0346 
0347   当对状态回调没有排序要求时,动态分配是首选方法。状态编号由setup函数分配,并在成功
0348   后返回给调用者。
0349 
0350   只有PREPARE和ONLINE阶段提供了一个动态分配范围。STARTING阶段则没有,因为该部分的大多
0351   数回调都有明确的排序要求。
0352 
0353 CPU热插拔状态的设置
0354 -------------------
0355 
0356 核心代码提供了以下函数用来设置状态:
0357 
0358 * cpuhp_setup_state(state, name, startup, teardown)
0359 * cpuhp_setup_state_nocalls(state, name, startup, teardown)
0360 * cpuhp_setup_state_cpuslocked(state, name, startup, teardown)
0361 * cpuhp_setup_state_nocalls_cpuslocked(state, name, startup, teardown)
0362 
0363 对于一个驱动程序或子系统有多个实例,并且每个实例都需要调用相同的CPU hotplug状态回
0364 调的情况,CPU hotplug核心提供多实例支持。与驱动程序特定的实例列表相比,其优势在于
0365 与实例相关的函数完全针对CPU hotplug操作进行序列化,并在添加和删除时提供状态回调的
0366 自动调用。要设置这样一个多实例状态,可以使用以下函数:
0367 
0368 * cpuhp_setup_state_multi(state, name, startup, teardown)
0369 
0370 @state参数要么是静态分配的状态,要么是动态分配状态(PUHP_PREPARE_DYN,CPUHP_ONLINE_DYN)
0371 的常量之一, 具体取决于应该分配动态状态的状态阶段(PREPARE,ONLINE)。
0372 
0373 @name参数用于sysfs输出和检测。命名惯例是"subsys:mode"或"subsys/driver:mode",
0374 例如 "perf:mode"或"perf/x86:mode"。常见的mode名称有:
0375 
0376 ======== ============================================
0377 prepare  对应PREPARE阶段中的状态
0378 
0379 dead     对应PREPARE阶段中不提供startup回调的状态
0380 
0381 starting 对应STARTING阶段中的状态
0382 
0383 dying    对应STARTING阶段中不提供startup回调的状态
0384 
0385 online   对应ONLINE阶段中的状态
0386 
0387 offline  对应ONLINE阶段中不提供startup回调的状态
0388 ======== ============================================
0389 
0390 由于@name参数只用于sysfs和检测,如果其他mode描述符比常见的描述符更好地描述状态的性质,
0391 也可以使用。
0392 
0393 @name参数的示例:"perf/online", "perf/x86:prepare", "RCU/tree:dying", "sched/waitempty"
0394 
0395 @startup参数是一个指向回调的函数指针,在CPU上线操作时被调用。若应用不需要startup
0396 回调,则将该指针设为NULL。
0397 
0398 @teardown参数是一个指向回调的函数指针,在CPU下线操作时调用。若应用不需要teardown
0399 回调,则将该指针设为NULL。
0400 
0401 这些函数在处理已注册回调的方式上有所不同:
0402 
0403   * cpuhp_setup_state_nocalls(), cpuhp_setup_state_nocalls_cpuslocked()和
0404     cpuhp_setup_state_multi()只注册回调。
0405 
0406   * cpuhp_setup_state()和cpuhp_setup_state_cpuslocked()注册回调,并对当前状态大于新
0407     安装状态的所有在线CPU调用@startup回调(如果不是NULL)。根据状态阶段,回调要么在
0408     当前的CPU上调用(PREPARE阶段),要么在CPU的热插拔线程中调用每个在线CPU(ONLINE阶段)。
0409 
0410     如果CPU N的回调失败,那么CPU 0...N-1的teardown回调被调用以回滚操作。状态设置失败,
0411     状态的回调没有被注册,在动态分配的情况下,分配的状态被释放。
0412 
0413 状态设置和回调调用是针对CPU热拔插操作进行序列化的。如果设置函数必须从CPU热插拔的读
0414 锁定区域调用,那么必须使用_cpuslocked()变体。这些函数不能在CPU热拔插回调中使用。
0415 
0416 函数返回值:
0417   ======== ==========================================================
0418   0        静态分配的状态设置成功
0419 
0420   >0       动态分配的状态设置成功
0421 
0422            返回的数值是被分配的状态编号。如果状态回调后来必须被移除,
0423            例如模块移除,那么这个数值必须由调用者保存,并作为状态移
0424            除函数的@state参数。对于多实例状态,动态分配的状态编号也
0425            需要作为实例添加/删除操作的@state参数。
0426 
0427   <0       操作失败
0428   ======== ==========================================================
0429 
0430 移除CPU热拔插状态
0431 -----------------
0432 
0433 为了移除一个之前设置好的状态,提供了如下函数:
0434 
0435 * cpuhp_remove_state(state)
0436 * cpuhp_remove_state_nocalls(state)
0437 * cpuhp_remove_state_nocalls_cpuslocked(state)
0438 * cpuhp_remove_multi_state(state)
0439 
0440 @state参数要么是静态分配的状态,要么是由cpuhp_setup_state*()在动态范围内分配
0441 的状态编号。如果状态在动态范围内,则状态编号被释放,可再次进行动态分配。
0442 
0443 这些函数在处理已注册回调的方式上有所不同:
0444 
0445   * cpuhp_remove_state_nocalls(), cpuhp_remove_state_nocalls_cpuslocked()
0446     和 cpuhp_remove_multi_state()只删除回调。
0447 
0448   * cpuhp_remove_state()删除回调,并调用所有当前状态大于被删除状态的在线CPU的
0449     teardown回调(如果不是NULL)。根据状态阶段,回调要么在当前的CPU上调用
0450     (PREPARE阶段),要么在CPU的热插拔线程中调用每个在线CPU(ONLINE阶段)。
0451 
0452     为了完成移除工作,teardown回调不能失败。
0453 
0454 状态移除和回调调用是针对CPU热拔插操作进行序列化的。如果移除函数必须从CPU hotplug
0455 读取锁定区域调用,那么必须使用_cpuslocked()变体。这些函数不能从CPU热插拔的回调中使用。
0456 
0457 如果一个多实例的状态被移除,那么调用者必须先移除所有的实例。
0458 
0459 多实例状态实例管理
0460 ------------------
0461 
0462 一旦多实例状态被建立,实例就可以被添加到状态中:
0463 
0464   * cpuhp_state_add_instance(state, node)
0465   * cpuhp_state_add_instance_nocalls(state, node)
0466 
0467 @state参数是一个静态分配的状态或由cpuhp_setup_state_multi()在动态范围内分配的状
0468 态编号。
0469 
0470 @node参数是一个指向hlist_node的指针,它被嵌入到实例的数据结构中。这个指针被交给
0471 多实例状态的回调,可以被回调用来通过container_of()检索到实例。
0472 
0473 这些函数在处理已注册回调的方式上有所不同:
0474 
0475   * cpuhp_state_add_instance_nocalls()只将实例添加到多实例状态的节点列表中。
0476 
0477   * cpuhp_state_add_instance()为所有当前状态大于@state的在线CPU添加实例并调用与
0478     @state相关的startup回调(如果不是NULL)。该回调只对将要添加的实例进行调用。
0479     根据状态阶段,回调要么在当前的CPU上调用(PREPARE阶段),要么在CPU的热插拔线
0480     程中调用每个在线CPU(ONLINE阶段)。
0481 
0482     如果CPU N的回调失败,那么CPU 0 ... N-1的teardown回调被调用以回滚操作,该函数
0483     失败,实例不会被添加到多实例状态的节点列表中。
0484 
0485 要从状态的节点列表中删除一个实例,可以使用这些函数:
0486 
0487   * cpuhp_state_remove_instance(state, node)
0488   * cpuhp_state_remove_instance_nocalls(state, node)
0489 
0490 参数与上述cpuhp_state_add_instance*()变体相同。
0491 
0492 这些函数在处理已注册回调的方式上有所不同:
0493 
0494   * cpuhp_state_remove_instance_nocalls()只从状态的节点列表中删除实例。
0495 
0496   * cpuhp_state_remove_instance()删除实例并调用与@state相关的回调(如果不是NULL),
0497     用于所有当前状态大于@state的在线CPU。 该回调只对将要被移除的实例进行调用。
0498     根据状态阶段,回调要么在当前的CPU上调用(PREPARE阶段),要么在CPU的热插拔
0499     线程中调用每个在线CPU(ONLINE阶段)。
0500 
0501     为了完成移除工作,teardown回调不能失败。
0502 
0503 节点列表的添加/删除操作和回调调用是针对CPU热拔插操作进行序列化。这些函数不能在
0504 CPU hotplug回调和CPU hotplug读取锁定区域内使用。
0505 
0506 样例
0507 ----
0508 
0509 在STARTING阶段设置和取消静态分配的状态,以获取上线和下线操作的通知::
0510 
0511    ret = cpuhp_setup_state(CPUHP_SUBSYS_STARTING, "subsys:starting", subsys_cpu_starting, subsys_cpu_dying);
0512    if (ret < 0)
0513         return ret;
0514    ....
0515    cpuhp_remove_state(CPUHP_SUBSYS_STARTING);
0516 
0517 在ONLINE阶段设置和取消动态分配的状态,以获取下线操作的通知::
0518 
0519    state = cpuhp_setup_state(CPUHP_ONLINE_DYN, "subsys:offline", NULL, subsys_cpu_offline);
0520    if (state < 0)
0521        return state;
0522    ....
0523    cpuhp_remove_state(state);
0524 
0525 在ONLINE阶段设置和取消动态分配的状态,以获取有关上线操作的通知,而无需调用回调::
0526 
0527    state = cpuhp_setup_state_nocalls(CPUHP_ONLINE_DYN, "subsys:online", subsys_cpu_online, NULL);
0528    if (state < 0)
0529        return state;
0530    ....
0531    cpuhp_remove_state_nocalls(state);
0532 
0533 在ONLINE阶段设置、使用和取消动态分配的多实例状态,以获得上线和下线操作的通知::
0534 
0535    state = cpuhp_setup_state_multi(CPUHP_ONLINE_DYN, "subsys:online", subsys_cpu_online, subsys_cpu_offline);
0536    if (state < 0)
0537        return state;
0538    ....
0539    ret = cpuhp_state_add_instance(state, &inst1->node);
0540    if (ret)
0541         return ret;
0542    ....
0543    ret = cpuhp_state_add_instance(state, &inst2->node);
0544    if (ret)
0545         return ret;
0546    ....
0547    cpuhp_remove_instance(state, &inst1->node);
0548    ....
0549    cpuhp_remove_instance(state, &inst2->node);
0550    ....
0551    remove_multi_state(state);
0552 
0553 测试热拔插状态
0554 ==============
0555 
0556 验证自定义状态是否按预期工作的一个方法是关闭一个CPU,然后再把它上线。也可以把CPU放到某
0557 些状态(例如 ``CPUHP_AP_ONLINE`` ),然后再回到 ``CPUHP_ONLINE`` 。这将模拟在
0558 ``CPUHP_AP_ONLINE`` 之后的一个状态出现错误,从而导致回滚到在线状态。
0559 
0560 所有注册的状态都被列举在 ``/sys/devices/system/cpu/hotplug/states`` ::
0561 
0562  $ tail /sys/devices/system/cpu/hotplug/states
0563  138: mm/vmscan:online
0564  139: mm/vmstat:online
0565  140: lib/percpu_cnt:online
0566  141: acpi/cpu-drv:online
0567  142: base/cacheinfo:online
0568  143: virtio/net:online
0569  144: x86/mce:online
0570  145: printk:online
0571  168: sched:active
0572  169: online
0573 
0574 要将CPU4回滚到 ``lib/percpu_cnt:online`` ,再回到在线状态,只需发出::
0575 
0576   $ cat /sys/devices/system/cpu/cpu4/hotplug/state
0577   169
0578   $ echo 140 > /sys/devices/system/cpu/cpu4/hotplug/target
0579   $ cat /sys/devices/system/cpu/cpu4/hotplug/state
0580   140
0581 
0582 需要注意的是,状态140的清除回调已经被调用。现在重新上线::
0583 
0584   $ echo 169 > /sys/devices/system/cpu/cpu4/hotplug/target
0585   $ cat /sys/devices/system/cpu/cpu4/hotplug/state
0586   169
0587 
0588 启用追踪事件后,单个步骤也是可见的::
0589 
0590   #  TASK-PID   CPU#    TIMESTAMP  FUNCTION
0591   #     | |       |        |         |
0592       bash-394  [001]  22.976: cpuhp_enter: cpu: 0004 target: 140 step: 169 (cpuhp_kick_ap_work)
0593    cpuhp/4-31   [004]  22.977: cpuhp_enter: cpu: 0004 target: 140 step: 168 (sched_cpu_deactivate)
0594    cpuhp/4-31   [004]  22.990: cpuhp_exit:  cpu: 0004  state: 168 step: 168 ret: 0
0595    cpuhp/4-31   [004]  22.991: cpuhp_enter: cpu: 0004 target: 140 step: 144 (mce_cpu_pre_down)
0596    cpuhp/4-31   [004]  22.992: cpuhp_exit:  cpu: 0004  state: 144 step: 144 ret: 0
0597    cpuhp/4-31   [004]  22.993: cpuhp_multi_enter: cpu: 0004 target: 140 step: 143 (virtnet_cpu_down_prep)
0598    cpuhp/4-31   [004]  22.994: cpuhp_exit:  cpu: 0004  state: 143 step: 143 ret: 0
0599    cpuhp/4-31   [004]  22.995: cpuhp_enter: cpu: 0004 target: 140 step: 142 (cacheinfo_cpu_pre_down)
0600    cpuhp/4-31   [004]  22.996: cpuhp_exit:  cpu: 0004  state: 142 step: 142 ret: 0
0601       bash-394  [001]  22.997: cpuhp_exit:  cpu: 0004  state: 140 step: 169 ret: 0
0602       bash-394  [005]  95.540: cpuhp_enter: cpu: 0004 target: 169 step: 140 (cpuhp_kick_ap_work)
0603    cpuhp/4-31   [004]  95.541: cpuhp_enter: cpu: 0004 target: 169 step: 141 (acpi_soft_cpu_online)
0604    cpuhp/4-31   [004]  95.542: cpuhp_exit:  cpu: 0004  state: 141 step: 141 ret: 0
0605    cpuhp/4-31   [004]  95.543: cpuhp_enter: cpu: 0004 target: 169 step: 142 (cacheinfo_cpu_online)
0606    cpuhp/4-31   [004]  95.544: cpuhp_exit:  cpu: 0004  state: 142 step: 142 ret: 0
0607    cpuhp/4-31   [004]  95.545: cpuhp_multi_enter: cpu: 0004 target: 169 step: 143 (virtnet_cpu_online)
0608    cpuhp/4-31   [004]  95.546: cpuhp_exit:  cpu: 0004  state: 143 step: 143 ret: 0
0609    cpuhp/4-31   [004]  95.547: cpuhp_enter: cpu: 0004 target: 169 step: 144 (mce_cpu_online)
0610    cpuhp/4-31   [004]  95.548: cpuhp_exit:  cpu: 0004  state: 144 step: 144 ret: 0
0611    cpuhp/4-31   [004]  95.549: cpuhp_enter: cpu: 0004 target: 169 step: 145 (console_cpu_notify)
0612    cpuhp/4-31   [004]  95.550: cpuhp_exit:  cpu: 0004  state: 145 step: 145 ret: 0
0613    cpuhp/4-31   [004]  95.551: cpuhp_enter: cpu: 0004 target: 169 step: 168 (sched_cpu_activate)
0614    cpuhp/4-31   [004]  95.552: cpuhp_exit:  cpu: 0004  state: 168 step: 168 ret: 0
0615       bash-394  [005]  95.553: cpuhp_exit:  cpu: 0004  state: 169 step: 140 ret: 0
0616 
0617 可以看到,CPU4一直下降到时间戳22.996,然后又上升到95.552。所有被调用的回调,
0618 包括它们的返回代码都可以在跟踪中看到。
0619 
0620 架构的要求
0621 ==========
0622 
0623 需要具备以下功能和配置:
0624 
0625 ``CONFIG_HOTPLUG_CPU``
0626   这个配置项需要在Kconfig中启用
0627 
0628 ``__cpu_up()``
0629   调出一个cpu的架构接口
0630 
0631 ``__cpu_disable()``
0632   关闭CPU的架构接口,在此程序返回后,内核不能再处理任何中断。这包括定时器的关闭。
0633 
0634 ``__cpu_die()``
0635   这实际上是为了确保CPU的死亡。实际上,看看其他架构中实现CPU热拔插的一些示例代
0636   码。对于那个特定的架构,处理器被从 ``idle()`` 循环中拿下来。 ``__cpu_die()``
0637   通常会等待一些per_cpu状态的设置,以确保处理器的死亡例程被调用来保持活跃。
0638 
0639 用户空间通知
0640 ============
0641 
0642 在CPU成功上线或下线后,udev事件被发送。一个udev规则,比如::
0643 
0644   SUBSYSTEM=="cpu", DRIVERS=="processor", DEVPATH=="/devices/system/cpu/*", RUN+="the_hotplug_receiver.sh"
0645 
0646 将接收所有事件。一个像这样的脚本::
0647 
0648   #!/bin/sh
0649 
0650   if [ "${ACTION}" = "offline" ]
0651   then
0652       echo "CPU ${DEVPATH##*/} offline"
0653 
0654   elif [ "${ACTION}" = "online" ]
0655   then
0656       echo "CPU ${DEVPATH##*/} online"
0657 
0658   fi
0659 
0660 可以进一步处理该事件。
0661 
0662 内核内联文档参考
0663 ================
0664 
0665 该API在以下内核代码中:
0666 
0667 include/linux/cpuhotplug.h