🌐 虚拟化概述
0%

🌐虚拟化概述

理解虚拟化的核心思想、目标与分类
基础概念

📌 什么是虚拟化

虚拟化 (Virtualization) 是一种资源管理技术,将物理资源(CPU、内存、I/O 设备)抽象为多个逻辑资源,使每个逻辑资源看起来像独立的物理资源。

核心目标
让多个隔离的执行环境(虚拟机)共享同一套物理硬件,每个 VM 认为自己独占整台机器。
  • 隔离性 — VM 之间互不干扰,一个 VM 崩溃不影响其他 VM
  • 封装性 — VM 的完整状态可保存、恢复、迁移
  • 硬件无关性 — VM 看到的硬件是 Hypervisor 虚拟的,可跨物理机迁移
  • 资源共享 — 多个 VM 共享物理资源,提高利用率
物理硬件 (Physical Hardware) CPU Memory I/O Devices Hypervisor / VMM VM 0 (Guest) Guest OS (Linux) Applications vCPU | vMEM | vIO VM 1 (Guest) Guest OS (Windows) Applications vCPU | vMEM | vIO VM 2 (Guest) Guest OS (FreeBSD) Applications vCPU | vMEM | vIO

🔑 关键术语

VMM / Hypervisor

虚拟机监视器,管理物理资源并为每个 VM 创建虚拟硬件抽象。是虚拟化的核心软件层。

Guest OS

运行在 VM 中的操作系统,认为自己运行在真实硬件上。不需要知道自己被虚拟化。

Host / Root

对于 Type-1 Hypervisor,指 Hypervisor 本身运行在最高特权级。对于 Type-2,指宿主 OS。

vCPU

虚拟 CPU,由 Hypervisor 通过时间片轮转在物理 CPU 上调度执行。

📏 Popek & Goldberg 虚拟化要求 (1974)

Popek 和 Goldberg 提出了一个系统可虚拟化的充分条件

三条要求
  1. 等价性 (Equivalence) — VM 上的程序行为与真实硬件上完全一致
  2. 资源控制 (Resource Control) — VMM 完全控制所有资源
  3. 效率性 (Efficiency) — 绝大多数指令直接在硬件上执行(不经过 VMM)

经典 x86 架构不满足可虚拟化条件(存在 17 条敏感但非特权的指令),这也是为什么需要 VT-x/AMD-V 硬件辅助。

🏗️ 虚拟化的三个层次

CPU 虚拟化

让多个 vCPU 分时复用物理 CPU。处理特权指令、中断、异常。最核心也最复杂。

内存虚拟化

每个 VM 看到连续的物理地址空间。Hypervisor 需要维护 GVA→GPA→HPA 两级映射。

I/O 虚拟化

让 VM 共享物理设备。可通过设备模拟、半虚拟化 (virtio)、设备直通等方式实现。

📐虚拟化分类

Type-1 vs Type-2、全虚拟化 vs 半虚拟化

🔷 Type-1 (Bare-Metal) Hypervisor

Hypervisor 直接运行在物理硬件上,没有底层宿主 OS。性能最好,是企业级虚拟化的标准。

物理硬件 Type-1 Hypervisor (直接运行在硬件上) VM 0 Guest OS + Apps VM 1 Guest OS + Apps VM 2 Guest OS + Apps 代表:Xen, VMware ESXi, KVM*, Hyper-V, Bao
💡 KVM 的特殊地位
KVM 将 Linux 内核本身变成 Type-1 Hypervisor。它以内核模块形式加载,利用 Linux 的内存管理、调度器等子系统,同时直接控制硬件 VT-x。严格来说 KVM 是 Type-1,但常被归为"Type-1.5"。

🔶 Type-2 (Hosted) Hypervisor

Hypervisor 作为应用程序运行在宿主 OS 上,依赖宿主 OS 管理硬件。

物理硬件 宿主 OS (Host OS) Type-2 Hypervisor (用户态程序) VM 0 Guest OS + Apps VM 1 Guest OS + Apps VM 2 Guest OS + Apps 代表:VirtualBox, VMware Workstation, Parallels, QEMU (TCG 模式)

性能较差,因为每次 VM 访问硬件都要经过宿主 OS 内核。适合开发测试,不适合生产环境。

⚡ 全虚拟化 vs 半虚拟化 vs 硬件辅助

方式原理Guest 修改性能
全虚拟化完全模拟硬件,Guest 无需修改不需要中(有开销)
半虚拟化Guest 知道自己被虚拟化,使用 Hypercall需要修改 OS
硬件辅助CPU 提供 VT-x/AMD-V 扩展不需要接近原生
⚠️ 现代实际方案
现代 Hypervisor 通常组合使用:CPU 用硬件辅助虚拟化,内存用 EPT/NPT,I/O 用半虚拟化 (virtio) 或设备直通。纯粹的全虚拟化和半虚拟化已很少单独使用。

📜虚拟化发展历史

从 IBM 大型机到现代云原生虚拟化

⏳ 发展时间线

1960s — IBM CP/CMS
最早的虚拟化系统。IBM 在 System/360 上实现分时共享,每个用户看到独立的虚拟机。
1972 — IBM VM/370
正式的虚拟机操作系统,Type-1 Hypervisor 的鼻祖。支持完整的虚拟机隔离。
1980s-1990s — 虚拟化低潮
x86 架构兴起,但 x86 不支持虚拟化(敏感非特权指令问题)。虚拟化主要用于大型机。
1998 — VMware 成立
通过二进制翻译在 x86 上实现全虚拟化,解决了 x86 不可虚拟化的问题。
2003 — Xen 开源
剑桥大学开发的半虚拟化方案,性能优秀但需要修改 Guest OS。后被 Citrix 收购。
2005-2006 — VT-x / AMD-V
Intel 和 AMD 分别推出硬件辅助虚拟化扩展。彻底解决 x86 虚拟化问题
2006 — KVM 合入 Linux
以色列公司 Qumranet 开发 KVM,将 Linux 内核变成 Hypervisor。2007 年合入主线。
2007 — EPT/NPT
Intel EPT (Extended Page Tables) 和 AMD NPT (Nested Page Tables) 硬件辅助内存虚拟化。
2010s — 云计算时代
AWS、Azure、GCP 大规模使用 KVM/Xen。容器 (Docker/K8s) 兴起,但 VM 仍是强隔离场景首选。
2019+ — 机密计算
Intel TDX、AMD SEV-SNP、ARM CCA 等 硬件可信执行环境,保护 VM 数据不被 Hypervisor 窃取。

🔧x86 架构速览

写 Hypervisor 必须掌握的 x86 知识

💍 特权级 (Privilege Rings)

