x86 vCPU 热插拔:拓扑、上线与异步移除
一、vCPU 热插拔与客户机状态
vCPU 热插拔用于在虚拟机运行期间调整虚拟处理器数量。热添加由 QEMU 创建 CPU 对象和执行线程,经 ACPI 通知后,客户机发现处理器、初始化每 CPU 状态,并将其上线供调度器使用。热移除则先由客户机迁移任务、中断和定时器,完成下线后,再由 QEMU 撤销相应设备与执行上下文。
这条路径属于虚拟机热插拔的 CPU 资源调整。与 PCIe 设备的插槽枚举不同,x86 PC 机器通过 ACPI CPU 热插拔接口表达处理器的存在与移除请求;新增处理器还受到启动时最大拓扑的约束。
本文以最大允许 4 个 vCPU、启动时使用 2 个的虚拟机为例,跟踪第三个 vCPU 的添加与移除。先区分预留位置、QEMU CPU 对象与客户机在线状态,再说明 QMP 操作、ACPI 通知、完成事件及源码实现。客户机采用支持 ACPI CPU 热插拔的 Linux,自动上线行为取决于客户机配置。
二、最大数量、设备存在与客户机在线
| 状态 | 含义 | 检查方式 |
|---|---|---|
| 最大拓扑 | 启动时预留可容纳的 socket、core、thread 位置和数量上限 | QEMU 的 smp/maxcpus 与 query-hotpluggable-cpus |
| QEMU 已创建设备 | 某个位置已经关联 CPU 对象及运行资源 | query-hotpluggable-cpus 中有 qom-path;query-cpus-fast 返回实例 |
| 客户机 present | 客户机已发现这个处理器 | 客户机 /sys/devices/system/cpu/present |
| 客户机 online | 客户机已完成上线,允许该 CPU 承担调度与中断工作 | 客户机 /sys/devices/system/cpu/online |
前三项也各有观察边界。QEMU 创建了设备,客户机可能尚未处理通知;客户机看到了 CPU,也可能按策略保持 offline。把 CPU 在客户机中 offline,通常只是停止使用,QEMU 对象和宿主线程仍可存在;它与从虚拟硬件中移除 CPU 是两件事。
Linux 的 possible 集合用于描述内核预先支持的 CPU 范围,通常在启动阶段确定。它还受客户机自身配置影响,不应机械地认为它一定与 QEMU 的 maxcpus 相等。最大拓扑也不代表预留了同样数量的独占物理核,宿主调度与绑核需要另行规划。
三、从 2 个 vCPU 扩到 3 个
下面列出示例虚拟机启动配置中与拓扑相关的部分。CPU 模型应已在本机验证可用;其余磁盘、内存、固件和 QMP 配置沿现有虚拟机设置。
-machine pc-q35-10.2,accel=kvm
-cpu Haswell-v4
-smp cpus=2,maxcpus=4,sockets=1,cores=4,threads=1
启动前预留 4 个单线程核心位置,最初只创建其中 2 个。进入 QMP 后先查询:
{"execute":"query-hotpluggable-cpus"}
在返回项中寻找没有 qom-path 的空位,使用该项实际返回的 type 和 props 创建 CPU。例如查询结果中 core-id 为 2 的空位使用下面的类型与拓扑属性:
{"execute":"device_add","arguments":{
"driver":"Haswell-v4-x86_64-cpu","id":"cpu-extra",
"socket-id":0,"core-id":2,"thread-id":0
}}
{"return":{}}
不同拓扑可能还返回 die-id 等字段,应完整沿用对应空位的属性。返回成功后,宿主侧通过 QMP query-cpus-fast 确认 QEMU 已建立实例;再进入客户机,只读查询发现与上线状态:
# 在客户机执行
cat /sys/devices/system/cpu/present
cat /sys/devices/system/cpu/online
lscpu -e=CPU,ONLINE
例如,添加前客户机 present 与 online 都是 0-1;添加后 present 成为 0-2,online 仍为 0-1。这说明发现已完成,而第三个 CPU 尚未上线,应检查客户机上线策略或错误日志;再次发送 device_add 不会解决这个阶段的问题。这里的连续编号只用于说明状态差异,实际编号须按客户机结果确认。
CPU 的 QMP 设备 ID、cpu-index、APIC ID、客户机逻辑 CPU 编号,以及宿主线程 ID 属于不同标识空间。尤其 query-cpus-fast 返回项顶层的 thread-id 是宿主线程 ID,而 props.thread-id 是虚拟拓扑中的线程位置;排查时应通过拓扑和查询结果关联,不能直接认为数字相等。
四、ACPI 通知与客户机上线、下线
Mermaid 源码
sequenceDiagram
accTitle: x86 vCPU 创建、上线与异步移除
accDescr: 管理程序创建 vCPU 后,QEMU 通知客户机发现并上线。移除时管理程序先得到受理响应,客户机迁走工作并回应 ACPI eject 后,QEMU 才真正拆除并发送完成事件。
participant M as 管理程序
participant Q as QEMU / ACPI
participant G as 客户机内核
M->>Q: device_add:空位与 CPU 类型
Q->>Q: 检查拓扑并创建 CPU
Q-->>M: 创建结果
Q->>G: SCI / 插入通知
G->>G: 发现 CPU,按策略上线
M->>Q: device_del
Q-->>M: 请求已受理
Q->>G: SCI / 移除通知
G->>G: 迁走任务、中断与定时器
G->>Q: ACPI eject
Q->>Q: 拆除 CPU,释放位置
Q-->>M: DEVICE_DELETED
机器模型维护可用 CPU 位置,ACPI 热插拔控制器保存每个位置的 CPU 指针、插入和移除标志。acpi_cpu_plug_cb() 将新 CPU 关联到位置,设置插入标记并发出 ACPI 事件。客户机处理 SCI 后执行固件提供的方法,确认 CPU 存在,进入自身 CPU 注册和上线流程。
移除时,管理程序发送 device_del,QEMU 设置移除标记并通知客户机。Linux 的 CPU hotplug 状态机按顺序执行下线回调,把任务、中断和定时器移到其他可用 CPU,并处理每 CPU 资源。客户机随后通过 ACPI eject 交还设备,QEMU 才能清理该 CPU 对象。CPU 插入与移除通知
如果客户机卡死、热插拔被禁用,或者下线流程无法完成,删除可能停在等待阶段。直接终止对应宿主线程会破坏虚拟机的执行模型,不能作为 CPU 热拔出的替代方案。x86 PC 路径还明确拒绝拔出启动 CPU,因此例子只移除后来添加的 cpu-extra。
五、移除完成事件与状态核对
{"execute":"device_del","arguments":{"id":"cpu-extra"}}
{"return":{}}
稍后可能收到完成事件(省略 timestamp):
{"event":"DEVICE_DELETED","data":{
"device":"cpu-extra","path":"/machine/peripheral/cpu-extra"
}}
命令成功表示移除请求已受理。管理程序需要持续接收并关联完成事件,也要能处理事件与响应的不同先后顺序。完成后再次查询可插拔 CPU 列表,该位置应不再包含 qom-path,运行实例列表中也不再包含被删除对象。客户机的 online 列表可能更早变化,不能单独用它证明宿主资源已经释放。
Libvirt 的 qemuDomainHotplugDelVcpu() 会标记待移除的别名、发送删除请求并等待设备移除;超时错误明确要求重新检查实际结果。事件可能在等待超时后才到达,因此重连后应重新查询状态,再决定是否重试或复用标识。Libvirt 的移除等待
使用 Libvirt 管理的虚拟机,应通过其 vCPU 管理接口操作,使运行状态与持久配置保持一致。--live、--config 和客户机内部 online 控制各有不同对象;不要用旁路 QMP 操作后再假定 Libvirt XML 会自动成为预期配置。
六、源码:拓扑位置、通知和拆除
| 入口或结构 | 阅读重点 |
|---|---|
| x86_possible_cpu_arch_ids() | 按 maxcpus 建立位置列表,把 APIC ID 与拓扑属性联系起来 |
| x86_cpu_pre_plug() | 检查型号、拓扑和可用位置,阻止重复占用或不匹配的 CPU |
| AcpiCpuStatus 与通知回调 | 维护插入、移除标记,并发出 SCI 对应的 ACPI 热插拔事件 |
| cpu_hotplug_wr() | 客户机写入 eject 标志后调用真正的 unplug,再解除对象关系 |
| x86_cpu_unplug_request_cb() / unplug_cb() | 拒绝启动 CPU、清空位置、unrealize 并更新机器计数 |
上层的通用设备接口把 CPU 与其他可插拔设备放在一致的生命周期框架中,具体协调由机器和 ACPI 实现。PCIe 设备通过端口插槽寄存器表达变化,CPU 则使用 ACPI CPU 热插拔寄存器与客户机 CPU hotplug 状态机。
七、从调整数量到管理完整生命周期
早期 CPU 增加接口更强调数量和 CPU 编号。现在的 query-hotpluggable-cpus 配合 device_add/device_del,显式表达空位、设备类型与拓扑,并用异步事件区分请求与完成。ACPI 中保留的旧接口兼容分支也说明,固件描述、机器版本与客户机支持需要一起演进。
在线扩容减少了重启,但不会自动增加应用并行度或宿主可用算力。新增 vCPU 还需要合适的宿主亲和性、NUMA 放置与客户机线程分配。缩容可降低争抢,也可能让剩余 CPU 的排队和尾延迟上升。应同时观察业务吞吐、尾延迟、客户机 online 状态和宿主调度等待。
可用性检查可以分三步:先验证预留位置能否成功创建,再验证客户机是否上线,最后验证移除事件和状态收敛。测试应包含客户机延迟响应或暂不响应的情况;只验证添加命令返回成功,会漏掉最容易出问题的后半段。