第一百九十七章: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。"
/*
* 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");#include <stdio.h>
/*
* 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);
}
int main() {
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");
return 0;
}二
工坊的墙壁上挂着一块巨大的黑板,上面画满了箭头——从 "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 暴露接口的模块是什么?