x86 有 4 个特权级(Ring 0-3),Ring 0 最高,Ring 3 最低:

Ring -1 VMX Root Ring 0 内核态 Ring 1 Ring 2 Ring 3 用户态 Linux/Windows 只用 Ring 0 和 Ring 3 VT-x 引入 Ring -1 Hypervisor 运行在此 Guest OS 仍认为 自己在 Ring 0

VT-x 引入了 VMX Root(Hypervisor)和 VMX Non-Root(Guest)两种运行模式,本质上是比 Ring 0 更高的特权级。

📖 关键系统寄存器

寄存器作用虚拟化相关
CR0控制寄存器 0:保护模式、分页等控制 Guest 的分页开关
CR3页表基址寄存器Guest CR3 → EPT 转换
CR4控制寄存器 4:PAE、VMXE 等必须设置 VMXE 才能进入 VMX
IDTR中断描述符表基址Guest IDTR 由 Hypervisor 管理
GDTR全局描述符表基址Guest GDT 由 Hypervisor 管理
MSR型号特定寄存器STAR、LSTAR、EFER 等需虚拟化
RFLAGS标志寄存器IF 位控制中断,需虚拟化

🏗️ 地址空间

GVA (Guest Virtual Address)

Guest OS 中进程的虚拟地址。经过 Guest 页表转换为 GPA。

GPA (Guest Physical Address)

Guest OS 认为的"物理地址"。实际上是 Hypervisor 分配的虚拟物理地址。

HPA (Host Physical Address)

真实的物理地址。EPT 将 GPA 映射到 HPA。

地址转换链
GVA ──Guest 页表──→ GPA ──EPT──→ HPA ──→ 物理内存

没有 EPT 时(影子页表)
GVA ──影子页表──→ HPA (Hypervisor 合并两级页表)

⚙️ 特权指令 vs 敏感指令

🔴 x86 虚拟化的核心难题
x86 有 17 条敏感但非特权指令(如 POPF, SGDT, PUSHF),它们在 Ring 3 执行时不触发异常但会泄露或修改特权状态。这违反了 Popek & Goldberg 的可虚拟化条件。

解决方案的演进:

  1. 软件方案:二进制翻译(VMware)、半虚拟化(Xen)
  2. 硬件方案:VT-x/AMD-V(2005+),让所有敏感指令都可陷入

💍特权级与 Ring

深入理解 x86 特权级机制与虚拟化的关系

🔒 特权级的工作方式

当 CPU 执行一条指令时,会检查当前 CPL (Current Privilege Level):

  • 特权指令(如 HLT, LIDT, MOV CRx):只在 Ring 0 执行,Ring 3 执行触发 #GP 异常
  • IOPL 敏感指令(如 IN, OUT, CLI, STI):受 IOPL 字段控制
  • 敏感但非特权指令(如 POPF, SGDT):Ring 3 不触发异常,但行为不同
; 特权指令 — Ring 3 执行会 #GP
mov cr3, rax       ; 写页表基址 — 只能在 Ring 0
hlt                ; 停机 — 只能在 Ring 0
lidt [rax]         ; 加载 IDT — 只能在 Ring 0

; 敏感但非特权 — Ring 3 执行不异常!
sgdt [rax]         ; 存储 GDT 寄存器 — 泄露内核地址
popf               ; 恢复 RFLAGS — 可能改变 IF 位

🎯 VT-x 如何解决这个问题

VT-x 引入了 VMX RootVMX Non-Root 两种操作模式:

  • VMX Root:Hypervisor 运行模式。可以执行 VMXON, VMCS 相关指令
  • VMX Non-Root:Guest 运行模式。某些指令会触发 VM Exit 陷入 Hypervisor

两个模式都可以使用 Ring 0-3,但 VMX Non-Root 模式下,即使在 Ring 0 执行某些指令也会触发 VM Exit。这完美解决了敏感非特权指令的问题。

💡 本质理解
VT-x 不是替代 Ring 0-3,而是在其之上增加了一层控制。Guest OS 仍然运行在 Ring 0(VMX Non-Root),但 Hypervisor 运行在 VMX Root,可以拦截 Guest 的任何行为。

🪤陷入与模拟 (Trap & Emulate)

虚拟化 CPU 的经典方法

🔄 基本原理

当 Guest 执行特权指令时,CPU 陷入(trap)到 Hypervisor,Hypervisor 模拟(emulate)该指令的效果,然后返回 Guest 继续执行。

Guest (Ring 0) mov eax, [addr] add ebx, 1 mov cr3, rax ← 陷阱! nop mov eax, [addr] hlt ← 陷阱! ... Hypervisor 捕获 #GP 异常 解码指令 更新 VM 状态 (如更新 vCR3) 返回 Guest 捕获 HLT 停止 vCPU 调度 等待唤醒事件 VM Exit 开销: ~1000-3000 cycles

⚡ 性能问题

每次陷入/模拟的开销约 1000-3000 个时钟周期。如果 Guest 频繁执行特权指令(如操作系统启动时),性能会严重下降。

这就是为什么:

  • VMware 发明了二进制翻译来减少陷入次数
  • Xen 用半虚拟化让 Guest 主动配合
  • Intel/AMD 推出 VT-x/AMD-V 让陷入更高效

🔄二进制翻译 (Binary Translation)

VMware 在 x86 上实现全虚拟化的核心技术

🧠 核心思想

不直接执行 Guest 代码,而是在执行前扫描并替换敏感指令为安全的等价代码。普通指令直接在硬件上执行(直接执行),只有敏感指令被翻译。

Guest 代码 mov eax, 1 pushf ← 敏感! add ebx, 2 sgdt [mem] ← 敏感! mov ecx, eax ... 翻译器 扫描 → 替换敏感指令 翻译后代码 mov eax, 1 ✓ 直接执行 call emulate_pushf add ebx, 2 ✓ 直接执行 call emulate_sgdt mov ecx, eax ✓ 直接执行 ... CPU 执行 翻译后的代码 在 Ring 3 运行

优点:大多数指令零开销直接执行。缺点:翻译本身有开销,需要维护翻译缓存(Translation Cache)。

⚙️VT-x / AMD-V 硬件辅助虚拟化

Intel VT-x 和 AMD-V 的核心机制
CPU 虚拟化 · 核心

🏗️ VMX 操作模式

VT-x 引入两种 CPU 操作模式:

VMX Root 模式 Hypervisor 可以执行 VMLAUNCH, VMRESUME 可以执行 VMCLEAR, VMPTRLD 可以执行 VMREAD, VMWRITE Ring 0-3 都可用 VMX Non-Root 模式 Guest OS + Apps 某些指令触发 VM Exit CPUID → VM Exit HLT → VM Exit Ring 0-3 都可用 VM Entry VM Exit

