Wiki LogoWiki - The Power of Many

M7: 硬件虚拟化与基础设施

概述

现代操作系统不仅直接运行在硬件上, 更多时候作为客户机 (Guest) 运行在 Hypervisor 之上, 或作为宿主机 (Host) 管理容器. 本模块深入硬件辅助虚拟化技术 (VT-x/EPT), I/O 虚拟化协议 (VirtIO) 以及容器底层的隔离机制.


模块知识结构


1. 虚拟化基础架构

1.1 Hypervisor 类型

类型架构特点代表实现
Type-1 (Bare-metal)直接运行在硬件上高性能, 高隔离Xen, VMware ESXi, Hyper-V
Type-2 (Hosted)运行在 OS 之上开发方便, 效率较低VirtualBox, VMware Workstation
KVM (Hybrid)作为内核模块Linux 既是 OS 又是 HypervisorLinux KVM

1.2 虚拟化的核心挑战

操作系统设计时假设独占硬件资源, 虚拟化需要解决以下问题:

挑战问题描述解决方案
特权指令Guest 执行 Ring 0 指令会影响宿主机硬件辅助 (VT-x) 或二进制翻译
地址空间Guest 物理地址与真实物理地址不同影子页表或 EPT
设备访问Guest 直接访问硬件会冲突设备模拟或直通 (SR-IOV)
中断传递物理中断需要路由到正确的 Guest虚拟中断控制器 (vAPIC)

1.3 Popek-Goldberg 虚拟化定理

1973 年提出的形式化虚拟化理论:

  • 等价性: 虚拟机行为与物理机等价 (除资源和时序外)
  • 资源控制: VMM 完全控制虚拟化资源
  • 高效性: 大部分指令直接在硬件执行

x86 的挑战: 某些敏感指令 (如 POPF) 在非特权模式下不触发陷阱, 违反了经典虚拟化条件. VT-x 通过引入新的 CPU 模式解决这一问题.


2. CPU 虚拟化: VT-x / VMX

Intel VT-x (AMD 对应 AMD-V) 引入了 VMX (Virtual Machine Extensions) 指令集, 从硬件层面支持虚拟化.

2.1 VMX 运行模式

  • VMX Root Operation: 宿主机 (Host/KVM) 运行模式, 拥有完整特权
  • VMX Non-Root Operation: 客户机 (Guest) 运行模式, 特权指令触发 VM Exit

两种模式各自包含完整的 Ring 0-3, 因此 Guest 内核可以正常运行在 Guest 的 Ring 0, 而不需要修改代码.

2.2 VMX 生命周期

2.3 VMCS (Virtual Machine Control Structure)

VMCS 是每个 vCPU 的核心数据结构, 大小为 4KB, 包含六个逻辑区域:

区域内容作用
Guest-State Area所有通用寄存器, 段寄存器, CR0/CR3/CR4, EFLAGS, RIPVM Entry 时加载, VM Exit 时保存
Host-State Area仅保存切换所需的最小状态VM Exit 时加载
VM-Execution Control控制哪些事件触发 VM Exit如: CR 访问拦截, I/O 拦截
VM-Exit Control控制 VM Exit 时的行为如: 是否保存 IA32_PAT
VM-Entry Control控制 VM Entry 时的行为如: 是否加载 IA32_EFER
VM-Exit InformationExit 原因和附加信息只读, 供 Hypervisor 分析

VMCS 关键字段

// Guest 状态
VMCS_GUEST_RIP           // 指令指针
VMCS_GUEST_RSP           // 栈指针
VMCS_GUEST_CR3           // 页表基址
VMCS_GUEST_INTERRUPTIBILITY  // 中断状态

// 执行控制
VMCS_PIN_BASED_CONTROLS  // NMI, 外部中断
VMCS_CPU_BASED_CONTROLS  // I/O, MSR, CPUID
VMCS_EXCEPTION_BITMAP    // 哪些异常触发 Exit

// Exit 信息
VMCS_EXIT_REASON         // 退出原因代码
VMCS_EXIT_QUALIFICATION  // 附加信息 (如 I/O 端口号)
VMCS_GUEST_LINEAR_ADDR   // 引发 Exit 的线性地址

2.4 VM Exit 分类

类型触发条件示例
无条件 Exit某些指令总是触发CPUID, INVD, VMCALL
条件 Exit由 VMCS 控制位决定CR 访问, I/O, MSR, 中断
异常 ExitException Bitmap 中的位置 1#PF, #GP (按需配置)

VM Exit 开销

VM Exit 是虚拟化最大的性能开销来源, 典型开销:

