Skip to content

第一百九十七章:KVM

大乘期

涉及内核源码:

铁匠 KVM 领着林小源走进熔炉背后的工坊。工坊里摆满了各种模具——每一个模具都是一个虚拟机的雏形。

"创建虚拟机。"KVM 说,拿起一个空模具,"第一步,打开 /dev/kvm。第二步,ioctl(KVM_CREATE_VM) 创建虚拟机实例。第三步,ioctl(KVM_CREATE_VCPU) 给它分配虚拟 CPU。"

他把一块烧红的金属倒入模具中,金属液缓缓填充模具的每一个角落。

"每个虚拟 CPU 有自己的一组寄存器——RIP、RSP、CR0、CR3。这些寄存器的值保存在 VMCS 里。"KVM 用锤子敲了敲模具,确保金属液均匀分布,"虚拟机运行时,CPU 在 non-root 模式执行客户机代码。遇到敏感指令——I/O 操作、MSR 访问、中断——CPU 自动切换回 root 模式,这就是 VM exit。"

c
/*
 * KVM 实现:
 *
 * 核心组件:
 *   kvm — 虚拟机实例
 *   vcpu — 虚拟 CPU
 *   vmcs — 虚拟机控制结构
 *
 * 创建虚拟机:
 *   kvm_fd = open("/dev/kvm", O_RDWR)
 *   vm_fd = ioctl(kvm_fd, KVM_CREATE_VM)
 *   vcpu_fd = ioctl(vm_fd, KVM_CREATE_VCPU)
 *
 * 运行虚拟机:
 *   ioctl(vcpu_fd, KVM_RUN)
 *   虚拟机运行在 non-root 模式
 *   VM exit 返回到 root 模式
 *
 * VM exit 原因:
 *   I/O 指令
 *   MSR 访问
 *   中断
 *   页面故障
 *
 * 内存管理:
 *   ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION)
 *   EPT 管理
 *
 * 设备模拟:
 *   QEMU 模拟设备
 *   virtio 半虚拟化
 */

/* 模拟 KVM 操作 */
struct kvm_vm {
    int fd;
    int vcpu_count;
    unsigned long memory_size;
};

struct kvm_vcpu {
    int fd;
    int id;
    unsigned long rip;
    unsigned long rsp;
};

void print_vm(struct kvm_vm *vm) {
    printf("VM: fd=%d, vcpus=%d, memory=%lu MB\n",
           vm->fd, vm->vcpu_count, vm->memory_size / 1024 / 1024);
}

void print_vcpu(struct kvm_vcpu *vcpu) {
    printf("VCPU %d: fd=%d, rip=0x%lx, rsp=0x%lx\n",
           vcpu->id, vcpu->fd, vcpu->rip, vcpu->rsp);
}

printf("=== KVM — 内核虚拟化 ===\n\n");

printf("KVM 是内核的虚拟化模块:\n\n");

printf("--- 创建虚拟机 ---\n");
printf("1. open(\"/dev/kvm\")\n");
printf("2. ioctl(KVM_CREATE_VM)\n");
printf("3. ioctl(KVM_CREATE_VCPU)\n");
printf("4. mmap(vcpu)\n\n");

/* 模拟 VM */
struct kvm_vm vm = {
    .fd = 3,
    .vcpu_count = 4,
    .memory_size = 4ULL * 1024 * 1024 * 1024,
};
print_vm(&vm);

printf("\n--- 虚拟 CPU ---\n");
struct kvm_vcpu vcpus[4];
for (int i = 0; i < 4; i++) {
    vcpus[i].fd = 10 + i;
    vcpus[i].id = i;
    vcpus[i].rip = 0x1000;
    vcpus[i].rsp = 0x7fff0000;
    print_vcpu(&vcpus[i]);
}

printf("\n--- 运行虚拟机 ---\n");
printf("while (1) {\n");
printf("  ioctl(vcpu_fd, KVM_RUN)\n");
printf("  switch (exit_reason) {\n");
printf("    case KVM_EXIT_IO: ...\n");
printf("    case KVM_EXIT_MMIO: ...\n");
printf("    case KVM_EXIT_INTR: ...\n");
printf("  }\n");
printf("}\n\n");

