1 .. include:: ../disclaimer-zh_CN.rst 2 3 :Original: Documentation/core-api/cpu_hotplug.rst 4 :翻译: 5 6 司延腾 Yanteng Si <siyanteng@loongson.cn> 7 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn> 8 9 :校译: 10 11 吴想成 Wu XiangCheng <bobwxc@email.cn> 12 13 .. _cn_core_api_cpu_hotplug: 14 15 ================= 16 内核中的CPU热拔插 17 ================= 18 19 :时间: 2021年9月 20 :作者: Sebastian Andrzej Siewior <bigeasy@linutronix.de>, 21 Rusty Russell <rusty@rustcorp.com.au>, 22 Srivatsa Vaddagiri <vatsa@in.ibm.com>, 23 Ashok Raj <ashok.raj@intel.com>, 24 Joel Schopp <jschopp@austin.ibm.com>, 25 Thomas Gleixner <tglx@linutronix.de> 26 27 简介 28 ==== 29 30 现代系统架构的演进已经在处理器中引入了先进的错误报告和纠正能力。有一些OEM也支 31 持可热拔插的NUMA(Non Uniform Memory Access,非统一内存访问)硬件,其中物理 32 节点的插入和移除需要支持CPU热插拔。 33 34 这样的进步要求内核可用的CPU被移除,要么是出于配置的原因,要么是出于RAS的目的, 35 以保持一个不需要的CPU不在系统执行路径。因此需要在Linux内核中支持CPU热拔插。 36 37 CPU热拔插支持的一个更新颖的用途是它在SMP的暂停恢复支持中的应用。双核和超线程支 38 持使得即使是笔记本电脑也能运行不支持这些方法的SMP内核。 39 40 41 命令行开关 42 ========== 43 44 ``maxcpus=n`` 45 限制启动时的CPU为 *n* 个。例如,如果你有四个CPU,使用 ``maxcpus=2`` 将只能启 46 动两个。你可以选择稍后让其他CPU上线。 47 48 ``nr_cpus=n`` 49 限制内核将支持的CPU总量。如果这里提供的数量低于实际可用的CPU数量,那么其他CPU 50 以后就不能上线了。 51 52 ``possible_cpus=n`` 53 这个选项设置 ``cpu_possible_mask`` 中的 ``possible_cpus`` 位。 54 55 这个选项只限于X86和S390架构。 56 57 ``cpu0_hotplug`` 58 允许关闭CPU0。 59 60 这个选项只限于X86架构。 61 62 CPU位图 63 ======= 64 65 ``cpu_possible_mask`` 66 系统中可能可用CPU的位图。这是用来为per_cpu变量分配一些启动时的内存,这些变量 67 不会随着CPU的可用或移除而增加/减少。一旦在启动时的发现阶段被设置,该映射就是静态 68 的,也就是说,任何时候都不会增加或删除任何位。根据你的系统需求提前准确地调整它 69 可以节省一些启动时的内存。 70 71 ``cpu_online_mask`` 72 当前在线的所有CPU的位图。在一个CPU可用于内核调度并准备接收设备的中断后,它被 73 设置在 ``__cpu_up()`` 中。当使用 ``__cpu_disable()`` 关闭一个CPU时,它被清 74 空,在此之前,所有的操作系统服务包括中断都被迁移到另一个目标CPU。 75 76 ``cpu_present_mask`` 77 系统中当前存在的CPU的位图。它们并非全部在线。当物理热拔插被相关的子系统 78 (如ACPI)处理时,可以改变和添加新的位或从位图中删除,这取决于事件是 79 hot-add/hot-remove。目前还没有定死规定。典型的用法是在启动时启动拓扑结构,这时 80 热插拔被禁用。 81 82 你真的不需要操作任何系统的CPU映射。在大多数情况下,它们应该是只读的。当设置每个 83 CPU资源时,几乎总是使用 ``cpu_possible_mask`` 或 ``for_each_possible_cpu()`` 84 来进行迭代。宏 ``for_each_cpu()`` 可以用来迭代一个自定义的CPU掩码。 85 86 不要使用 ``cpumask_t`` 以外的任何东西来表示CPU的位图。 87 88 89 使用CPU热拔插 90 ============= 91 92 内核选项 *CONFIG_HOTPLUG_CPU* 需要被启用。它目前可用于多种架构,包括ARM、MIPS、 93 PowerPC和X86。配置是通过sysfs接口完成的:: 94 95 $ ls -lh /sys/devices/system/cpu 96 total 0 97 drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu0 98 drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu1 99 drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu2 100 drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu3 101 drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu4 102 drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu5 103 drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu6 104 drwxr-xr-x 9 root root 0 Dec 21 16:33 cpu7 105 drwxr-xr-x 2 root root 0 Dec 21 16:33 hotplug 106 -r--r--r-- 1 root root 4.0K Dec 21 16:33 offline 107 -r--r--r-- 1 root root 4.0K Dec 21 16:33 online 108 -r--r--r-- 1 root root 4.0K Dec 21 16:33 possible 109 -r--r--r-- 1 root root 4.0K Dec 21 16:33 present 110 111 文件 *offline* 、 *online* 、*possible* 、*present* 代表CPU掩码。每个CPU文件 112 夹包含一个 *online* 文件,控制逻辑上的开(1)和关(0)状态。要在逻辑上关闭CPU4:: 113 114 $ echo 0 > /sys/devices/system/cpu/cpu4/online 115 smpboot: CPU 4 is now offline 116 117 一旦CPU被关闭,它将从 */proc/interrupts* 、*/proc/cpuinfo* 中被删除,也不应该 118 被 *top* 命令显示出来。要让CPU4重新上线:: 119 120 $ echo 1 > /sys/devices/system/cpu/cpu4/online 121 smpboot: Booting Node 0 Processor 4 APIC 0x1 122 123 CPU又可以使用了。这应该对所有的CPU都有效。CPU0通常比较特殊,被排除在CPU热拔插之外。 124 在X86上,内核选项 *CONFIG_BOOTPARAM_HOTPLUG_CPU0* 必须被启用,以便能够关闭CPU0。 125 或者,可以使用内核命令选项 *cpu0_hotplug* 。CPU0的一些已知的依赖性: 126 127 * 从休眠/暂停中恢复。如果CPU0处于离线状态,休眠/暂停将失败。 128 * PIC中断。如果检测到PIC中断,CPU0就不能被移除。 129 130 如果你发现CPU0上有任何依赖性,请告知Fenghua Yu <fenghua.yu@intel.com>。 131 132 CPU的热拔插协作 133 =============== 134 135 下线情况 136 -------- 137 138 一旦CPU被逻辑关闭,注册的热插拔状态的清除回调将被调用,从 ``CPUHP_ONLINE`` 开始,到 139 ``CPUHP_OFFLINE`` 状态结束。这包括: 140 141 * 如果任务因暂停操作而被冻结,那么 *cpuhp_tasks_frozen* 将被设置为true。 142 143 * 所有进程都会从这个将要离线的CPU迁移到新的CPU上。新的CPU是从每个进程的当前cpuset中 144 选择的,它可能是所有在线CPU的一个子集。 145 146 * 所有针对这个CPU的中断都被迁移到新的CPU上。 147 148 * 计时器也会被迁移到新的CPU上。 149 150 * 一旦所有的服务被迁移,内核会调用一个特定的例程 ``__cpu_disable()`` 来进行特定的清 151 理。 152 153 CPU热插拔API 154 ============ 155 156 CPU热拔插状态机 157 --------------- 158 159 CPU热插拔使用一个从CPUHP_OFFLINE到CPUHP_ONLINE的线性状态空间的普通状态机。每个状态都 160 有一个startup和teardown的回调。 161 162 当一个CPU上线时,将按顺序调用startup回调,直到达到CPUHP_ONLINE状态。当设置状态的回调 163 或将实例添加到多实例状态时,也可以调用它们。 164 165 当一个CPU下线时,将按相反的顺序依次调用teardown回调,直到达到CPUHP_OFFLINE状态。当删 166 除状态的回调或从多实例状态中删除实例时,也可以调用它们。 167 168 如果某个使用场景只需要一个方向的热插拔操作回调(CPU上线或CPU下线),则在设置状态时, 169 可以将另一个不需要的回调设置为NULL。 170 171 状态空间被划分成三个阶段: 172 173 * PREPARE阶段 174 175 PREPARE阶段涵盖了从CPUHP_OFFLINE到CPUHP_BRINGUP_CPU之间的状态空间。 176 177 在该阶段中,startup回调在CPU上线操作启动CPU之前被调用,teardown回调在CPU下线操作使 178 CPU功能失效之后被调用。 179 180 这些回调是在控制CPU上调用的,因为它们显然不能在热插拔的CPU上运行,此时热插拔的CPU要 181 么还没有启动,要么已经功能失效。 182 183 startup回调用于设置CPU成功上线所需要的资源。teardown回调用于释放资源或在热插拔的CPU 184 功能失效后,将待处理的工作转移到在线的CPU上。 185 186 允许startup回调失败。如果回调失败,CPU上线操作被中止,CPU将再次被降到之前的状态(通 187 常是CPUHP_OFFLINE)。 188 189 本阶段中的teardown回调不允许失败。 190 191 * STARTING阶段 192 193 STARTING阶段涵盖了CPUHP_BRINGUP_CPU + 1到CPUHP_AP_ONLINE之间的状态空间。 194 195 该阶段中的startup回调是在早期CPU设置代码中的CPU上线操作期间,禁用中断的情况下在热拔 196 插的CPU上被调用。teardown回调是在CPU完全关闭前不久的CPU下线操作期间,禁用中断的情况 197 下在热拔插的CPU上被调用。 198 199 该阶段中的回调不允许失败。 200 201 回调用于低级别的硬件初始化/关机和核心子系统。 202 203 * ONLINE阶段 204 205 ONLINE阶段涵盖了CPUHP_AP_ONLINE + 1到CPUHP_ONLINE之间的状态空间。 206 207 该阶段中的startup回调是在CPU上线时在热插拔的CPU上调用的。teardown回调是在CPU下线操 208 作时在热插拔CPU上调用的。 209 210 回调是在每个CPU热插拔线程的上下文中调用的,该线程绑定在热插拔的CPU上。回调是在启用 211 中断和抢占的情况下调用的。 212 213 允许回调失败。如果回调失败,CPU热插拔操作被中止,CPU将恢复到之前的状态。 214 215 CPU 上线/下线操作 216 ----------------- 217 218 一个成功的上线操作如下:: 219 220 [CPUHP_OFFLINE] 221 [CPUHP_OFFLINE + 1]->startup() -> 成功 222 [CPUHP_OFFLINE + 2]->startup() -> 成功 223 [CPUHP_OFFLINE + 3] -> 略过,因为startup == NULL 224 ... 225 [CPUHP_BRINGUP_CPU]->startup() -> 成功 226 === PREPARE阶段结束 227 [CPUHP_BRINGUP_CPU + 1]->startup() -> 成功 228 ... 229 [CPUHP_AP_ONLINE]->startup() -> 成功 230 === STARTUP阶段结束 231 [CPUHP_AP_ONLINE + 1]->startup() -> 成功 232 ... 233 [CPUHP_ONLINE - 1]->startup() -> 成功 234 [CPUHP_ONLINE] 235 236 一个成功的下线操作如下:: 237 238 [CPUHP_ONLINE] 239 [CPUHP_ONLINE - 1]->teardown() -> 成功 240 ... 241 [CPUHP_AP_ONLINE + 1]->teardown() -> 成功 242 === STARTUP阶段开始 243 [CPUHP_AP_ONLINE]->teardown() -> 成功 244 ... 245 [CPUHP_BRINGUP_ONLINE - 1]->teardown() 246 ... 247 === PREPARE阶段开始 248 [CPUHP_BRINGUP_CPU]->teardown() 249 [CPUHP_OFFLINE + 3]->teardown() 250 [CPUHP_OFFLINE + 2] -> 略过,因为teardown == NULL 251 [CPUHP_OFFLINE + 1]->teardown() 252 [CPUHP_OFFLINE] 253 254 一个失败的上线操作如下:: 255 256 [CPUHP_OFFLINE] 257 [CPUHP_OFFLINE + 1]->startup() -> 成功 258 [CPUHP_OFFLINE + 2]->startup() -> 成功 259 [CPUHP_OFFLINE + 3] -> 略过,因为startup == NULL 260 ... 261 [CPUHP_BRINGUP_CPU]->startup() -> 成功 262 === PREPARE阶段结束 263 [CPUHP_BRINGUP_CPU + 1]->startup() -> 成功 264 ... 265 [CPUHP_AP_ONLINE]->startup() -> 成功 266 === STARTUP阶段结束 267 [CPUHP_AP_ONLINE + 1]->startup() -> 成功 268 --- 269 [CPUHP_AP_ONLINE + N]->startup() -> 失败 270 [CPUHP_AP_ONLINE + (N - 1)]->teardown() 271 ... 272 [CPUHP_AP_ONLINE + 1]->teardown() 273 === STARTUP阶段开始 274 [CPUHP_AP_ONLINE]->teardown() 275 ... 276 [CPUHP_BRINGUP_ONLINE - 1]->teardown() 277 ... 278 === PREPARE阶段开始 279 [CPUHP_BRINGUP_CPU]->teardown() 280 [CPUHP_OFFLINE + 3]->teardown() 281 [CPUHP_OFFLINE + 2] -> 略过,因为teardown == NULL 282 [CPUHP_OFFLINE + 1]->teardown() 283 [CPUHP_OFFLINE] 284 285 一个失败的下线操作如下:: 286 287 [CPUHP_ONLINE] 288 [CPUHP_ONLINE - 1]->teardown() -> 成功 289 ... 290 [CPUHP_ONLINE - N]->teardown() -> 失败 291 [CPUHP_ONLINE - (N - 1)]->startup() 292 ... 293 [CPUHP_ONLINE - 1]->startup() 294 [CPUHP_ONLINE] 295 296 递归失败不能被合理地处理。 297 请看下面的例子,由于下线操作失败而导致的递归失败:: 298 299 [CPUHP_ONLINE] 300 [CPUHP_ONLINE - 1]->teardown() -> 成功 301 ... 302 [CPUHP_ONLINE - N]->teardown() -> 失败 303 [CPUHP_ONLINE - (N - 1)]->startup() -> 成功 304 [CPUHP_ONLINE - (N - 2)]->startup() -> 失败 305 306 CPU热插拔状态机在此停止,且不再尝试回滚,因为这可能会导致死循环:: 307 308 [CPUHP_ONLINE - (N - 1)]->teardown() -> 成功 309 [CPUHP_ONLINE - N]->teardown() -> 失败 310 [CPUHP_ONLINE - (N - 1)]->startup() -> 成功 311 [CPUHP_ONLINE - (N - 2)]->startup() -> 失败 312 [CPUHP_ONLINE - (N - 1)]->teardown() -> 成功 313 [CPUHP_ONLINE - N]->teardown() -> 失败 314 315 周而复始,不断重复。在这种情况下,CPU留在该状态中:: 316 317 [CPUHP_ONLINE - (N - 1)] 318 319 这至少可以让系统取得进展,让用户有机会进行调试,甚至解决这个问题。 320 321 分配一个状态 322 ------------ 323 324 有两种方式分配一个CPU热插拔状态: 325 326 * 静态分配 327 328 当子系统或驱动程序有相对于其他CPU热插拔状态的排序要求时,必须使用静态分配。例如, 329 在CPU上线操作期间,PERF核心startup回调必须在PERF驱动startup回调之前被调用。在CPU 330 下线操作中,驱动teardown回调必须在核心teardown回调之前调用。静态分配的状态由 331 cpuhp_state枚举中的常量描述,可以在include/linux/cpuhotplug.h中找到。 332 333 在适当的位置将状态插入枚举中,这样就满足了排序要求。状态常量必须被用于状态的设置 334 和移除。 335 336 当状态回调不是在运行时设置的,并且是kernel/cpu.c中CPU热插拔状态数组初始化的一部分 337 时,也需要静态分配。 338 339 * 动态分配 340 341 当对状态回调没有排序要求时,动态分配是首选方法。状态编号由setup函数分配,并在成功 342 后返回给调用者。 343 344 只有PREPARE和ONLINE阶段提供了一个动态分配范围。STARTING阶段则没有,因为该部分的大多 345 数回调都有明确的排序要求。 346 347 CPU热插拔状态的设置 348 ------------------- 349 350 核心代码提供了以下函数用来设置状态: 351 352 * cpuhp_setup_state(state, name, startup, teardown) 353 * cpuhp_setup_state_nocalls(state, name, startup, teardown) 354 * cpuhp_setup_state_cpuslocked(state, name, startup, teardown) 355 * cpuhp_setup_state_nocalls_cpuslocked(state, name, startup, teardown) 356 357 对于一个驱动程序或子系统有多个实例,并且每个实例都需要调用相同的CPU hotplug状态回 358 调的情况,CPU hotplug核心提供多实例支持。与驱动程序特定的实例列表相比,其优势在于 359 与实例相关的函数完全针对CPU hotplug操作进行序列化,并在添加和删除时提供状态回调的 360 自动调用。要设置这样一个多实例状态,可以使用以下函数: 361 362 * cpuhp_setup_state_multi(state, name, startup, teardown) 363 364 @state参数要么是静态分配的状态,要么是动态分配状态(PUHP_PREPARE_DYN,CPUHP_ONLINE_DYN) 365 的常量之一, 具体取决于应该分配动态状态的状态阶段(PREPARE,ONLINE)。 366 367 @name参数用于sysfs输出和检测。命名惯例是"subsys:mode"或"subsys/driver:mode", 368 例如 "perf:mode"或"perf/x86:mode"。常见的mode名称有: 369 370 ======== ============================================ 371 prepare 对应PREPARE阶段中的状态 372 373 dead 对应PREPARE阶段中不提供startup回调的状态 374 375 starting 对应STARTING阶段中的状态 376 377 dying 对应STARTING阶段中不提供startup回调的状态 378 379 online 对应ONLINE阶段中的状态 380 381 offline 对应ONLINE阶段中不提供startup回调的状态 382 ======== ============================================ 383 384 由于@name参数只用于sysfs和检测,如果其他mode描述符比常见的描述符更好地描述状态的性质, 385 也可以使用。 386 387 @name参数的示例:"perf/online", "perf/x86:prepare", "RCU/tree:dying", "sched/waitempty" 388 389 @startup参数是一个指向回调的函数指针,在CPU上线操作时被调用。若应用不需要startup 390 回调,则将该指针设为NULL。 391 392 @teardown参数是一个指向回调的函数指针,在CPU下线操作时调用。若应用不需要teardown 393 回调,则将该指针设为NULL。 394 395 这些函数在处理已注册回调的方式上有所不同: 396 397 * cpuhp_setup_state_nocalls(), cpuhp_setup_state_nocalls_cpuslocked()和 398 cpuhp_setup_state_multi()只注册回调。 399 400 * cpuhp_setup_state()和cpuhp_setup_state_cpuslocked()注册回调,并对当前状态大于新 401 安装状态的所有在线CPU调用@startup回调(如果不是NULL)。根据状态阶段,回调要么在 402 当前的CPU上调用(PREPARE阶段),要么在CPU的热插拔线程中调用每个在线CPU(ONLINE阶段)。 403 404 如果CPU N的回调失败,那么CPU 0...N-1的teardown回调被调用以回滚操作。状态设置失败, 405 状态的回调没有被注册,在动态分配的情况下,分配的状态被释放。 406 407 状态设置和回调调用是针对CPU热拔插操作进行序列化的。如果设置函数必须从CPU热插拔的读 408 锁定区域调用,那么必须使用_cpuslocked()变体。这些函数不能在CPU热拔插回调中使用。 409 410 函数返回值: 411 ======== ========================================================== 412 0 静态分配的状态设置成功 413 414 >0 动态分配的状态设置成功 415 416 返回的数值是被分配的状态编号。如果状态回调后来必须被移除, 417 例如模块移除,那么这个数值必须由调用者保存,并作为状态移 418 除函数的@state参数。对于多实例状态,动态分配的状态编号也 419 需要作为实例添加/删除操作的@state参数。 420 421 <0 操作失败 422 ======== ========================================================== 423 424 移除CPU热拔插状态 425 ----------------- 426 427 为了移除一个之前设置好的状态,提供了如下函数: 428 429 * cpuhp_remove_state(state) 430 * cpuhp_remove_state_nocalls(state) 431 * cpuhp_remove_state_nocalls_cpuslocked(state) 432 * cpuhp_remove_multi_state(state) 433 434 @state参数要么是静态分配的状态,要么是由cpuhp_setup_state*()在动态范围内分配 435 的状态编号。如果状态在动态范围内,则状态编号被释放,可再次进行动态分配。 436 437 这些函数在处理已注册回调的方式上有所不同: 438 439 * cpuhp_remove_state_nocalls(), cpuhp_remove_state_nocalls_cpuslocked() 440 和 cpuhp_remove_multi_state()只删除回调。 441 442 * cpuhp_remove_state()删除回调,并调用所有当前状态大于被删除状态的在线CPU的 443 teardown回调(如果不是NULL)。根据状态阶段,回调要么在当前的CPU上调用 444 (PREPARE阶段),要么在CPU的热插拔线程中调用每个在线CPU(ONLINE阶段)。 445 446 为了完成移除工作,teardown回调不能失败。 447 448 状态移除和回调调用是针对CPU热拔插操作进行序列化的。如果移除函数必须从CPU hotplug 449 读取锁定区域调用,那么必须使用_cpuslocked()变体。这些函数不能从CPU热插拔的回调中使用。 450 451 如果一个多实例的状态被移除,那么调用者必须先移除所有的实例。 452 453 多实例状态实例管理 454 ------------------ 455 456 一旦多实例状态被建立,实例就可以被添加到状态中: 457 458 * cpuhp_state_add_instance(state, node) 459 * cpuhp_state_add_instance_nocalls(state, node) 460 461 @state参数是一个静态分配的状态或由cpuhp_setup_state_multi()在动态范围内分配的状 462 态编号。 463 464 @node参数是一个指向hlist_node的指针,它被嵌入到实例的数据结构中。这个指针被交给 465 多实例状态的回调,可以被回调用来通过container_of()检索到实例。 466 467 这些函数在处理已注册回调的方式上有所不同: 468 469 * cpuhp_state_add_instance_nocalls()只将实例添加到多实例状态的节点列表中。 470 471 * cpuhp_state_add_instance()为所有当前状态大于@state的在线CPU添加实例并调用与 472 @state相关的startup回调(如果不是NULL)。该回调只对将要添加的实例进行调用。 473 根据状态阶段,回调要么在当前的CPU上调用(PREPARE阶段),要么在CPU的热插拔线 474 程中调用每个在线CPU(ONLINE阶段)。 475 476 如果CPU N的回调失败,那么CPU 0 ... N-1的teardown回调被调用以回滚操作,该函数 477 失败,实例不会被添加到多实例状态的节点列表中。 478 479 要从状态的节点列表中删除一个实例,可以使用这些函数: 480 481 * cpuhp_state_remove_instance(state, node) 482 * cpuhp_state_remove_instance_nocalls(state, node) 483 484 参数与上述cpuhp_state_add_instance*()变体相同。 485 486 这些函数在处理已注册回调的方式上有所不同: 487 488 * cpuhp_state_remove_instance_nocalls()只从状态的节点列表中删除实例。 489 490 * cpuhp_state_remove_instance()删除实例并调用与@state相关的回调(如果不是NULL), 491 用于所有当前状态大于@state的在线CPU。 该回调只对将要被移除的实例进行调用。 492 根据状态阶段,回调要么在当前的CPU上调用(PREPARE阶段),要么在CPU的热插拔 493 线程中调用每个在线CPU(ONLINE阶段)。 494 495 为了完成移除工作,teardown回调不能失败。 496 497 节点列表的添加/删除操作和回调调用是针对CPU热拔插操作进行序列化。这些函数不能在 498 CPU hotplug回调和CPU hotplug读取锁定区域内使用。 499 500 样例 501 ---- 502 503 在STARTING阶段设置和取消静态分配的状态,以获取上线和下线操作的通知:: 504 505 ret = cpuhp_setup_state(CPUHP_SUBSYS_STARTING, "subsys:starting", subsys_cpu_starting, subsys_cpu_dying); 506 if (ret < 0) 507 return ret; 508 .... 509 cpuhp_remove_state(CPUHP_SUBSYS_STARTING); 510 511 在ONLINE阶段设置和取消动态分配的状态,以获取下线操作的通知:: 512 513 state = cpuhp_setup_state(CPUHP_ONLINE_DYN, "subsys:offline", NULL, subsys_cpu_offline); 514 if (state < 0) 515 return state; 516 .... 517 cpuhp_remove_state(state); 518 519 在ONLINE阶段设置和取消动态分配的状态,以获取有关上线操作的通知,而无需调用回调:: 520 521 state = cpuhp_setup_state_nocalls(CPUHP_ONLINE_DYN, "subsys:online", subsys_cpu_online, NULL); 522 if (state < 0) 523 return state; 524 .... 525 cpuhp_remove_state_nocalls(state); 526 527 在ONLINE阶段设置、使用和取消动态分配的多实例状态,以获得上线和下线操作的通知:: 528 529 state = cpuhp_setup_state_multi(CPUHP_ONLINE_DYN, "subsys:online", subsys_cpu_online, subsys_cpu_offline); 530 if (state < 0) 531 return state; 532 .... 533 ret = cpuhp_state_add_instance(state, &inst1->node); 534 if (ret) 535 return ret; 536 .... 537 ret = cpuhp_state_add_instance(state, &inst2->node); 538 if (ret) 539 return ret; 540 .... 541 cpuhp_remove_instance(state, &inst1->node); 542 .... 543 cpuhp_remove_instance(state, &inst2->node); 544 .... 545 remove_multi_state(state); 546 547 测试热拔插状态 548 ============== 549 550 验证自定义状态是否按预期工作的一个方法是关闭一个CPU,然后再把它上线。也可以把CPU放到某 551 些状态(例如 ``CPUHP_AP_ONLINE`` ),然后再回到 ``CPUHP_ONLINE`` 。这将模拟在 552 ``CPUHP_AP_ONLINE`` 之后的一个状态出现错误,从而导致回滚到在线状态。 553 554 所有注册的状态都被列举在 ``/sys/devices/system/cpu/hotplug/states`` :: 555 556 $ tail /sys/devices/system/cpu/hotplug/states 557 138: mm/vmscan:online 558 139: mm/vmstat:online 559 140: lib/percpu_cnt:online 560 141: acpi/cpu-drv:online 561 142: base/cacheinfo:online 562 143: virtio/net:online 563 144: x86/mce:online 564 145: printk:online 565 168: sched:active 566 169: online 567 568 要将CPU4回滚到 ``lib/percpu_cnt:online`` ,再回到在线状态,只需发出:: 569 570 $ cat /sys/devices/system/cpu/cpu4/hotplug/state 571 169 572 $ echo 140 > /sys/devices/system/cpu/cpu4/hotplug/target 573 $ cat /sys/devices/system/cpu/cpu4/hotplug/state 574 140 575 576 需要注意的是,状态140的清除回调已经被调用。现在重新上线:: 577 578 $ echo 169 > /sys/devices/system/cpu/cpu4/hotplug/target 579 $ cat /sys/devices/system/cpu/cpu4/hotplug/state 580 169 581 582 启用追踪事件后,单个步骤也是可见的:: 583 584 # TASK-PID CPU# TIMESTAMP FUNCTION 585 # | | | | | 586 bash-394 [001] 22.976: cpuhp_enter: cpu: 0004 target: 140 step: 169 (cpuhp_kick_ap_work) 587 cpuhp/4-31 [004] 22.977: cpuhp_enter: cpu: 0004 target: 140 step: 168 (sched_cpu_deactivate) 588 cpuhp/4-31 [004] 22.990: cpuhp_exit: cpu: 0004 state: 168 step: 168 ret: 0 589 cpuhp/4-31 [004] 22.991: cpuhp_enter: cpu: 0004 target: 140 step: 144 (mce_cpu_pre_down) 590 cpuhp/4-31 [004] 22.992: cpuhp_exit: cpu: 0004 state: 144 step: 144 ret: 0 591 cpuhp/4-31 [004] 22.993: cpuhp_multi_enter: cpu: 0004 target: 140 step: 143 (virtnet_cpu_down_prep) 592 cpuhp/4-31 [004] 22.994: cpuhp_exit: cpu: 0004 state: 143 step: 143 ret: 0 593 cpuhp/4-31 [004] 22.995: cpuhp_enter: cpu: 0004 target: 140 step: 142 (cacheinfo_cpu_pre_down) 594 cpuhp/4-31 [004] 22.996: cpuhp_exit: cpu: 0004 state: 142 step: 142 ret: 0 595 bash-394 [001] 22.997: cpuhp_exit: cpu: 0004 state: 140 step: 169 ret: 0 596 bash-394 [005] 95.540: cpuhp_enter: cpu: 0004 target: 169 step: 140 (cpuhp_kick_ap_work) 597 cpuhp/4-31 [004] 95.541: cpuhp_enter: cpu: 0004 target: 169 step: 141 (acpi_soft_cpu_online) 598 cpuhp/4-31 [004] 95.542: cpuhp_exit: cpu: 0004 state: 141 step: 141 ret: 0 599 cpuhp/4-31 [004] 95.543: cpuhp_enter: cpu: 0004 target: 169 step: 142 (cacheinfo_cpu_online) 600 cpuhp/4-31 [004] 95.544: cpuhp_exit: cpu: 0004 state: 142 step: 142 ret: 0 601 cpuhp/4-31 [004] 95.545: cpuhp_multi_enter: cpu: 0004 target: 169 step: 143 (virtnet_cpu_online) 602 cpuhp/4-31 [004] 95.546: cpuhp_exit: cpu: 0004 state: 143 step: 143 ret: 0 603 cpuhp/4-31 [004] 95.547: cpuhp_enter: cpu: 0004 target: 169 step: 144 (mce_cpu_online) 604 cpuhp/4-31 [004] 95.548: cpuhp_exit: cpu: 0004 state: 144 step: 144 ret: 0 605 cpuhp/4-31 [004] 95.549: cpuhp_enter: cpu: 0004 target: 169 step: 145 (console_cpu_notify) 606 cpuhp/4-31 [004] 95.550: cpuhp_exit: cpu: 0004 state: 145 step: 145 ret: 0 607 cpuhp/4-31 [004] 95.551: cpuhp_enter: cpu: 0004 target: 169 step: 168 (sched_cpu_activate) 608 cpuhp/4-31 [004] 95.552: cpuhp_exit: cpu: 0004 state: 168 step: 168 ret: 0 609 bash-394 [005] 95.553: cpuhp_exit: cpu: 0004 state: 169 step: 140 ret: 0 610 611 可以看到,CPU4一直下降到时间戳22.996,然后又上升到95.552。所有被调用的回调, 612 包括它们的返回代码都可以在跟踪中看到。 613 614 架构的要求 615 ========== 616 617 需要具备以下功能和配置: 618 619 ``CONFIG_HOTPLUG_CPU`` 620 这个配置项需要在Kconfig中启用 621 622 ``__cpu_up()`` 623 调出一个cpu的架构接口 624 625 ``__cpu_disable()`` 626 关闭CPU的架构接口,在此程序返回后,内核不能再处理任何中断。这包括定时器的关闭。 627 628 ``__cpu_die()`` 629 这实际上是为了确保CPU的死亡。实际上,看看其他架构中实现CPU热拔插的一些示例代 630 码。对于那个特定的架构,处理器被从 ``idle()`` 循环中拿下来。 ``__cpu_die()`` 631 通常会等待一些per_cpu状态的设置,以确保处理器的死亡例程被调用来保持活跃。 632 633 用户空间通知 634 ============ 635 636 在CPU成功上线或下线后,udev事件被发送。一个udev规则,比如:: 637 638 SUBSYSTEM=="cpu", DRIVERS=="processor", DEVPATH=="/devices/system/cpu/*", RUN+="the_hotplug_receiver.sh" 639 640 将接收所有事件。一个像这样的脚本:: 641 642 #!/bin/sh 643 644 if [ "${ACTION}" = "offline" ] 645 then 646 echo "CPU ${DEVPATH##*/} offline" 647 648 elif [ "${ACTION}" = "online" ] 649 then 650 echo "CPU ${DEVPATH##*/} online" 651 652 fi 653 654 可以进一步处理该事件。 655 656 内核内联文档参考 657 ================ 658 659 该API在以下内核代码中: 660 661 include/linux/cpuhotplug.h
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.