KVM 的对象管理与 vCPU 执行
一、KVM 的对象、执行环境与设备交接
KVM 是 Linux 内核中的虚拟化组件。它通过硬件辅助执行客户机,维护 vCPU、内存映射和中断状态;QEMU 提供机器与设备模型,Linux 调度器决定各个 vCPU 线程何时获得物理 CPU。普通客户机指令可以直接执行,访问由 QEMU 模拟的设备时则可能需要返回用户态。三者共同组成一台可运行的虚拟机。
本文跟踪“执行 CPUID → 读取 32 位设备寄存器 → 继续执行”这条路径,再比较 virtio 队列通知中的 ioeventfd 和 irqfd。一次 KVM_RUN 可以在内核里完成多次 VM Entry / VM Exit;只有需要用户态处理时才返回 QEMU。宿主调度器另外决定这个线程何时占用物理 CPU。
硬件入口以 Intel VMX 为例;AMD SVM 使用不同的底层指令和控制结构,KVM 的用户态接口保持相同。
QEMU 使用 TCG 时,执行路径转为指令翻译与宿主代码缓存,见QEMU TCG 的翻译块与执行循环。
源码版本:QEMU 10.2.0、Linux 6.18。
二、从 /dev/kvm 建立 VM 与 vCPU
一个用户态虚拟机程序先打开 /dev/kvm,查询 API 与可用能力,再创建 VM、登记 RAM、创建 vCPU 并设置初始寄存器。KVM 用文件描述符区分操作范围:VM 级资源供多个 vCPU 共用,每个 vCPU 则保存独立的执行上下文。
Mermaid 源码
flowchart TB
accTitle: 通过 KVM 文件描述符建立并运行虚拟机
accDescr: 用户态打开 dev kvm,创建 VM 描述符后登记内存并创建 vCPU。调用线程通过 vCPU 描述符执行 KVM_RUN,经过内核准备进入客户机。共享 kvm_run 在需要用户态处理时交接退出原因与结果。
DEV["打开 /dev/kvm<br/>查询接口与硬件能力"]
VM["VM 描述符<br/>一台虚拟机的共享配置"]
RAM["登记 RAM 内存槽<br/>客户机物理范围 ↔ 用户态后备内存"]
VCPU["vCPU 描述符<br/>寄存器 · 指令位置 · 执行状态"]
THREAD["QEMU vCPU 线程<br/>调用 KVM_RUN"]
KERNEL["同一线程进入 KVM<br/>准备映射、事件与执行状态"]
GUEST["硬件客户机环境<br/>执行客户机代码"]
SHARED["共享 kvm_run<br/>退出原因 · MMIO 参数与结果"]
DEV -->|KVM_CREATE_VM| VM
VM -->|配置共享内存| RAM
VM -->|KVM_CREATE_VCPU| VCPU
VCPU -->|配置完成后运行| THREAD
THREAD --> KERNEL --> GUEST
THREAD <-->|用户态交接| SHARED
KERNEL <-->|内核交接| SHARED
classDef api fill:#e4f1ec,stroke:#438575,color:#173e34
classDef state fill:#f4f3eb,stroke:#aaa98c,color:#3c4542
classDef execution fill:#eef3f2,stroke:#738b87,color:#263c38
class DEV,VM,VCPU api
class RAM,SHARED state
class THREAD,KERNEL,GUEST execution
| 接口 | 负责的范围 | 典型操作 |
|---|---|---|
/dev/kvm | 宿主 KVM 能力与对象创建 | 查询能力,以 KVM_CREATE_VM 得到 VM 描述符 |
| VM 描述符 | 这一台虚拟机的共享资源 | 登记内存槽、配置中断、创建 vCPU |
| vCPU 描述符 | 一个客户机处理器的执行状态 | 设置寄存器、映射交接区、调用 KVM_RUN |
创建 VM 得到的是待配置对象,内存槽描述 GPA 与用户态 RAM 后备范围的关系,vCPU 创建也不会自行启动客户机。准备完毕后,QEMU 的对应宿主线程调用 KVM_RUN,才开始执行这个 vCPU。线程进入内核、进入客户机、再返回用户态时,仍是同一个可被 Linux 调度的任务;它不必独占一个物理核。VM 创建接口、vCPU 对象
在 QEMU 中,这条准备路径从一个 CPUState 对象衔接到一个宿主线程:kvm_start_vcpu_thread() 创建线程,入口 kvm_vcpu_thread_fn() 接收同一个 CPUState。线程先执行 kvm_init_vcpu(),取得 vCPU 描述符并映射 kvm_run,再进入下面的循环。机器模型、RAM 和设备的建立过程见QEMU 虚拟机启动过程。
do {
qemu_process_cpu_events(cpu);
if (cpu_can_run(cpu)) {
r = kvm_cpu_exec(cpu);
if (r == EXCP_DEBUG) {
cpu_handle_guest_debug(cpu);
}
}
} while (!cpu->unplug || cpu_can_run(cpu));
这段线程循环把“对象已创建”和“允许执行”分开:事件处理先协调线程状态,满足运行条件后才进入 kvm_cpu_exec()。该函数调用 KVM_RUN;返回后,线程仍可处理暂停、调试或继续执行。本例后续的 CPUID 和设备读取,都由这个已经完成初始化的 vCPU 发起。
三、VM Entry、内核处理与用户态返回
开启硬件虚拟化后,普通客户机指令在处理器上直接执行。VM Entry 将处理器切入客户机执行环境;拦截到指定操作、异常或外部事件时,VM Exit 把控制权交回宿主内核。哪些操作需要拦截,取决于虚拟化控制设置和硬件能力。
VM Exit 后仍在当前 vCPU 线程的内核执行路径中。KVM 能完成处理时,会继续进入客户机;需要 QEMU 处理时,才结束当前 KVM_RUN。硬件退出本身不要求切换到另一个宿主线程,也不等同于返回用户态。
| 边界 | 记录与处理者 | 下一步 |
|---|---|---|
| 客户机 → KVM | VMX 的硬件退出原因及附加信息,由内核 VMX 后端读取 | 内核处理后重新进入客户机,或转成用户态请求 |
| KVM → QEMU | KVM_RUN 返回值,以及共享区中的 kvm_run.exit_reason | QEMU 调用设备模型、处理管理请求,再决定是否调用 KVM_RUN |
| 宿主任务之间切换 | Linux 调度器保存、恢复宿主线程上下文 | 同一个 vCPU 线程再次获得 CPU 后,接着执行其用户态、内核态或客户机路径 |
因此,硬件退出次数、KVM_RUN 返回次数、宿主线程调度切换次数是三种统计。优化其中一项,未必会等比例减少另外两项。
KVM_RUN 内部还有一层执行循环。下面是 Linux vcpu_run() 中连续的判断代码:运行或等待分支。
if (kvm_vcpu_running(vcpu)) {
r = vcpu_enter_guest(vcpu);
} else {
r = vcpu_block(vcpu);
}
if (r <= 0)
break;
内核已经处理完一次退出、允许继续时,返回正值,外层 for 循环再次进入客户机;返回 0 或负值才离开该循环。下面的 CPUID 与 MMIO 示例都从 vcpu_enter_guest() 进入运行路径:CPUID 在内核中处理完毕,返回正值继续循环;需要 QEMU 填入读取结果的 MMIO 返回 0,跳出循环并把请求交给用户态。vcpu_block() 则处理当前不能运行、需要等待的 vCPU。内核循环的返回值与 ioctl 对 QEMU 返回的成功值 0 属于两个层次。
四、跟踪实例:CPUID 后读取一个设备寄存器
1. 在内核中处理 CPUID
客户机执行 CPUID 时,VMX 后端按硬件退出原因找到 kvm_emulate_cpuid()。该函数读取客户机 EAX、ECX,按照虚拟 CPU 配置计算结果,写回 EAX、EBX、ECX、EDX,并推进被模拟的指令。普通执行路径返回“已处理”,vcpu_run() 继续循环,QEMU 的那次 KVM_RUN 仍未返回。CPUID 模拟
这里的结果来自客户机可见的 CPU 配置,并非简单复制宿主 CPUID。QEMU 在启动时参与配置虚拟 CPU 能力,但常规 CPUID 查询不需要每次再回到 QEMU 查询。
2. 返回 QEMU 读取设备寄存器
假设客户机读取一个由 QEMU 模拟的 32 位设备寄存器,地址为 0xf0001000。该设备访问不能由 KVM 内核设备处理,进入用户态 MMIO 路径。KVM 记录待完成的读操作,写入请求后返回:
exit_reason = KVM_EXIT_MMIO
mmio.phys_addr = 0xf0001000
mmio.len = 4
mmio.is_write = 0
mmio.data[0..3] = 等待 QEMU 填入
设备返回 0x12345678 后,x86 字节序下:
mmio.data[0..3] = 78 56 34 12
QEMU 从 KVM_RUN 返回后,按 exit_reason 进入下面的实际分支。address_space_rw() 使用客户机物理地址查找 MemoryRegion,长度、方向和数据缓冲区都直接来自同一份共享请求:MMIO 分支。
case KVM_EXIT_MMIO:
/* Called outside BQL */
address_space_rw(&address_space_memory,
run->mmio.phys_addr, attrs,
run->mmio.data,
run->mmio.len,
run->mmio.is_write);
ret = 0;
break;
本例 is_write = 0,设备读取回调把 0x12345678 放进 mmio.data。QEMU 的执行循环随后再次调用 KVM_RUN,内核消费这 4 个字节,完成此前暂停的指令模拟,更新客户机寄存器与指令位置。客户机下一条指令才可以使用读取结果;一次设备读取到这里完成。
Mermaid 源码
sequenceDiagram
accTitle: CPUID 内核处理与 MMIO 用户态交接
accDescr: 一次 KVM_RUN 内可处理 CPUID 并继续客户机执行。需要 QEMU 处理的 MMIO 读返回到用户态,设备结果写回共享区,再次 KVM_RUN 完成此前指令。
participant Q as QEMU vCPU 线程
participant K as KVM 内核
participant G as 客户机执行
participant D as QEMU 设备模型
Q->>K: KVM_RUN
K->>G: VM Entry
G->>K: CPUID → VM Exit
K->>K: 写回 CPUID 结果、推进指令
K->>G: VM Entry,原 KVM_RUN 尚未返回
G->>K: 设备 MMIO 读 → VM Exit
K->>K: 记录待完成读取,填写 kvm_run
K-->>Q: KVM_RUN 返回 0,KVM_EXIT_MMIO
Q->>D: address_space_rw:读取设备寄存器
D-->>Q: 返回 0x12345678
Q->>Q: mmio.data = 78 56 34 12
Q->>K: 再次 KVM_RUN
K->>K: complete_userspace_io 消费结果、完成指令
K->>G: VM Entry,继续后续指令
普通 RAM 访问通常直接经过客户机页表与 EPT / NPT 完成地址转换;EPT / NPT 缺页也可能在 KVM 中修复映射。内核设备、ioeventfd 匹配的写入和硬件直通设备又有不同路径。因此不能把所有 MMIO、所有 EPT 退出或所有设备请求都算成一次用户态退出。
五、交接载体:kvm_run 的 I/O 字段
QEMU 通过 KVM_CREATE_VCPU 获得 vCPU 文件描述符,查询 KVM_GET_VCPU_MMAP_SIZE 后,以该描述符映射共享区。CPUState.kvm_run 指向起始位置的 struct kvm_run。共享区包含请求字段及部分附加数据,不是所有客户机寄存器和设备状态的完整副本。创建与映射
| 字段 | 格式 | 写入方与使用方 |
|---|---|---|
exit_reason | 32 位枚举值 | KVM 写入;QEMU 在正常返回后选择对应联合体成员 |
io.direction / size / port / count | 8 / 8 / 16 / 32 位 | KVM 描述端口 I/O 的方向、单项字节数、端口和项数 |
io.data_offset | 64 位偏移 | 相对 kvm_run 起始地址定位数据;OUT 由 KVM 填数据,IN 由 QEMU 填数据 |
mmio.phys_addr / len / is_write | 64 / 32 / 8 位 | KVM 描述客户机物理地址、当前片段字节数及读写方向 |
mmio.data[8] | 最多 8 字节 | 写请求由 KVM 填入;读请求由 QEMU 写回读取结果 |
immediate_exit | 8 位输入 | 用户态设置,KVM_RUN 入口读取,用于完成必要收尾后尽快返回 |
io.data_offset 是共享区偏移,mmio.phys_addr 是客户机物理地址,两者都不能当作 QEMU 宿主指针直接解引用。MMIO 单片段的 8 字节限制也不意味着客户机指令只允许访问 8 字节:内核可以拆分访问,连续返回多个请求。ABI 字段定义
内核的 struct kvm_vcpu 则保存 vCPU ID、待处理请求、执行模式及架构状态,run 指向共享区。mmio_needed、mmio_fragments 和 mmio_cur_fragment 跟踪尚待完成的 MMIO;arch.complete_userspace_io 保存重新进入内核后的继续处理函数。这些属于内核内部状态,不能用共享区中的一个 exit_reason 代替。内核 vCPU 对象
六、暂停边界:完成当前 I/O 后停止执行
前面的读取沿“返回 MMIO 请求 → QEMU 填入结果 → 再次 KVM_RUN”完成。管理线程若在这个往返中要求暂停,仍需走完当前指令的交接,随后停止执行新的客户机指令。对于 KVM_EXIT_IO、KVM_EXIT_MMIO 等接口,设备回调结束时,内核可能还保留尚未完成的指令模拟;KVM API 要求重新进入 KVM_RUN,才能把这部分客户机状态视为一致。这个顺序直接影响暂停、快照与热迁移。
| 阶段 | 共享区 | 内核中的读取状态 |
|---|---|---|
| 请求返回 | MMIO 地址、长度及读方向有效 | 等待用户态提供读结果,指令未完成 |
| QEMU 设备回调完成 | data 中已有读取结果 | 仍待下一次进入;内核尚未消费结果 |
| 下一次 KVM_RUN | 内核读取 data;必要时发布下一个片段 | 消费结果、推进片段或完成指令 |
| 完成后继续执行或返回 | 随后一次返回按新的原因解释 | 可以进入客户机;也可以响应暂停而不执行新指令 |
设备已经返回数据,管理线程同时请求暂停
假设 QEMU 已把上例的 78 56 34 12 写入共享区,此时收到暂停请求。若直接保存 CPU 状态,内核中的 MMIO 指令仍未完成;若直接恢复正常运行,客户机又可能执行更多指令。需要的动作是:重新进入 KVM,消费这次读取结果,随后停在下一条客户机指令执行之前。
immediate_exit = 1 提供这个“完成待处理操作后立即返回”的入口。Linux 6.18 在 KVM_RUN 入口采样该字段,记录 wants_to_run = false;x86 路径先调用 arch.complete_userspace_io 指向的继续处理函数,再检查 wants_to_run。单片段读取完成后,KVM 以 -EINTR 返回,期间不执行新的客户机指令。多片段 I/O 若还需用户态提供数据,会先发布下一片段;调用方要继续交接,直到整个操作完成。入口采样、完成与退出的顺序
这个字段在入口读取一次。单独改写它不会使一个已经在客户机中运行的 vCPU 立即退出;QEMU 结合信号 kick 让线程离开运行路径,并在必要的重新进入中完成 I/O。QEMU 的重新进入与 kick、I/O 完成约定
调用方要先检查 KVM_RUN 的返回值。常规返回值为 0 时才按 exit_reason 分派请求;信号等错误返回要走对应处理分支,不能重复消费共享区里上一次的 MMIO 数据。
七、通知优化:ioeventfd 与 irqfd 的处理路径
上例的寄存器读取需要设备返回 4 字节结果,vCPU 必须等待这次读取完成。virtio 的队列 doorbell 写入则只表示“队列里有新请求”:描述符和缓冲区已经由客户机写入共享内存,后端可以异步处理。KVM 因而可以用事件通知替代这次通用用户态寄存器回调。
Mermaid 源码
sequenceDiagram
accTitle: 客户机提交方向的普通 MMIO 与 ioeventfd 对比
accDescr: 客户机先在共享内存发布队列内容。普通 MMIO 使 KVM_RUN 返回 QEMU 设备模型;匹配 ioeventfd 的 doorbell 写在 KVM 内发布事件并继续客户机。后端从共享队列取请求,eventfd 只通知,不传输数据。
participant G as 客户机驱动
participant K as KVM 内核
participant Q as QEMU vCPU / 设备模型
participant E as kickfd
participant B as 设备后端
participant M as 客户机内存中的队列
G-->>M: 发布描述符、缓冲区与 avail
alt doorbell 由普通用户态 MMIO 回调处理
G->>K: doorbell 写:VM Exit
K->>Q: KVM_RUN 返回 KVM_EXIT_MMIO
Q->>Q: 分派设备写回调
Q->>B: 通知或提交后端工作
Q->>K: 再次 KVM_RUN,完成指令
K->>G: VM Entry,继续执行
else 配置 ioeventfd 后的队列通知
G->>K: doorbell 写:通常仍有 VM Exit
K->>E: 地址 / 长度 / 可选数据匹配,signal kickfd
E->>B: 监听者得到通知
K->>G: 完成写入并继续,不返回 QEMU
end
B-->>M: 读取 avail、描述符及缓冲区,处理请求
Note over E,M: kickfd 只通知;描述符和实际数据留在客户机内存中
1. 客户机 → 后端:ioeventfd 匹配队列通知
QEMU 通过 KVM_IOEVENTFD 注册 PIO / MMIO 地址、访问长度及可选的匹配值。客户机写入 doorbell 后,KVM 在内核 I/O 总线上命中注册项,调用 eventfd_signal() 通知监听者,完成这次写入并继续客户机。监听者可以是 QEMU 的专用后端、内核 vhost worker 或其他后端;同一 vCPU 线程无需返回 QEMU 执行通用 MMIO 回调。地址匹配与写入处理
此处省去的是特定通知导致的 KVM_RUN 用户态往返。常见 x86 路径仍需先 VM Exit 到 KVM;后端线程也仍可能被唤醒和调度。设备配置读取、不匹配的寄存器访问与异常处理继续使用各自的原路径。
2. 后端 → 客户机:irqfd 通知完成
后端处理缓冲区并发布 used 记录后,可以触发与 KVM_IRQFD 绑定的 eventfd。KVM 根据已配置的 GSI / MSI 路由设置客户机待处理中断,按 vCPU 状态及中断允许条件交付。内核后端由此省去“通知 QEMU,再由 QEMU 调用中断注入接口”的接力;用户态后端触发 eventfd 本身仍可能需要系统调用。电平触发还涉及 resample 与撤销语义,不能简单视为无条件投递一个向量。IRQFD 接口
Mermaid 源码
sequenceDiagram
accTitle: 后端完成方向的 QEMU 中转与 irqfd 对比
accDescr: 后端先在共享队列中发布 used。没有 irqfd 时可通知 QEMU,再调用中断注入接口;绑定 irqfd 后触发 callfd,由 KVM 中断路由处理,不必由 QEMU 中转。callfd 与提交方向的 kickfd 是两个独立事件;客户机收到中断后从 used 消费完成信息。
participant B as 设备后端
participant Q as QEMU 设备模型
participant C as callfd
participant K as KVM 中断路由
participant G as 客户机驱动
Note over B,G: 后端先将完成记录发布到共享内存中的 used
alt 经 QEMU 中转完成通知
B->>Q: 完成通知
Q->>K: KVM_IRQ_LINE / KVM_SIGNAL_MSI 等接口
K->>G: 满足条件时交付中断
else 配置 irqfd 后
B->>C: 需要中断时 signal
C->>K: irqfd 处理事件,使用预设 GSI / MSI 路由
K->>G: 满足条件时交付中断,必要时唤醒 vCPU
end
G->>G: 从共享 used 读取完成信息,回收描述符
Note over C,G: callfd 不携带完成数据;与提交方向的 kickfd 相互独立
| 路径 | 方向与内容 | 减少的步骤 | 保留的工作 |
|---|---|---|---|
| 普通用户态 MMIO | 客户机与 QEMU 同步交接地址、读写数据 | 通用基线 | VM Exit、KVM_RUN 返回、设备回调、重新进入 |
| ioeventfd | 客户机写 doorbell → 后端事件 | 匹配写入的 vCPU 用户态回调与重新进入 | 通常仍有 VM Exit;后端读取队列、处理请求 |
| irqfd | 后端完成事件 → KVM 中断路由 → 客户机 | 经 QEMU 中转完成通知与显式注入的步骤 | 发布完成、路由、注入资格判断及可能的唤醒 |
eventfd 中不传网包、磁盘数据或描述符链。提交方向由后端读取 avail 和描述符,完成方向由客户机读取 used;双方还须遵守队列的内存顺序。通知次数可以因批处理或通知抑制而少于请求数。队列的具体布局见virtqueue 数据结构与发包过程。
八、源码调用链:运行、分派与继续执行
| 执行层 | 入口与关键操作 |
|---|---|
| QEMU 外层循环 | kvm_cpu_exec() 调用 KVM_RUN,按 IO / MMIO 等原因分派,处理完成后继续循环。 |
| KVM 入口与收尾 | kvm_arch_vcpu_ioctl_run() 先调用 complete_userspace_io,再检查是否继续执行,进入 vcpu_run。 |
| 内核执行循环 | vcpu_run() 调用 vcpu_enter_guest;处理结果大于 0 时继续,等于 0 时返回请求,小于 0 时返回错误。 |
| 硬件退出分派 | kvm_vmx_exit_handlers 将 VMX 原因对应到 CPUID、端口 I/O、控制寄存器等处理函数。 |
| MMIO 继续处理 | complete_emulated_mmio() 消费本片段读结果,推进剩余片段,全部完成后继续指令模拟。 |
| 端口 I/O | kvm_handle_io() 以 size 和 count 遍历紧凑数据数组,访问 QEMU 的 I/O 地址空间。 |
内核处理函数返回的“继续执行”与 KVM_RUN 对用户态返回的 0 含义不同。前者让内核循环继续,后者表示 ioctl 正常结束、用户态应检查退出原因。读调用链时需要保留这两个层次。
九、演进与取舍:减少往返,保留正确交接
用户态设备模拟便于实现多种设备,但高频寄存器访问会反复经过 KVM_RUN 返回、地址空间分派和重新进入。virtio 将大量工作描述放进共享队列,把寄存器访问集中到配置与通知;队列记录和实际数据留在客户机内存中。这降低了每个请求必须经过用户态寄存器处理的需求。
2009 年,Linux 引入 ioeventfd 和 irqfd。ioeventfd 的原始设计抓住了两类访问的区别:设备寄存器读取需要同步返回数据,而队列 doorbell 写入只负责通知后端有新工作,可以在发布事件后继续执行客户机。这为通知路径提供了比完整设备模拟更短的交接方式。
ioeventfd / irqfd 缩短通知交接,vhost 与 vhost-user再调整由谁执行队列的数据处理。两项优化可以组合,但收益来源不同:前者减少控制通知往返,后者改变数据面的执行上下文。配置变更、异常处理及不匹配访问仍需完整设备语义。
客户机执行 HLT 后,宿主线程还需要决定是否继续轮询或进入睡眠,具体过程见KVM vCPU 的阻塞、轮询与唤醒。
| 机制 | 减少的工作 | 增加的约束或代价 |
|---|---|---|
| 共享 virtqueue | 逐项寄存器交接,允许批量提交与完成 | 描述符所有权、内存顺序及通知抑制需要双方配合 |
| ioeventfd | 特定队列通知返回 vCPU 用户态线程的往返 | 需要建立地址匹配与事件生命周期 |
| irqfd | 完成通知经过用户态再调用中断注入接口的往返 | 需要配置中断路由,并处理电平触发等通知语义 |
| 内核或独立后端 | QEMU 通用设备模型的数据面处理 | 增加队列绑定、内存共享、迁移及故障处理的协调 |
这些执行边界在宿主机观察。Linux 的 kvm_exit 记录硬件退出,kvm_userspace_exit 记录 KVM 向用户态返回,QEMU 的 kvm_run_exit 则来自 QEMU 自己的 trace 系统。先在宿主执行 perf list 'kvm:*' 确认可用内核事件;QEMU 事件需通过该进程的 trace 配置启用,不会自动出现在 perf 的内核事件列表里。
对本文 CPUID 示例,应能在退出原因中找到 CPUID,而无需对应一次用户态设备请求;MMIO 示例则包含内核退出与用户态交接。实际追踪还会混有定时器、其他 vCPU 和调度事件,需要按 vCPU 线程及时间窗口筛选。事件计数用于定位处理层,业务延迟和后端 CPU 消耗用于判断成本。
内存映射和多 vCPU 同步构成执行循环的另一半。宿主页变化后,KVM 要撤销旧 EPT/NPT 映射;配置更新要通过请求与通知,使相关 vCPU 在再次进入客户机前采用新状态。减少退出不能省略这些正确性要求。映射建立与失效见EPT 缺页处理,请求位图、kick 和完成边界见KVM vCPU 请求与跨 CPU 同步。