阶段周期数 (估算)
保存 Guest 状态到 VMCS~200-400 cycles
加载 Host 状态~300-500 cycles
TLB 刷新 (无 VPID)~1000+ cycles
Hypervisor 处理逻辑变化很大
总计~2000-10000 cycles

优化策略:

  1. 减少不必要的 Exit (调整 VMCS 控制位)
  2. 启用 VPID 避免 TLB 全刷新
  3. 合并多个操作减少 Exit 次数

2.5 嵌套虚拟化 (Nested Virtualization)

在虚拟机内运行虚拟机 (如 KVM on KVM), 需要 VMCS Shadowing:

  • L1 的 VMCS 操作被 L0 拦截和模拟
  • Intel VMCS Shadowing 允许 L1 部分直接操作硬件 VMCS

2.6 KVM 内核源码深入分析

KVM (Kernel-based Virtual Machine) 是 Linux 内核中的 Type-1 Hypervisor 实现. 理解其核心代码路径是掌握虚拟化原理的关键.

核心数据结构

// arch/x86/include/asm/kvm_host.h

struct kvm {
    struct mm_struct *mm;                // 宿主机地址空间
    struct kvm_memslots __rcu *memslots[KVM_ADDRESS_SPACE_NUM];
    struct kvm_vcpu *vcpus[KVM_MAX_VCPUS]; // vCPU 数组
    atomic_t online_vcpus;
    
    struct kvm_io_bus __rcu *buses[KVM_NR_BUSES];  // I/O 总线模拟
    struct kvm_vm_stat stat;             // 统计信息
    struct kvm_arch arch;                // 架构相关 (EPT, APIC 等)
    
    refcount_t users_count;
    struct mutex lock;
    spinlock_t mmu_lock;                 // 保护 EPT/影子页表
    // ...
};

struct kvm_vcpu {
    struct kvm *kvm;                     // 所属 VM
    int vcpu_id;
    int cpu;                             // 当前运行的物理 CPU
    
    struct kvm_run *run;                 // 与用户空间共享的内存
    struct kvm_vcpu_arch arch;           // 架构状态 (寄存器, VMCS 等)
    
    struct task_struct *task;            // 对应的 QEMU 线程
    bool preempted;
    bool ready;
    
    struct kvm_dirty_ring dirty_ring;    // 脏页追踪
    // ...
};

vCPU 运行核心路径

// virt/kvm/kvm_main.c
// 用户空间通过 ioctl(KVM_RUN) 触发

static long kvm_vcpu_ioctl(struct file *filp, unsigned int ioctl, ...)
{
    switch (ioctl) {
    case KVM_RUN:
        r = kvm_arch_vcpu_ioctl_run(vcpu);  // 进入架构相关代码
        break;
    // ...
    }
}

// arch/x86/kvm/x86.c
int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
{
    vcpu_load(vcpu);                     // 1. 加载 vCPU 上下文
    
    for (;;) {
        if (kvm_vcpu_running(vcpu)) {
            r = vcpu_run(vcpu);          // 2. 核心运行循环
        } else {
            r = vcpu_block(vcpu);        // vCPU 处于 HLT 等待
        }
        
        if (r <= 0) break;               // 需要返回用户空间
        
        if (signal_pending(current)) {   // 3. 处理信号
            r = -EINTR;
            vcpu->run->exit_reason = KVM_EXIT_INTR;
            break;
        }
    }
    
    vcpu_put(vcpu);
    return r;
}

VM Entry/Exit 核心流程

// arch/x86/kvm/vmx/vmx.c
// Intel VMX 实现的核心入口

static int vmx_vcpu_run(struct kvm_vcpu *vcpu)
{
    struct vcpu_vmx *vmx = to_vmx(vcpu);
    
    // 1. 准备 VMCS
    vmx_vcpu_load_vmcs(vcpu, cpu);
    
    // 2. 同步 Guest 状态到 VMCS
    vmx_sync_dirty_debug_regs(vcpu);
    vmx_load_guest_msr(vmx);
    
    // 3. 执行 VM Entry
    vmx->exit_reason.full = __vmx_vcpu_run(vmx, ...);  // 汇编入口
    
    // 4. VM Exit 后回到这里
    vmx_vcpu_enter_exit(vcpu, vmx);
    
    // 5. 读取 Exit 原因并处理
    if (vmx->exit_reason.basic == EXIT_REASON_EXCEPTION_NMI)
        kvm_set_exception(vcpu, ...);
    
    return vmx_complete_atomic_exit(vcpu);
}