📋 VMCS (Virtual Machine Control Structure)

VMCS 是 VT-x 的核心数据结构,存储 VM 的所有控制信息。每个 vCPU 有一个 VMCS。

Guest-State Area

保存 Guest 的寄存器状态:CR0/3/4, RSP, RIP, RFLAGS, 段寄存器, GDTR, IDTR 等。VM Entry 时加载,VM Exit 时保存。

Host-State Area

保存 Hypervisor 的寄存器状态。VM Exit 时自动加载,让 CPU 回到 Hypervisor 上下文。

VM-Execution Fields

控制哪些操作会触发 VM Exit:I/O 位图、MSR 位图、Exception 位图、EPT 指针等。

VM-Exit/Entry Fields

控制 Exit/Entry 行为:Exit 原因、Exit Qualification、Entry 控制、Entry MSR 加载列表等。

🚪 VM Exit 触发条件

以下事件会导致 CPU 从 VMX Non-Root 退出到 VMX Root:

类别触发条件Exit Reason
指令CPUID, HLT, INVD, INVLPG33, 12, 28, 14
控制寄存器MOV CRx, LMSW, CLTS28-30
I/OIN, OUT, INS, OUTS按 I/O 位图
中断外部中断、NMI、异常0-32
MSRRDMSR, WRMSR31, 32
EPTEPT 违规、EPT 配置变更48, 49
定时器VMX Preemption Timer 超时52
⚠️ VM Exit 开销
每次 VM Exit 约 1000-3000 cycles。设计 Hypervisor 时要尽量减少 VM Exit 次数。可以通过 MSR 位图、I/O 位图来选择性地让某些操作不触发 Exit。

🔑 关键 VMX 指令

; 启用 VMX
VMXON  [vmxon_region]     ; 进入 VMX 操作模式

; VMCS 操作
VMCLEAR [vmcs_region]     ; 初始化 VMCS
VMPTRLD [vmcs_region]     ; 加载 VMCS 指针 (当前 VMCS)
VMREAD  field, reg        ; 读取 VMCS 字段
VMWRITE reg, field        ; 写入 VMCS 字段

; VM 切换
VMLAUNCH                  ; 首次启动 Guest
VMRESUME                  ; 恢复 Guest 执行

; 退出 VMX
VMXOFF                   ; 退出 VMX 操作模式

📋VMCS 结构详解

Virtual Machine Control Structure 的完整剖析

📐 VMCS 布局

VMCS Region (4KB, 4字节对齐) Guest-State Area CR0, CR3, CR4 RSP, RIP, RFLAGS CS, SS, DS, ES, FS, GS GDTR, IDTR, LDTR, TR MSR, Activity State... Host-State Area CR0, CR3, CR4 RSP, RIP CS, SS, DS, ES, FS, GS GDTR, IDTR, TR MSR 加载列表 VM-Execution Fields Pin-Based Controls Processor Controls Exception Bitmap I/O Bitmap A/B EPTP, MSR Bitmap VM-Exit Information Fields Exit Reason, Exit Qualification Guest Linear/Physical Address IDT-Vectoring, VM-Exit Intr Info VM-Entry Fields Entry Controls MSR 加载列表 Event Injection (中断/异常) VMCS Revision ID (4 bytes) | Abort Error Code | VMCS Data

⚙️ 关键 VMCS 字段 (VM-Execution Controls)

字段作用典型配置
Pin-Based Controls外部中断、NMI 虚拟化控制启用外部中断退出
Proc-Based Controls控制哪些指令触发 VM ExitHLT/CPUID/CR 访问退出
Proc-Based Controls 2EPT、VPID、RDTSCP 等启用 EPT, VPID
Exception Bitmap哪些异常触发 VM Exit通常 0 或特定位
I/O Bitmap A/B哪些 I/O 端口触发 VM Exit全 1 = 所有 I/O 退出
MSR Bitmap哪些 MSR 读写触发 VM Exit选择性退出
EPTPEPT 页表指针指向 EPT PML4
CR0/CR4 Guest-Host Mask哪些 CR 位由 Hypervisor 控制PG, PE 等

🚪VM Entry / VM Exit

CPU 在 Hypervisor 和 Guest 之间切换的完整流程

➡️ VM Entry 流程

执行 VMLAUNCHVMRESUME 时,CPU 自动执行:

  1. 检查 VMCS 是否有效,VMX 控制字段是否合法
  2. 从 VMCS Host-State Area 加载 Hypervisor 状态的"回退地址"(保存到内部寄存器)
  3. 从 VMCS Guest-State Area 加载 Guest 状态:CR0/3/4, RSP, RIP, RFLAGS, 段寄存器等
  4. 如果配置了 MSR 加载列表,从内存加载 MSR 值
  5. 如果有 Event Injection(注入中断/异常),模拟其发生
  6. 切换到 VMX Non-Root 模式,开始执行 Guest 代码

⬅️ VM Exit 流程

当触发退出条件时,CPU 自动执行:

  1. 将 Guest 状态保存到 VMCS Guest-State Area
  2. 将 VM Exit 信息写入 VMCS:Exit Reason, Exit Qualification 等
  3. 从 VMCS Host-State Area 加载 Hypervisor 状态
  4. 如果配置了 MSR 存储列表,保存 Guest MSR 到内存
  5. 切换到 VMX Root 模式,RIP 指向 VMCS 中的 Host RIP
  6. CPU 开始执行 Hypervisor 代码
💡 关键点
VM Entry 和 VM Exit 都是硬件自动完成的,不需要软件干预。Hypervisor 只需要正确配置 VMCS,然后执行 VMLAUNCH/VMRESUME。退出后处理 Exit Reason 即可。

🔄 完整的 Hypervisor 执行循环

/* Hypervisor 主循环 */
while (1) {
    // 1. VM Entry: 硬件自动加载 Guest 状态
    vmresume();   // 或 vmlaunch() 首次启动

    // 2. Guest 执行,直到触发 VM Exit
    //    (硬件自动保存 Guest 状态,加载 Host 状态)

    // 3. 处理 VM Exit
    uint64_t reason = vmread(VMCS_EXIT_REASON);
    switch (reason) {
    case EXIT_REASON_CPUID:
        handle_cpuid();
        break;
    case EXIT_REASON_HLT:
        handle_hlt();
        break;
    case EXIT_REASON_IO_INSTRUCTION:
        handle_io();
        break;
    case EXIT_REASON_EPT_VIOLATION:
        handle_ept_violation();
        break;
    default:
        panic("Unhandled VM Exit: %lu", reason);
    }
    // 4. 更新 Guest RIP (跳过触发 Exit 的指令)
    advance_guest_rip();
    // 5. 回到步骤 1,继续执行 Guest
}