printf("--- VM exit 原因 ---\n");
printf("I/O 指令:\n");
printf("  in/out 指令\n");
printf("  QEMU 模拟设备\n\n");
printf("MSR 访问:\n");
printf("  特殊寄存器\n\n");
printf("中断:\n");
printf("  外部中断\n\n");
printf("页面故障:\n");
printf("  EPT 违规\n\n");

printf("--- 内存管理 ---\n");
printf("EPT (Extended Page Tables):\n");
printf("  两层页表\n");
printf("  GPA -> HVA -> HPA\n\n");
printf("KVM_SET_USER_MEMORY_REGION:\n");
printf("  设置内存区域\n");
printf("  用户空间提供内存\n\n");

printf("--- 设备模拟 ---\n");
printf("QEMU:\n");
printf("  模拟硬件设备\n");
printf("  I/O 处理\n\n");
printf("virtio:\n");
printf("  半虚拟化\n");
printf("  高效 I/O\n\n");

printf("--- KVM API ---\n");
printf("/dev/kvm:\n");
printf("  KVM_GET_API_VERSION\n");
printf("  KVM_CREATE_VM\n");
printf("  KVM_CREATE_VCPU\n");
printf("  KVM_RUN\n");

工坊的墙壁上挂着一块巨大的黑板,上面画满了箭头——从 "VM entry" 到 "non-root execution",再到 "VM exit",最后回到 "root handler"。这是一个循环,永不停歇。

"VM exit 的原因有很多。"KVM 用手指点着黑板,"I/O 指令——虚拟机执行 in/out 指令时,CPU 切回 root 模式,由 QEMU 模拟设备。MSR 访问——读写特殊寄存器。中断——外部中断需要注入虚拟机。页面故障——EPT 违规。"

"每次 VM exit 都要处理?"

"对。"KVM 说,"QEMU 是用户空间的进程,它通过 ioctl 和我通信。VM exit 发生时,我把 exit 原因告诉 QEMU,QEMU 处理完,再让我恢复虚拟机运行。整个过程对虚拟机是透明的——它以为自己在直接操作硬件。"

林小源看着那个循环的箭头,突然明白了:虚拟机的每一次"直接"操作,背后都可能是一次 VM exit 和一次 QEMU 处理。透明的代价是开销。

KVM 领着林小源走到工坊深处的一间密室。密室的墙壁上刻满了页表——不是一层,而是两层。

"这是 EPT——扩展页表。"KVM 说,"内存虚拟化的基石。"

林小源看到第一层页表将虚拟机的虚拟地址(GVA)翻译成虚拟机的物理地址(GPA)。第二层页表将 GPA 翻译成宿主机的物理地址(HPA)。

"GVA -> GPA -> HPA。"林小源念道。

"对。"KVM 说,"两层翻译都由硬件完成,不需要软件介入。虚拟机访问内存时,CPU 自动查两层页表,性能接近原生。"

他顿了顿,补充道:"如果虚拟机访问了未映射的 GPA,EPT 会产生违规,触发 VM exit。我处理违规,分配物理内存,更新 EPT,然后恢复虚拟机。"

林小源伸手触碰那些页表条目,指尖传来一阵冰凉——那是硬件级别的隔离,比 namespace 的气泡壁面坚固得多。


道藏笔记

内核启示

KVM 是内核的虚拟化模块。

核心组件:

  • kvm — 虚拟机实例
  • vcpu — 虚拟 CPU
  • vmcs — 虚拟机控制结构

创建流程:

  • open("/dev/kvm")
  • KVM_CREATE_VM
  • KVM_CREATE_VCPU
  • KVM_RUN

VM exit 原因:

  • I/O 指令
  • MSR 访问
  • 中断
  • 页面故障

内存管理:

  • EPT 两层页表
  • KVM_SET_USER_MEMORY_REGION

KVM 是引擎——驱动虚拟化的运行。


破关试炼

KVM 之试

把 Linux 内核变成 hypervisor,并通过 /dev/kvm 向 QEMU 暴露接口的模块是什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道