// arch/x86/kvm/vmx/vmenter.S (关键汇编)
SYM_FUNC_START(__vmx_vcpu_run)
    // 保存 Host 通用寄存器
    push    %rbp
    push    %rbx
    push    %r12-r15
    
    // 加载 Guest 通用寄存器
    mov     VCPU_RBX(%rdi), %rbx
    mov     VCPU_RCX(%rdi), %rcx
    // ... 其他寄存器
    
    // 执行 VMLAUNCH 或 VMRESUME
    vmlaunch  // 或 vmresume
    
    // --- Guest 在此执行 ---
    // VM Exit 后返回到这里
    
    // 保存 Guest 寄存器到 VMCS
    mov     %rbx, VCPU_RBX(%rdi)
    // ...
    
    // 恢复 Host 寄存器
    pop     %r15-%r12
    ret
SYM_FUNC_END(__vmx_vcpu_run)

VM Exit 处理分发

// arch/x86/kvm/vmx/vmx.c

static int (*kvm_vmx_exit_handlers[])(struct kvm_vcpu *) = {
    [EXIT_REASON_EXCEPTION_NMI]     = handle_exception_nmi,
    [EXIT_REASON_EXTERNAL_INTERRUPT]= handle_external_interrupt,
    [EXIT_REASON_CPUID]             = kvm_emulate_cpuid,
    [EXIT_REASON_HLT]               = kvm_emulate_halt,
    [EXIT_REASON_IO_INSTRUCTION]    = handle_io,
    [EXIT_REASON_CR_ACCESS]         = handle_cr,
    [EXIT_REASON_MSR_READ]          = kvm_emulate_rdmsr,
    [EXIT_REASON_MSR_WRITE]         = kvm_emulate_wrmsr,
    [EXIT_REASON_EPT_VIOLATION]     = handle_ept_violation,
    [EXIT_REASON_EPT_MISCONFIG]     = handle_ept_misconfig,
    [EXIT_REASON_VMCALL]            = kvm_emulate_hypercall,
    // ... 更多处理函数
};

static int __vmx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t exit_fastpath)
{
    struct vcpu_vmx *vmx = to_vmx(vcpu);
    u32 exit_reason = vmx->exit_reason.basic;
    
    // Fast path: 某些 Exit 可快速处理
    if (exit_reason < kvm_vmx_max_exit_handlers &&
        kvm_vmx_exit_handlers[exit_reason])
        return kvm_vmx_exit_handlers[exit_reason](vcpu);
    
    // Unknown exit - 返回用户空间
    vcpu->run->exit_reason = KVM_EXIT_UNKNOWN;
    return 0;
}

EPT Violation 处理

// arch/x86/kvm/vmx/vmx.c

static int handle_ept_violation(struct kvm_vcpu *vcpu)
{
    gpa_t gpa = vmcs_read64(GUEST_PHYSICAL_ADDRESS);
    u64 error_code = vmcs_read64(EXIT_QUALIFICATION);
    
    // 判断违规类型
    bool is_write = error_code & EPT_VIOLATION_WRITE;
    bool is_fetch = error_code & EPT_VIOLATION_INSTR_FETCH;
    
    // 调用通用 page fault 处理
    return kvm_mmu_page_fault(vcpu, gpa, error_code, NULL, 0);
}

// arch/x86/kvm/mmu/mmu.c
int kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t gpa, u64 error_code, ...)
{
    // 1. 检查是否 MMIO 区域
    if (is_mmio_fault)
        return handle_mmio_page_fault(vcpu, gpa, ...);
    
    // 2. 分配 EPT 页表并建立映射
    r = kvm_mmu_do_page_fault(vcpu, gpa, error_code, ...);
    
    // 3. 成功则重新进入 Guest
    return r;
}

KVM 性能统计