🗺️内存虚拟化概述

GVA → GPA → HPA 的两级地址转换
内存虚拟化

🎯 核心问题

每个 Guest OS 认为自己拥有从地址 0 开始的连续物理内存。但实际上:

  • Guest 的"物理地址" (GPA) 不是真实物理地址 (HPA)
  • Hypervisor 需要维护 GPA → HPA 的映射
  • 同时 Guest 自己维护 GVA → GPA 的页表
GVA (Guest 虚拟) 进程 A 代码 进程 A 数据 内核空间 GPA (Guest "物理") 内核代码 内核数据 用户页帧 设备 MMIO HPA (真实物理) 物理页帧 0x1A 物理页帧 0x2F 物理页帧 0x05 设备寄存器 Guest 页表 EPT 两级地址转换:GVA→GPA (Guest 管理) + GPA→HPA (Hypervisor 管理)

🔧 三种实现方式

1. 影子页表 (Shadow Page Table)

Hypervisor 将两级页表合并为一级,直接映射 GVA→HPA。每次 Guest 修改页表都需要同步。开销大,已较少使用。

2. EPT/NPT (硬件辅助)

硬件自动完成两级转换。CPU 先查 Guest 页表得到 GPA,再查 EPT 得到 HPA。现代主流方案

3. 半虚拟化页表

Guest 主动将页表更新通知给 Hypervisor。需要修改 Guest OS。Xen 早期使用。

👻影子页表 (Shadow Page Table)

软件方式实现两级页表合并

🧠 工作原理

Hypervisor 为每个 Guest 维护一份影子页表,直接映射 GVA → HPA。Guest 修改自己的页表时,Hypervisor 拦截并更新影子页表。

/* 影子页表结构 */
Guest 页表:  GVA 0x1000 → GPA 0x5000
EPT:         GPA 0x5000 → HPA 0x8A000
影子页表:    GVA 0x1000 → HPA 0x8A000   // 直接映射!

/* 当 Guest 修改 CR3 (切换页表) */
VM Exit → Hypervisor 捕获
→ 解析 Guest 新页表
→ 构建新的影子页表
→ 将影子页表加载到物理 CR3
→ VM Resume
⚠️ 性能问题
每次 Guest 修改页表(包括正常的进程切换、页面分配)都会触发 VM Exit。开销巨大,特别是内存密集型工作负载。这就是为什么 EPT 被发明。

📊EPT / NPT (嵌套页表)

硬件辅助内存虚拟化的核心机制

🏗️ EPT 结构

EPT (Extended Page Tables) 是 Intel 的硬件辅助内存虚拟化。它是一个由 Hypervisor 维护的页表,将 GPA 映射到 HPA。

地址转换过程(硬件自动完成):

/* 完整的 GVA → HPA 转换过程 */
1. CPU 使用 Guest CR3 指向的 Guest 页表
2. 查 Guest 页表: GVAGPA
3. 使用 EPTP 指向的 EPT 页表
4. 查 EPT: GPAHPA
5. 访问真实物理内存

/* 每一级 Guest 页表项都要经过 EPT 转换 */
/* 最坏情况: 4级 Guest × 4级 EPT = 24 次内存访问 */
/* 实际: 用 Page Walk Cache 优化到 ~6 次 */
GVA Guest 虚拟地址 Guest 页表 CR3 → PML4 → PDPT → PD → PT 4级,硬件自动遍历 GPA Guest 物理地址 EPT 页表 EPTP → PML4 → PDPT → PD → PT Hypervisor 维护 HPA Guest 页表遍历的每一步 GPA 都要经过 EPT 转换 最坏 24 次访存 → 用 Page Walk Cache 优化到 ~6 次

⚡ EPT 违规 (EPT Violation)

当 Guest 访问的 GPA 在 EPT 中没有有效映射时,触发 EPT Violation(VM Exit reason 48)。

常见场景:

  • 首次访问:Hypervisor 需要分配物理页并建立 EPT 映射
  • MMIO 区域:设备寄存器映射,需要模拟设备操作
  • 内存回收:Hypervisor 撤销了映射(如 ballooning)
  • 写保护:用于脏页跟踪(热迁移时)

📊 EPT 页表项权限

名称含义
0Read允许读
1Write允许写
2Execute允许执行
3EPT Memory Type0=UC, 6=WB
5Accessed硬件置位,表示被访问过
6Dirty硬件置位,表示被写过
7Suppress VE禁止 Virtualization Exception
💡 EPT vs 影子页表性能对比
EPT 将内存虚拟化的开销从 数十% 降低到 1-5%。唯一增加的是 TLB miss 时的页表遍历开销(因为需要两级遍历)。

🏷️VPID 与 TLB 管理

Virtual Processor Identifier 优化 TLB 性能

❓ 没有 VPID 的问题

每次 VM Entry/Exit 时,TLB 中的地址映射变得无效(因为 Root/Non-Root 的映射不同)。必须刷新整个 TLB,性能损失巨大。

✅ VPID 的解决方案

VPID 为每个 VMX 操作上下文分配一个唯一的标识符。TLB 条目标记了它属于哪个 VPID,不同上下文的 TLB 条目可以共存。

  • VPID = 0:VMX Root 操作(Hypervisor)
  • VPID = 1, 2, 3...:每个 vCPU 有唯一 VPID
  • VM Entry/Exit 时不需要刷新 TLB
💡 性能提升
启用 VPID 后,VM Entry/Exit 的 TLB 开销从 数万 cycles 降低到几乎为零。这是 EPT 必须搭配的功能。

📌内存分配策略

Hypervisor 如何为 VM 分配和管理物理内存

📊 分配策略

静态分配

启动时为每个 VM 分配固定大小内存。简单但不灵活,可能导致内存浪费。

Memory Ballooning

Guest 中运行 balloon 驱动,主动释放不需要的内存回 Hypervisor。VMware 和 KVM 都支持。

内存超售 (Overcommit)

分配给所有 VM 的总内存超过物理内存。依赖不同时使用全部内存的假设。可能导致 swap。

KSM (Kernel Same-page Merging)

扫描相同内容的内存页,合并为一份(COW)。在相同 OS 的 VM 间效果显著。

🔌I/O 虚拟化概述

让 VM 共享物理设备的三种方式
I/O 虚拟化

🔀 三种 I/O 虚拟化方式

① 设备模拟 软件模拟硬件设备 VM: IN/OUT 指令 ↓ VM Exit Hypervisor 模拟设备 ✅ 兼容性最好 ❌ 性能最差 代表: QEMU emulated NIC ② 半虚拟化 (VirtIO) Guest 知道被虚拟化 VM: VirtIO 驱动 ↓ 共享内存 vring 队列 + 事件通知 ✅ 性能好 ⚠️ 需要 Guest 驱动 代表: virtio-net, virtio-blk ③ 设备直通 物理设备直接给 VM VM 直接访问设备 ↓ IOMMU 保护 DMA 直接到 Guest 内存 ✅ 性能最好 (接近原生) ❌ 设备不能共享 需要: Intel VT-d / AMD-Vi

🖥️设备模拟 (Device Emulation)

在 Hypervisor 中模拟硬件设备

🔄 工作流程

Guest 执行 I/O 指令(IN/OUT)时,触发 VM Exit。Hypervisor 根据 I/O 端口和方向模拟设备行为:

/* 处理 Guest 的 IN 指令 */
void handle_io_exit(vmexit_info_t *exit) {
    uint16_t port = exit->port;      // I/O 端口号
    uint32_t size = exit->size;      // 1/2/4 字节
    bool is_in = exit->direction;    // IN=true, OUT=false

    if (is_in) {
        // 从模拟设备读取数据
        uint32_t val = emu_device_read(port, size);
        // 写入 Guest 的 EAX 寄存器
        set_guest_reg(REG_RAX, val);
    } else {
        // 从 Guest 的 EAX 读取数据
        uint32_t val = get_guest_reg(REG_RAX);
        // 写入模拟设备
        emu_device_write(port, val, size);
    }
}

📦 常见模拟设备

设备类型说明
i440FX芯片组模拟传统 PC 芯片组(QEMU 默认)
PIIX3 IDE磁盘传统 IDE 控制器,兼容性好
e1000网卡Intel 千兆网卡,多数 OS 有驱动
Cirrus VGA显卡基础 VGA 显示
AC97声卡音频设备
RTC时钟实时时钟 (CMOS)

VirtIO 半虚拟化驱动

高性能 I/O 虚拟化的标准方案

🧠 核心思想

VirtIO 让 Guest 知道自己被虚拟化,使用共享内存 + 事件通知的高效通信机制,避免每次 I/O 都触发 VM Exit。

Guest (VirtIO 驱动) Virtqueue 描述符环 Available Ring Guest 写 Used Ring (Hypervisor 写) 共享内存中的环形缓冲区 Hypervisor (VirtIO 后端) 读取 Available Ring 处理请求 → 写回 Used Ring 通知 Guest (中断/事件) 通知 中断 大多数 I/O 通过共享内存完成,无需 VM Exit

📦 VirtIO 设备类型

设备说明性能
virtio-net网络设备~10 Gbps+
virtio-blk块设备 (磁盘)接近原生 SSD
virtio-scsiSCSI 控制器支持多设备
virtio-gpuGPU 设备2D/3D 加速
virtio-fs文件系统共享高性能共享目录
vhost-net内核态 VirtIO 后端比用户态快 2-3x

🔗设备直通 (Device Passthrough)

使用 IOMMU 将物理设备直接分配给 VM

🛡️ IOMMU 的作用

IOMMU (Intel VT-d / AMD-Vi) 是 DMA 设备和物理内存之间的地址转换单元,类似于 CPU 的 MMU。

  • DMA 地址转换:设备使用 GPA,IOMMU 转换为 HPA
  • 隔离保护:防止设备 DMA 访问其他 VM 的内存
  • 中断重映射:设备中断路由到正确的 VM
/* IOMMU 地址转换 */
设备发出 DMA 请求: DMA addr = 0x1000 (GPA)
         ↓
IOMMU 查设备页表: GPA 0x1000 → HPA 0x8A000
         ↓
物理内存访问: HPA 0x8A000

🧩SR-IOV

Single Root I/O Virtualization — 硬件级 I/O 虚拟化

🧠 核心思想

SR-IOV 让一个物理设备在硬件层面呈现为多个独立的虚拟设备(VF, Virtual Function)。每个 VF 可以直通给不同的 VM。

PF (Physical Function)

完整的 PCIe 功能,由 Hypervisor 管理。负责创建和管理 VF。

VF (Virtual Function)

轻量级 PCIe 功能,直通给 VM。每个 VF 有独立的 BAR 空间和 MSI-X 中断。

典型场景:一张 10GbE 网卡可以虚拟出 64 个 VF,每个 VF 分配给一个 VM,性能接近原生。

🔔中断虚拟化概述

如何让 VM 正确接收和处理中断
中断虚拟化

📡 中断虚拟化的层次

外部中断

物理设备中断。Hypervisor 拦截后注入到对应的 VM。通过虚拟 LAPIC 投递。

虚拟中断

模拟设备(如 virtio)产生的中断。Hypervisor 直接注入到 Guest 的虚拟 LAPIC。

异常

Guest 内部的 CPU 异常(如缺页)。可以由 Guest 直接处理,或被 Hypervisor 拦截。

📡虚拟 APIC

虚拟化本地高级可编程中断控制器

🏗️ APIC 虚拟化

每个 CPU 核心有一个 LAPIC(Local APIC),负责接收和投递中断。Hypervisor 需要为每个 vCPU 虚拟一个 LAPIC。

  • Guest 写 LAPIC 寄存器 → 可能触发 VM Exit
  • Hypervisor 维护虚拟 LAPIC 状态
  • 注入中断时,设置虚拟 LAPIC 的 IRR(中断请求寄存器)
  • VM Entry 时,硬件自动检查虚拟 LAPIC 并注入中断

📬Posted Interrupts

无 VM Exit 的中断注入

🚀 原理

传统中断注入需要 VM Exit → 注入 → VM Entry,开销大。Posted Interrupts 允许直接将中断"投递"到正在运行的 vCPU,无需 VM Exit。

  • 内存中维护一个 Posted Interrupt Descriptor
  • 发送方(如设备驱动)直接写入描述符的 PIR(Posted Interrupt Request)位图
  • CPU 在 VMX Non-Root 模式下检查描述符,自动注入中断
  • 仅在目标 vCPU 不在运行时才需要 VM Exit
💡 性能提升
Posted Interrupts 将中断注入延迟从 ~2000 cycles 降低到 接近零。对网络密集型负载效果显著。

🏗️Hypervisor 架构设计

Type-1 Hypervisor 的设计模式与权衡
架构设计

📐 设计模式

宏内核 (Monolithic)

所有功能在一个地址空间。性能好但 bug 可能影响所有 VM。如 VMware ESXi。

微内核 (Microkernel)

最小内核 + 用户态服务。隔离好但 IPC 开销大。如 L4, Bao, seL4。