# 查看 vCPU 统计信息
cat /sys/kernel/debug/kvm/*/vcpu0/stat

# 关键指标含义
exits        # VM Exit 总次数
halt_exits   # HLT 指令触发的 Exit
io_exits     # I/O 指令触发的 Exit
mmio_exits   # MMIO 访问触发的 Exit
irq_exits    # 中断注入触发的 Exit
fpu_reload   # FPU 状态重载次数

2.7 多架构虚拟化支持 (Multi-Architecture Support)

虽然 KVM 的核心逻辑是通用的, 但底层硬件交互在不同架构上差异巨大.

2.7.1 AMD SVM (Secure Virtual Machine)

与 Intel VMX 不同, AMD 使用 VMCB (Virtual Machine Control Block) 单页结构, 且指令集更精简 (VMRUN).

VMCB 结构:

// arch/x86/include/asm/svm.h

struct vmcb_control_area {
    u32 intercept_cr;        // CR 读写拦截
    u32 intercept_dr;        // DR 读写拦截
    u32 intercept_exceptions;// 异常拦截
    u64 tsc_offset;          // TSC 偏移
    u8  tlb_control;         // TLB 刷新控制
    // ...
};

struct vmcb_save_area {
    struct vmcb_seg es, cs, ss, ds; // 段寄存器
    u64 rip, rsp, rax;              // 通用寄存器
    u64 cr0, cr2, cr3, cr4;         // 控制寄存器
    // ...
};

struct vmcb {
    struct vmcb_control_area control;
    struct vmcb_save_area save;
};

AMD vCPU 运行循环 (svm_vcpu_run):

// arch/x86/kvm/svm/svm.c

static void svm_vcpu_run(struct kvm_vcpu *vcpu)
{
    struct vcpu_svm *svm = to_svm(vcpu);
    
    // 1. 将 Guest 状态写入 VMCB
    svm->vmcb->save.rip = vcpu->arch.regs[VCPU_REGS_RIP];
    
    // 2. 汇编入口: 核心指令 clgi; vmload; vmrun; vmsave; stgi
    __svm_vcpu_run(svm->vmcb_pa, svm->host_save_user_msrs);
    
    // 3. 从 VMCB 读取 Guest 状态
    vcpu->arch.regs[VCPU_REGS_RIP] = svm->vmcb->save.rip;
    
    // 4. 处理 Exit
    handle_exit(vcpu);
}

2.7.2 ARM64 Virtualization (EL2)

ARMv8 引入了 Exception Levels (EL), 其中 EL2 是专门的 Hypervisor 模式. Linux KVM 在 ARM 上通常使用 VHE (Virtualization Host Extensions) 模式, 允许 Host 内核直接运行在 EL2.

关键系统寄存器:

  • HCR_EL2 (Hypervisor Configuration Register): 控制虚拟化行为, trap 配置.
  • VTTBR_EL2 (Virtualization Translation Table Base Register): 指向 Stage 2 页表 (类似 EPT).

ARM vCPU 进入流程:

// arch/arm64/kvm/hyp/vhe/switch.c

static void __kvm_vcpu_run_vhe(struct kvm_vcpu *vcpu)
{
    struct kvm_cpu_context *host_ctxt;
    struct kvm_cpu_context *guest_ctxt;
    
    host_ctxt = &this_cpu_ptr(&kvm_host_data)->host_ctxt;
    guest_ctxt = &vcpu->arch.ctxt;
    
    // 1. 保存 Host 寄存器 (Callee-saved)
    sysreg_save_host_state_vhe(host_ctxt);
    
    // 2. 加载 Guest 寄存器 & 配置 HCR_EL2
    sysreg_restore_guest_state_vhe(guest_ctxt);
    __activate_traps(vcpu);
    
    // 3. 进入 Guest (ERET 到 EL1)
    // ARM 使用异常返回 (ERET) 指令降级到 Guest
    __guest_enter(vcpu);
    
    // --- Guest 执行 ---
    
    // 4. Guest 退出 (Trap 到 EL2)
    __guest_exit(vcpu);
    
    // 5. 恢复 Host 状态
    sysreg_save_guest_state_vhe(guest_ctxt);
    sysreg_restore_host_state_vhe(host_ctxt);
}

对比 Intel VT-x:

特性Intel VT-xARMv8 (VHE)
模式VMX Root / Non-RootEL2 (Host) / EL1 (Guest)
切换VMLAUNCH / VMEXITERET / Exception
内存EPT (Extended Page Tables)Stage 2 Translation
I/OPIO / MMIO TrapMMIO Trap (System Register)

3. 内存虚拟化: EPT / NPT

3.1 地址转换层次

虚拟化引入了三级地址空间:

地址类型缩写含义
Guest Virtual AddressGVAGuest 进程使用的虚拟地址
Guest Physical AddressGPAGuest 内核看到的 "物理" 地址
Host Physical AddressHPA真正的物理内存地址

3.2 影子页表 vs EPT

方案工作原理优点缺点
影子页表Hypervisor 维护 GVA→HPA 的直接映射无需硬件支持每次 Guest 修改页表需同步, 开销大
EPT/NPT硬件自动完成 GPA→HPA 转换对 Guest 透明, 无需同步页表 Walk 次数增加

3.3 EPT 页表结构

EPT 采用与普通页表相同的四级结构:

EPT Entry 格式(64-bit)

Bit 0: Read access
Bit 1: Write access  
Bit 2: Execute access (可配置 User/Supervisor)
Bit 7: Large page (2MB/1GB)
Bit 8: Accessed (硬件置位)
Bit 9: Dirty (硬件置位, 需启用)
Bits 12-51: 下一级页表物理地址 / 最终 HPA

3.4 EPT Violation vs EPT Misconfiguration

类型触发条件处理方式
EPT Violation访问权限不足分配内存, 更新 EPT, 注入异常
EPT MisconfigurationEPT Entry 配置错误严重错误, 通常导致 Guest 崩溃

3.5 大页支持

EPT 支持 2MB 和 1GB 大页, 减少页表级数:

页大小EPT 级数TLB 条目覆盖
4KB4 级4KB
2MB3 级2MB
1GB2 级1GB

注意: Guest 内部使用大页与 EPT 使用大页是独立的. 最佳情况是两者都使用大页.

3.6 TLB 管理: VPID 与 PCID

技术作用配置
VPID为每个 vCPU 分配 ID, 避免 VM 切换时刷新 TLBVMCS Execution Control
PCID为每个进程分配 ID, 避免上下文切换刷新 TLBCR4.PCIDE 位

VPID 是虚拟化性能的关键优化, 典型配置:

Control.VPID = 1  // 启用
VMCS.VPID = <unique_id>  // 每个 vCPU 唯一

4. I/O 虚拟化

4.1 I/O 虚拟化方案对比

方案原理性能隔离性
全模拟QEMU 软件模拟设备最低最高
半虚拟化 (VirtIO)Guest 知道自己在虚拟环境, 使用优化协议较高
设备直通 (Passthrough)Guest 直接控制物理设备接近裸机设备独占
SR-IOV硬件支持的虚拟功能 (VF) 直通接近裸机硬件隔离

4.2 VirtIO 架构

VirtIO 是 OASIS 标准化的半虚拟化协议, 已成为 Linux 虚拟化的事实标准.

VirtIO 设备类型

设备用途后端
virtio-net网络TAP, vhost-net, DPDK
virtio-blk块存储文件, LVM, Ceph
virtio-scsiSCSI 存储支持多 LUN
virtio-fs文件系统共享virtiofsd (DAX)
virtio-gpu图形加速3D 加速
virtio-balloon内存调整动态内存回收

4.3 Virtqueue 数据结构

每个 Virtqueue 由三部分组成:

Descriptor Table Entry

struct vring_desc {
    __le64 addr;    // 缓冲区物理地址 (GPA)
    __le32 len;     // 缓冲区长度
    __le16 flags;   // VRING_DESC_F_NEXT, _WRITE, _INDIRECT
    __le16 next;    // 链表中下一个描述符索引
};

Available Ring (Guest → Host)

struct vring_avail {
    __le16 flags;           // 是否禁用通知
    __le16 idx;             // 下一个可用位置
    __le16 ring[QSIZE];     // 描述符链头索引
    __le16 used_event;      // 事件通知阈值 (可选)
};

Used Ring (Host → Guest)

struct vring_used {
    __le16 flags;
    __le16 idx;
    struct vring_used_elem ring[QSIZE];
    __le16 avail_event;
};

struct vring_used_elem {
    __le32 id;     // 描述符链头索引
    __le32 len;    // 写入的字节数
};

4.4 VirtIO 通知机制

方向机制说明
Guest → Hostvirtio_notify()写 PCI BAR 或 eventfd
Host → Guest中断注入通过 irqfd 或 VMCS 中断窗口

vhost-net 优化: 将 VirtIO 后端移入内核, 避免用户态/内核态切换.

4.5 IOMMU (VT-d / AMD-Vi)

IOMMU 允许设备 DMA 时使用虚拟地址, 实现:

  1. 设备隔离: 防止设备越界访问
  2. 地址重映射: 设备看到的地址与物理地址分离
  3. 中断重映射: 防止设备伪造中断

4.6 SR-IOV (Single Root I/O Virtualization)

SR-IOV 允许单个物理设备表现为多个虚拟设备:

概念说明
PF (Physical Function)物理设备, 拥有完整功能
VF (Virtual Function)轻量级虚拟设备, 可直通给 Guest
# 启用 VF
echo 4 > /sys/class/net/eth0/device/sriov_numvfs

# 查看 VF
lspci | grep "Virtual Function"

5. 容器底层机制

容器不是虚拟机, 而是操作系统提供的进程隔离机制.

5.1 容器 vs 虚拟机

特性虚拟机容器
隔离级别硬件级 (独立内核)进程级 (共享内核)
启动时间秒 ~ 分钟毫秒 ~ 秒
资源开销高 (独立 OS 实例)低 (共享内核)
安全边界较弱 (依赖内核)

5.2 Linux Namespaces

Namespace 提供进程视图隔离:

Namespace系统调用标志隔离对象引入版本
MountCLONE_NEWNS挂载点2.4.19
UTSCLONE_NEWUTS主机名, 域名2.6.19
IPCCLONE_NEWIPCIPC 对象2.6.19
PIDCLONE_NEWPID进程 ID2.6.24
NetworkCLONE_NEWNET网络设备, 协议栈2.6.29
UserCLONE_NEWUSERUID/GID 映射3.8
CgroupCLONE_NEWCGROUPCgroup 根目录4.6
TimeCLONE_NEWTIME时钟5.6

Namespace 操作

// 创建新 namespace
int fd = clone(child_fn, stack, CLONE_NEWPID | CLONE_NEWNS, NULL);

// 加入已有 namespace
int ns_fd = open("/proc/1234/ns/pid", O_RDONLY);
setns(ns_fd, CLONE_NEWPID);

// 取消共享当前 namespace
unshare(CLONE_NEWNET);

5.3 Cgroups v2

Cgroups 提供资源限制和统计:

核心控制器

控制器接口文件功能
cpucpu.max, cpu.weightCPU 时间配额
memorymemory.max, memory.high内存限制, OOM 控制
ioio.max, io.weight块 I/O 带宽
pidspids.max进程数限制
cpusetcpuset.cpus, cpuset.memsCPU/NUMA 绑定

配置示例

# 创建 cgroup
mkdir /sys/fs/cgroup/container1

# 限制 CPU 为 20% (20ms/100ms)
echo "20000 100000" > /sys/fs/cgroup/container1/cpu.max

# 限制内存为 512MB
echo "536870912" > /sys/fs/cgroup/container1/memory.max

# 添加进程
echo $PID > /sys/fs/cgroup/container1/cgroup.procs

5.4 OverlayFS (联合文件系统)

容器镜像使用分层存储:

# 挂载 OverlayFS
mount -t overlay overlay \
    -o lowerdir=/images/layer1:/images/layer2,\
       upperdir=/container/upper,\
       workdir=/container/work \
    /container/merged

5.5 OCI 规范与 runc

OCI (Open Container Initiative) 定义了容器标准:

规范内容
Runtime Spec容器生命周期, 配置格式
Image Spec镜像格式, 分发协议
Distribution SpecRegistry API

config.json 核心结构

{
    "ociVersion": "1.0.0",
    "process": {
        "args": ["/bin/sh"],
        "env": ["PATH=/usr/bin"],
        "cwd": "/"
    },
    "root": {
        "path": "rootfs",
        "readonly": false
    },
    "linux": {
        "namespaces": [
            {"type": "pid"},
            {"type": "network"},
            {"type": "mount"}
        ],
        "resources": {
            "memory": {"limit": 536870912}
        }
    }
}

5.6 容器运行时架构

组件职责
containerd镜像管理, 容器生命周期
containerd-shim托管容器进程, 允许 containerd 重启
runc创建 namespace/cgroup, 执行容器进程

6. 安全容器

6.1 传统容器的安全问题

共享内核带来的风险:

风险描述
内核漏洞容器可利用内核漏洞逃逸
系统调用攻击面所有 syscall 对容器可用
/proc, /sys 泄露敏感信息可能暴露

6.2 Kata Containers

Kata Containers 为每个容器启动轻量级虚拟机:

关键技术

技术作用
轻量级 VMMFirecracker, Cloud-Hypervisor
共享文件系统virtio-fs + DAX (零拷贝)
最小化 Guest 内核裁剪内核, 快速启动

6.3 Firecracker MicroVM

Amazon 开发的专用 VMM:

特性数值
内存开销~5MB
启动时间~125ms
代码行数~50K (Rust)
设备支持仅 virtio-net, virtio-blk, serial
# 创建 MicroVM
curl --unix-socket /tmp/firecracker.socket -X PUT \
    -d '{"vcpu_count": 2, "mem_size_mib": 1024}' \
    http://localhost/machine-config

6.4 gVisor (用户态内核)

Google 开发的用户态内核实现:

  • 拦截容器系统调用
  • 在用户态重新实现 Linux 内核功能
  • 减少对宿主机内核的依赖

6.5 安全容器运行时综合对比

特性runc (标准)Kata ContainersFirecrackergVisor
隔离机制Namespace + Cgroup轻量级 VMMicroVM用户态内核
内核共享共享宿主机内核独立 Guest 内核独立 Guest 内核Sentry (用户态)
启动时间~50ms~150-300ms~125ms~150ms
内存开销~5MB~30-50MB~5MB~20MB
性能损耗基准5-15%1-5%10-30%
Syscall 攻击面全部暴露VM 边界隔离VM 边界隔离受限重实现
兼容性完全兼容良好受限设备部分兼容
适用场景可信工作负载多租户, CI/CDServerless不可信代码
主要用户Docker 默认OpenStack ZunAWS LambdaGoogle Cloud Run Sandbox

选型建议:

  • 追求最强隔离: Kata Containers
  • 追求最快启动: Firecracker
  • 追求最小攻击面: gVisor
  • 追求最佳兼容性: 标准 runc + Seccomp

6.6 机密计算 (Confidential Computing)

机密计算通过硬件加密保护运行时数据, 防止云提供商和宿主机管理员访问敏感信息.

6.6.1 技术对比

技术厂商加密范围信任边界
AMD SEVAMDVM 内存加密仅 Guest 可见明文
AMD SEV-ESAMD+ 寄存器状态加密防止 VMM 读取 CPU 状态
AMD SEV-SNPAMD+ 完整性保护防止恶意修改 Guest 内存
Intel TDXIntelTD (Trust Domain) 隔离硬件隔离的可信执行环境
Intel SGXIntel飞地 (Enclave) 内存加密仅应用级保护

6.6.2 AMD SEV-SNP 架构

6.6.3 Intel TDX 架构

6.6.4 启用配置

# AMD SEV (KVM)
# 检查 SEV 支持
dmesg | grep -i sev

# 启动 SEV 虚拟机
qemu-system-x86_64 \
    -machine q35,confidential-guest-support=sev0 \
    -object sev-guest,id=sev0,policy=0x1

# Intel TDX (需要 TDX 内核)
# 检查 TDX 支持
cat /sys/module/kvm_intel/parameters/tdx

# 启动 TDX 虚拟机
qemu-system-x86_64 \
    -machine q35,confidential-guest-support=tdx \
    -object tdx-guest,id=tdx

6.6.5 远程证明 (Remote Attestation)


7. 高级虚拟化主题

7.1 vCPU 亲和性与 NUMA

# 绑定 vCPU 到物理 CPU
virsh vcpupin vm1 0 2
virsh vcpupin vm1 1 3

# 配置 NUMA 拓扑
<numatune>
    <memory mode="strict" nodeset="0"/>
</numatune>

7.2 内存气球 (Ballooning)

动态调整 Guest 内存:

  1. Host 请求 Guest 释放内存
  2. Guest 通过 virtio-balloon 驱动 "膨胀" 气球
  3. Guest 内部分配内存给气球, 从可用内存中移除
  4. Host 回收这些物理页

7.3 实时迁移 (Live Migration)

实时迁移允许在不中断服务的情况下将虚拟机从一台物理主机迁移到另一台.

7.3.1 Pre-copy 算法 (主流)

收敛条件:

  • 脏页数量 < 阈值 (如 50 页)
  • 迭代次数达到上限 (如 30 轮)
  • 脏页速率降至可接受水平

7.3.2 Post-copy 算法

对比:

特性Pre-copyPost-copy
停机时间较长 (等待收敛)极短 (立即切换)
总迁移时间可能很长 (高脏页率)固定 (内存大小/带宽)
容错性高 (源端保留完整副本)低 (切换后依赖源端)
适用场景一般工作负载写密集型工作负载

7.3.3 优化技术

技术原理效果
XBZRLE压缩脏页增量 (异或编码)减少 40-70% 传输量
Multifd多线程并行传输提升 3-5 倍带宽利用率
Auto-converge限制 vCPU 降低脏页率强制收敛
RDMA绕过内核直接内存访问降低延迟至 μs 级
# QEMU Live Migration 示例
virsh migrate --live --verbose vm1 qemu+ssh://dst-host/system

# 启用压缩
virsh migrate-setmaxdowntime vm1 100  # 最大停机 100ms
virsh migrate-setspeed vm1 1000       # 限速 1000 MiB/s

# 监控迁移进度
virsh domjobinfo vm1

内核源码引用

主题源码路径关键函数/结构
KVM 核心virt/kvm/kvm_main.ckvm_vcpu_ioctl(), kvm_vm_ioctl()
VMX 驱动arch/x86/kvm/vmx/vmx.cvmx_vcpu_run(), vmx_handle_exit()
VMCS 操作arch/x86/kvm/vmx/vmcs.hvmcs_read32(), vmcs_write32()
EPT 逻辑arch/x86/kvm/mmu/mmu.ckvm_mmu_page_fault(), ept_page_fault()
VirtIO 核心drivers/virtio/virtio.cregister_virtio_driver()
VirtIO Ringdrivers/virtio/virtio_ring.cvring_new_virtqueue()
Namespacekernel/nsproxy.ccopy_namespaces(), create_new_namespaces()
Cgroups v2kernel/cgroup/cgroup.ccgroup_attach_task()
OverlayFSfs/overlayfs/ovl_fill_super()

思考题

  1. VM Exit 为什么昂贵? 频繁切换会导致什么性能问题?
  2. EPT 相比影子页表 (Shadow Page Tables) 最大的优势是什么?
  3. 为什么容器不需要像虚拟机那样加载整个内核镜像?
  4. 在 VirtIO 中, 为什么 Guest 和 Host 之间需要内存屏障?
  5. 如果在容器内 root 用户逃逸到宿主机, 关键原因通常出在哪里?
  6. VPID 如何减少 VM 切换时的 TLB 刷新开销?
  7. SR-IOV 的 VF 与 PF 在功能上有什么区别?
  8. Kata Containers 如何平衡虚拟机隔离性和容器启动速度?

实验建议

  1. KVM 实验: 使用 kvmtool 创建最小化虚拟机, 观察 VM Exit 原因
  2. EPT 分析: 通过 perf kvm stat 分析 EPT violation 频率
  3. VirtIO 调试: 使用 virtio_net 驱动源码理解 Virtqueue 工作流程
  4. 容器剖析: 使用 unshare 命令手动创建各类 Namespace
  5. Cgroups 限制: 设置 CPU 配额, 使用 stress 工具验证限制效果

On this page

概述模块知识结构1. 虚拟化基础架构1.1 Hypervisor 类型1.2 虚拟化的核心挑战1.3 Popek-Goldberg 虚拟化定理2. CPU 虚拟化: VT-x / VMX2.1 VMX 运行模式2.2 VMX 生命周期2.3 VMCS (Virtual Machine Control Structure)VMCS 关键字段2.4 VM Exit 分类VM Exit 开销2.5 嵌套虚拟化 (Nested Virtualization)2.6 KVM 内核源码深入分析核心数据结构vCPU 运行核心路径VM Entry/Exit 核心流程VM Exit 处理分发EPT Violation 处理KVM 性能统计2.7 多架构虚拟化支持 (Multi-Architecture Support)2.7.1 AMD SVM (Secure Virtual Machine)2.7.2 ARM64 Virtualization (EL2)3. 内存虚拟化: EPT / NPT3.1 地址转换层次3.2 影子页表 vs EPT3.3 EPT 页表结构EPT Entry 格式(64-bit)3.4 EPT Violation vs EPT Misconfiguration3.5 大页支持3.6 TLB 管理: VPID 与 PCID4. I/O 虚拟化4.1 I/O 虚拟化方案对比4.2 VirtIO 架构VirtIO 设备类型4.3 Virtqueue 数据结构Descriptor Table EntryAvailable Ring (Guest → Host)Used Ring (Host → Guest)4.4 VirtIO 通知机制4.5 IOMMU (VT-d / AMD-Vi)4.6 SR-IOV (Single Root I/O Virtualization)5. 容器底层机制5.1 容器 vs 虚拟机5.2 Linux NamespacesNamespace 操作5.3 Cgroups v2核心控制器配置示例5.4 OverlayFS (联合文件系统)5.5 OCI 规范与 runcconfig.json 核心结构5.6 容器运行时架构6. 安全容器6.1 传统容器的安全问题6.2 Kata Containers关键技术6.3 Firecracker MicroVM6.4 gVisor (用户态内核)6.5 安全容器运行时综合对比6.6 机密计算 (Confidential Computing)6.6.1 技术对比6.6.2 AMD SEV-SNP 架构6.6.3 Intel TDX 架构6.6.4 启用配置6.6.5 远程证明 (Remote Attestation)7. 高级虚拟化主题7.1 vCPU 亲和性与 NUMA7.2 内存气球 (Ballooning)7.3 实时迁移 (Live Migration)7.3.1 Pre-copy 算法 (主流)7.3.2 Post-copy 算法7.3.3 优化技术内核源码引用思考题实验建议