混合型 (Hybrid)

核心功能在内核,设备驱动可分离。如 Xen (Dom0 驱动), KVM (Linux 内核)。

🧱 典型模块

Type-1 Hypervisor 组件:
├── 启动模块        // 从 bootloader 接管硬件
├── VMX 管理        // VMXON/VMCS 初始化
├── vCPU 管理       // vCPU 创建、调度、上下文切换
├── 内存管理        // 物理页分配、EPT 管理
├── VM Exit 处理    // 各类 Exit 的处理逻辑
├── 中断管理        // 虚拟 APIC、中断注入
├── I/O 子系统      // 设备模拟、virtio 后端
├── 调度器          // vCPU 在物理 CPU 上的调度
├── Hypercall       // Guest-Hypervisor 通信接口
└── 调试/日志       // 调试支持

🚀Hypervisor 启动流程

从 bootloader 到第一个 Guest 运行

📋 启动步骤

1. Bootloader 加载
BIOS/UEFI 加载 Hypervisor 到内存。可以是裸二进制或 ELF。
2. 硬件初始化
设置 GDT、IDT、页表。初始化中断控制器 (APIC)。检测 CPU 特性。
3. 内存初始化
检测可用内存,建立物理页分配器(buddy/slab)。初始化 EPT 所需的数据结构。
4. VMX 初始化
检查 VT-x 支持。设置 CR4.VMXE。分配 VMXON region。执行 VMXON。
5. 创建 VM
分配 VMCS。配置 Guest 状态(CR0/3/4, RIP, RSP)。配置 VMX 控制字段。设置 EPT。
6. 加载 Guest
将 Guest 镜像加载到分配的物理内存。设置 Guest 的初始寄存器状态。
7. 启动 Guest
执行 VMLAUNCH。CPU 切换到 VMX Non-Root 模式。Guest 开始运行。
8. 处理 VM Exit
进入主循环。处理 Exit → VM Resume。Guest 持续运行。

⏱️VM 调度

vCPU 如何在物理 CPU 上调度

🔄 调度模型

Work-Conserving

物理 CPU 空闲时,立即运行就绪的 vCPU。不浪费 CPU 时间。大多数 Hypervisor 使用此模型。

Non-Work-Conserving

即使 CPU 空闲,也不立即运行 vCPU。用于严格的时间隔离和实时 VM。

Co-Scheduling

同一 VM 的多个 vCPU 尽量同时运行。减少锁竞争,但可能浪费 CPU。

📞Hypercall

Guest 与 Hypervisor 的通信接口

📡 机制

Hypercall 是 Guest 主动请求 Hypervisor 服务的机制,类似系统调用:

/* x86 上的 Hypercall 实现 */
/* 方式 1: VMCALL 指令 (Intel) */
/* 方式 2: VMMCALL 指令 (AMD) */
/* 方式 3: 特定 MSR 写入 */

/* Guest 中发起 Hypercall */
static inline long hypercall(int nr, long a0, long a1) {
    long ret;
    asm volatile(
        "vmcall"
        : "=a"(ret)
        : "a"(nr), "D"(a0), "S"(a1)
        : "memory"
    );
    return ret;
}

/* Hypervisor 中处理 */
case EXIT_REASON_VMCALL:
    int nr = get_guest_reg(REG_RAX);
    switch (nr) {
    case HYPERCALL_PUTCHAR:
        uart_putc(get_guest_reg(REG_RDI));
        break;
    case HYPERCALL_SHUTDOWN:
        vm_shutdown();
        break;
    }

📦热迁移 (Live Migration)

不停机地将 VM 迁移到另一台物理机

🔄 迁移流程

阶段 1: 预迁移
在目标主机上创建 VM 框架,分配资源。
阶段 2: 内存复制 (迭代)
将 Guest 内存复制到目标主机。标记脏页(EPT dirty bit)。多轮迭代,每轮只传脏页。
阶段 3: 停机切换
暂停源 VM。传输最后的脏页和 CPU 状态。在目标主机恢复 VM。停机时间通常 < 100ms。
阶段 4: 清理
释放源主机上的 VM 资源。更新网络路由。

🔐VM 逃逸与防御

虚拟化安全的核心挑战

⚠️ 常见攻击面

Hypervisor Bug

VM Exit 处理中的漏洞。如 QEMU 设备模拟的缓冲区溢出。

共享组件漏洞

共享的虚拟设备(virtio)中的 bug。如 virtio-net 的越界读写。

侧信道攻击

利用共享硬件(缓存、分支预测器)泄露信息。如 Spectre, Meltdown。

🕵️侧信道攻击

利用硬件微架构泄露信息

🎯 典型攻击

攻击利用机制防御
Spectre分支预测 + 缓存时序retpoline, IBRS
Meltdown乱序执行 + 缓存时序KPTI, 硬件修复
L1TFL1 缓存推测加载L1D flush on VM entry
MDS微架构数据采样MD_CLEAR, 调度隔离

🛡️TDX / SEV 机密计算

保护 VM 数据不被 Hypervisor 窃取

🔒 核心目标

即使 Hypervisor 被攻破,Guest 的数据仍然安全。Hypervisor 不可信

Intel TDX

Trust Domain Extensions。CPU 硬件加密 Guest 内存。Hypervisor 无法读取 TD 内存内容。

AMD SEV-SNP

Secure Encrypted Virtualization。内存加密 + 完整性保护。防止 Hypervisor 篡改。

ARM CCA

Confidential Compute Architecture。ARM 的机密计算方案。Realm VM 概念。

💻开发环境搭建

写 Type-1 Hypervisor 的工具链准备
实战 · 开始

🛠️ 工具链

# 交叉编译工具链
apt install gcc-x86-64-linux-gnu nasm qemu-system-x86

# 或使用 LLVM/Clang
apt install clang lld

# 调试工具
apt install gdb                # 源码级调试
apt install bochs              # Bochs 模拟器 (支持 VMX 调试)

# QEMU (带 VT-x 透传)
qemu-system-x86_64 \
  -cpu host,+vmx \             # 透传 VMX 给 Guest
  -enable-kvm \
  -m 512M \
  -kernel hypervisor.bin

📁 项目结构

my-hypervisor/
├── boot/
│   ├── boot.S              # 汇编入口,设置栈、GDT、IDT
│   └── linker.ld           # 链接脚本
├── core/
│   ├── vmx.c               # VMX 初始化 (VMXON/VMXOFF)
│   ├── vmcs.c              # VMCS 配置和读写
│   ├── vcpu.c              # vCPU 创建和管理
│   └── vmexit.c            # VM Exit 处理
├── mem/
│   ├── alloc.c             # 物理页分配器
│   ├── ept.c               # EPT 页表管理
│   └── mmio.c              # MMIO 处理
├── io/
│   ├── uart.c              # 串口输出 (调试)
│   ├── ioport.c            # I/O 端口处理
│   └── virtio.c            # VirtIO 后端 (可选)
├── intr/
│   ├── apic.c              # 虚拟 APIC
│   └── idt.c               # IDT 设置
├── guest/
│   ├── guest.S             # 简单的 Guest 代码
│   └── guest.bin           # Guest 二进制镜像
├── Makefile
└── run.sh                  # QEMU 启动脚本

⚙️VMX 初始化

第一步:启用 VT-x 硬件虚拟化

📋 初始化步骤

/* 1. 检查 CPU 是否支持 VMX */
uint32_t eax, ebx, ecx, edx;
cpuid(1, &eax, &ebx, &ecx, &edx);
if (!(ecx & CPUID_VMX)) {
    panic("VT-x not supported");
}

/* 2. 检查 MSR 是否启用 VMX outside SMX */
uint64_t feature = rdmsr(IA32_FEATURE_CONTROL);
if (!(feature & FEATURE_CONTROL_VMXON_OUTSIDE_SMX)) {
    // 需要在 BIOS 中启用 VT-x
    panic("VT-x locked by BIOS");
}

/* 3. 设置 CR4.VMXE = 1 */
uint64_t cr4 = readcr4();
writecr4(cr4 | CR4_VMXE);

/* 4. 分配 VMXON region (4KB, 物理地址 4KB 对齐) */
uint64_t vmxon_pa = alloc_page();   // 物理页
uint32_t *vmxon = (uint32_t *)phys_to_virt(vmxon_pa);
vmxon[0] = rdmsr(IA32_VMX_BASIC) & 0x7FFFFFFF;  // Revision ID

/* 5. 执行 VMXON */
uint64_t ret = vmxon(vmxon_pa);
if (ret != 0) {
    panic("VMXON failed: %lu", ret);
}
// CPU 现在处于 VMX Root 操作模式!

⚠️ 关键注意点

🔴 退出 VMX
在 Hypervisor 关机或卸载前,必须执行 VMXOFF。否则 CPU 会一直处于 VMX 模式,可能导致系统不稳定。

📋VMCS 配置

配置 VMCS 的各个字段

🔧 VMCS 初始化代码

/* 1. 分配 VMCS region (4KB, 4字节对齐) */
uint64_t vmcs_pa = alloc_page();
uint32_t *vmcs = (uint32_t *)phys_to_virt(vmcs_pa);
vmcs[0] = rdmsr(IA32_VMX_BASIC) & 0x7FFFFFFF;

/* 2. 清除 VMCS */
vmclear(vmcs_pa);

/* 3. 加载 VMCS */
vmptrld(vmcs_pa);

/* 4. 配置 Host State (VM Exit 后 CPU 回到的状态) */
vmwrite(VMCS_HOST_CS, read_cs());
vmwrite(VMCS_HOST_SS, read_ss());
vmwrite(VMCS_HOST_DS, read_ds());
vmwrite(VMCS_HOST_ES, read_es());
vmwrite(VMCS_HOST_FS, read_fs());
vmwrite(VMCS_HOST_GS, read_gs());
vmwrite(VMCS_HOST_TR, read_tr());
vmwrite(VMCS_HOST_GDTR, read_gdtr());
vmwrite(VMCS_HOST_IDTR, read_idtr());
vmwrite(VMCS_HOST_CR0, readcr0());
vmwrite(VMCS_HOST_CR3, readcr3());
vmwrite(VMCS_HOST_CR4, readcr4());
vmwrite(VMCS_HOST_RSP, (uint64_t)get_stack_top());
vmwrite(VMCS_HOST_RIP, (uint64_t)vmexit_handler);

/* 5. 配置 Guest State (VM Entry 后 Guest 看到的状态) */
vmwrite(VMCS_GUEST_CS, GUEST_CS_SELECTOR);
vmwrite(VMCS_GUEST_SS, GUEST_SS_SELECTOR);
vmwrite(VMCS_GUEST_DS, GUEST_DS_SELECTOR);
vmwrite(VMCS_GUEST_ES, GUEST_ES_SELECTOR);
vmwrite(VMCS_GUEST_CR0, CR0_NE | CR0_ET | CR0_PE);
vmwrite(VMCS_GUEST_CR3, guest_cr3);   // Guest 页表
vmwrite(VMCS_GUEST_CR4, CR4_VMXE);
vmwrite(VMCS_GUEST_RIP, GUEST_ENTRY_POINT);
vmwrite(VMCS_GUEST_RSP, GUEST_STACK_TOP);
vmwrite(VMCS_GUEST_RFLAGS, 0x2);  // 固定为 1 的位

/* 6. 配置 VMX 控制字段 */
vmwrite(VMCS_PIN_BASED, PIN_EXT_INT);
vmwrite(VMCS_PROC_BASED,
    CPU_HLT | CPU_CR3_LOAD | CPU_CR3_STORE |
    CPU_IO | CPU_CPUID | CPU_SECONDARY);
vmwrite(VMCS_PROC_BASED2, CPU2_EPT | CPU2_VPID);
vmwrite(VMCS_EPTP, make_eptp(ept_pml4_pa));
vmwrite(VMCS_VPID, 1);  // 每个 vCPU 唯一

🎯Guest 启动

VMLAUNCH:首次进入 Guest 世界

🚀 启动代码

/* 准备 Guest 镜像 */
// 将 guest.bin 加载到 Guest 的物理地址空间
load_guest_image("guest.bin", GUEST_LOAD_ADDR);

/* 建立 EPT 映射 */
// GPA 0x0 ~ 0x100000 → HPA (分配的物理页)
ept_map(ept_root, 0x0, va_to_pa(guest_mem), 0x100000,
    EPT_READ | EPT_WRITE | EPT_EXEC);

/* VMLAUNCH */
uint64_t ret = vmlaunch();
if (ret != 0) {
    // VMLAUNCH 失败,检查 VMCS 错误
    uint64_t err = vmread(VMCS_VM_INSTRUCTION_ERROR);
    panic("VMLAUNCH failed: error %lu", err);
}
// 如果成功,CPU 现在在执行 Guest 代码
// 不会从这里返回 —— 直到 VM Exit 才回到 vmexit_handler
💡 VMLAUNCH vs VMRESUME
VMLAUNCH 只能用一次(首次启动)。之后每次 VM Exit 后都用 VMRESUME 恢复 Guest。如果用错了会触发 VM Fail。

🔄Exit 处理循环

Hypervisor 的核心:处理每一种 VM Exit

🔧 常见 Exit 处理

/* CPUID 处理 — 最简单的 Exit */
void handle_cpuid() {
    uint32_t leaf = get_guest_reg(REG_RAX);
    uint32_t sub  = get_guest_reg(REG_RCX);

    uint32_t eax, ebx, ecx, edx;
    cpuid_count(leaf, sub, &eax, &ebx, &ecx, &edx);

    // 可以修改返回值来隐藏某些特性
    set_guest_reg(REG_RAX, eax);
    set_guest_reg(REG_RBX, ebx);
    set_guest_reg(REG_RCX, ecx);
    set_guest_reg(REG_RDX, edx);
}

/* HLT 处理 — 暂停 vCPU */
void handle_hlt() {
    // Guest 执行了 HLT
    // 1. 标记 vCPU 为 halted
    current_vcpu->halted = true;
    // 2. 等待中断唤醒
    // 3. 调度器可以运行其他 vCPU
}

/* MSR 处理 */
void handle_msr(bool is_write) {
    uint32_t msr = get_guest_reg(REG_RCX);
    if (is_write) {
        uint64_t val = get_guest_reg(REG_RAX)
                     | (get_guest_reg(REG_RDX) << 32);
        // 处理或记录 MSR 写入
        vcpu_set_msr(current_vcpu, msr, val);
    } else {
        uint64_t val = vcpu_get_msr(current_vcpu, msr);
        set_guest_reg(REG_RAX, val & 0xFFFFFFFF);
        set_guest_reg(REG_RDX, val >> 32);
    }
}

🗺️EPT 内存映射

为 Guest 建立 GPA → HPA 映射

📊 EPT 页表构建

/* EPT 页表项结构 */
typedef struct {
    uint64_t read      : 1;   // 位 0: 允许读
    uint64_t write     : 1;   // 位 1: 允许写
    uint64_t execute   : 1;   // 位 2: 允许执行
    uint64_t mem_type  : 3;   // 位 3-5: 内存类型 (0=UC, 6=WB)
    uint64_t accessed  : 1;   // 位 5: 访问位 (硬件设置)
    uint64_t dirty     : 1;   // 位 6: 脏位 (硬件设置)
    uint64_t rsvd      : 1;   // 位 7
    uint64_t addr      : 40;  // 位 12-51: 物理地址
    uint64_t rsvd2     : 12;  // 位 52-63
} ept_entry_t;

/* 建立 EPT 映射 */
void ept_map(uint64_t *pml4, uint64_t gpa, uint64_t hpa,
             uint64_t size, uint64_t flags) {
    // 4KB 粒度映射
    for (uint64_t offset = 0; offset < size; offset += 0x1000) {
        uint64_t g = gpa + offset;
        uint64_t h = hpa + offset;

        // 查找或创建页表项
        ept_entry_t *entry = ept_walk_create(pml4, g);
        entry->addr = h >> 12;
        entry->read = !!(flags & EPT_READ);
        entry->write = !!(flags & EPT_WRITE);
        entry->execute = !!(flags & EPT_EXEC);
        entry->mem_type = 6;  // Write-Back
    }
}

🔌I/O 处理

处理 Guest 的 I/O 端口访问

🔧 I/O Exit 处理

void handle_io_exit() {
    // Exit Qualification 包含 I/O 信息
    uint64_t qual = vmread(VMCS_EXIT_QUALIFICATION);

    uint32_t size   = (qual & 0x7) + 1;        // 1/2/4 字节
    bool     is_in  = !!(qual & 0x8);          // IN=true
    bool     is_str = !!(qual & 0x10);         // 字符串 I/O
    uint16_t port   = (qual >> 16) & 0xFFFF;  // 端口号

    if (is_in) {
        uint32_t val = ioport_read(port, size);
        set_guest_reg(REG_RAX, val);
    } else {
        uint32_t val = get_guest_reg(REG_RAX);
        ioport_write(port, val, size);
    }
}

🔔中断处理

中断注入和虚拟 APIC

📡 中断注入

/* 注入中断到 Guest */
void inject_interrupt(uint8_t vector) {
    uint32_t info = 0;
    info |= vector;                    // 中断向量号
    info |= (0 << 8);                // 类型: 外部中断
    info |= (1 << 31);               // Valid: 有效

    vmwrite(VMCS_ENTRY_INTR_INFO, info);
    // VM Entry 时硬件自动注入此中断
}

🐛调试技巧

Hypervisor 开发的调试方法

🛠️ 调试方法

串口输出 (UART)

最可靠的调试方式。直接写 I/O 端口 0x3F8。QEMU 会输出到终端。不受 VM Exit 影响。

GDB + QEMU

qemu -s -S 启动后用 gdb target remote :1234 连接。可调试 Hypervisor 代码。

VMCS Dump

出错时 dump 所有 VMCS 字段。检查 Exit Reason、Exit Qualification、Guest RIP。

Bochs

Bochs 模拟器支持 VMX 指令模拟。适合在不支持 VT-x 的机器上开发。

📚参考项目

学习和参考的开源 Hypervisor 项目

📖 推荐项目

Bao Hypervisor

轻量级 Type-1 Hypervisor,代码简洁,适合学习。支持 ARM 和 RISC-V。GitHub: bao-project/bao-hypervisor

KVM (Linux 内核)

最成熟的开源虚拟化。代码在内核源码 arch/x86/kvm/。复杂但功能完整。

Xen

经典的 Type-1 Hypervisor。代码结构清晰,文档丰富。适合学习虚拟化架构。

SimpleSVM (教学)

极简的 AMD-V 教学 Hypervisor。只有几百行代码,展示核心概念。

seL4

经过形式化验证的微内核。支持虚拟化扩展。适合学习安全 Hypervisor 设计。

Intel SDM

Intel 手册 Vol 3 Chapter 23-33。VMX 的权威参考。必读!

📅 30 天学习计划

第 1-3 天: 基础
虚拟化概述、x86 特权级、VT-x 基础概念
第 4-7 天: CPU 虚拟化
VMCS 结构、VM Entry/Exit、VMX 指令
第 8-10 天: 搭建环境
工具链、QEMU、编写第一个 bootloader
第 11-14 天: VMX 初始化
VMXON、VMCS 配置、VMLAUNCH
第 15-18 天: Exit 处理
CPUID、HLT、I/O、MSR 处理
第 19-22 天: 内存虚拟化
EPT 页表、EPT Violation 处理
第 23-25 天: I/O 虚拟化
设备模拟、VirtIO 基础
第 26-28 天: 中断虚拟化
虚拟 APIC、中断注入
第 29-30 天: 调试与完善
运行真实 Guest、调试、性能优化