Lecture 27: 虚拟机(Virtual Machines)

目录 · ← l19 · l21 →

Lecture 27: 虚拟机(Virtual Machines)

概述

进程抽象只是底层机器的一个子集(部分指令、虚拟内存、系统调用);如果让”进程”拥有完整的机器(全部指令与寄存器、物理内存视图、MMU、I/O 设备、陷阱与中断),它就是一台虚拟机(VM)。本讲讨论虚拟机监视器(hypervisor)的实现:模拟 vs 直接执行 + 陷阱模拟、虚拟 I/O 与虚拟内存(影子页表/嵌套页表),以及虚拟机如何成就现代数据中心与云计算。

核心概念与系统机制图解

进程 vs 虚拟机:抽象层级的差异

进程抽象(OS 提供):             虚拟机抽象(hypervisor 提供):
  ● 内存: 虚拟内存页的线性数组      ● CPU: 全部指令+寄存器(含特权指令)
  ● CPU: 所有非特权指令+寄存器      ● 内存: 物理内存页 + MMU(页表等)
  ● 系统调用: 文件/进程操作等       ● I/O 设备: 定时器/磁盘/网卡/显示
  ● 是底层机器的子集               ● 陷阱与中断: 系统调用只是普通陷阱
                                  ● 是一台"完整私有机器"
  • 运行在 VM 里的 OS 叫客户操作系统(guest OS);管理 VM 的 OS 叫 hypervisor(虚拟机监视器, VMM);一台物理机可同时运行多个不同 guest OS 的 VM。

实现方式 1:完全模拟(Simulation)

  • 用程序模拟 CPU 指令、MMU 与物理内存(一个大数组)、I/O 设备(磁盘用镜像文件)。
  • 太慢:CPU/内存慢 100 倍,I/O 慢 2 倍。

实现方式 2:直接执行 + 陷阱模拟(Direct Execution)——主流

把 guest OS 放在"用户模式"运行:
  ● 绝大多数指令以全速直接执行
  ● 特权指令(如 CLI/STI/POPF/HALT)执行时产生非法指令陷阱 → hypervisor 模拟
  ● 特权指令在内核代码中相对罕见 → 模拟开销占比小

CLI(关中断)的模拟示例

1. guest OS 在用户模式执行 CLI → 非法指令陷阱
2. hypervisor 的陷阱处理器运行(IDT 指向 hypervisor)
3. hypervisor 检查到是 CLI → 模拟: 把"本 VM 的中断屏蔽"标记置位
4. 返回陷阱: CPU 回到用户模式, guest OS 从 CLI 下一条继续, 中断已被屏蔽

guest 内的系统调用(应用 syscall → guest OS sysret):

应用执行 syscall → 陷阱进 hypervisor(机器IDT) → hypervisor 模拟 syscall
  → 设置 vCPU 为内核模式 → 返回用户模式 → guest OS 执行系统调用
  → guest OS 执行 sysret → 再次陷阱进 hypervisor → hypervisor 模拟 sysret
  → vCPU 回用户模式 → 应用继续

虚拟 I/O 设备

  • guest OS 读写”虚拟设备寄存器”时,hypervisor 让这些访问触发陷阱,由陷阱处理器模拟设备功能;完成后在虚拟 CPU 上模拟一个中断。
  • 减少陷阱数:为 guest 写新的设备驱动,用 hypervisor 调用(系统调用)代替设备寄存器访问 → 半虚拟化(paravirtualization)

虚拟化虚拟内存:影子页表与嵌套页表

guest 虚拟AS → guest "物理"AS → host "机器"AS
影子页表(Shadow Page Maps):
  hypervisor 维护"guest虚拟→机器物理"的合成页表, 装入真实MMU
  guest 每次改页表都要 hypervisor 拦截更新影子表 → 开销高
x86-64 硬件扩展(Intel EPT / AMD NPT):
  增加一层页表: VM内页表(VPN→PPN) + hypervisor页表(PPN→MPN)
  硬件自动完成两级翻译, 无需影子表 → 大幅降低开销

虚拟机的价值与历史

封装性: VM 封装全部执行状态 → 可复制、保存、迁移
数据中心整合: 一台机器跑多个"单应用 VM"(隔离+利用率) → 云计算的基石
历史: 1960s IBM 发明(一台机器多用户); 80-90年代沉寂(人人有PC);
     1990s 中期因 Windows 垄断地位而复兴(兼容性测试); 2000s 数据中心整合 → 云

代码示例与系统调用解说

示例:用 Linux KVM/QEMU 体验虚拟机(命令行形态)

# 1. 创建磁盘镜像(模拟一块"物理磁盘")
qemu-img create -f qcow2 myvm.qcow2 8G
# 2. 启动一台虚拟机(客户 OS 的"裸机")
qemu-system-x86_64 \
    -enable-kvm \              # 使用硬件虚拟化扩展(嵌套页表)
    -m 2048 \                  # 给 VM 2GB "物理内存"
    -drive file=myvm.qcow2,format=qcow2 \
    -cdrom ubuntu.iso          # 安装 guest OS

【代码做了什么?】 创建磁盘镜像并启动一个完整 x86 机器(CPU、内存、磁盘、BIOS)运行客户操作系统。

【系统机制透视】

  • -enable-kvm 开启 Intel VT-x/AMD-V:CPU 硬件直接支持”guest 模式”,特权指令自动陷入 hypervisor(VMM),配合 EPT 嵌套页表实现两级地址翻译——这正是 Lecture 27 讲的”直接执行 + 硬件辅助”。
  • 磁盘镜像是”用文件模拟物理磁盘”:文件系统(qcow2 格式)在宿主机上存储 VM 的块设备——最外层的文件系统”虚拟化”了里层的文件系统。

关键要点

  1. 进程抽象是机器的子集;VM 让”进程”拥有完整机器(CPU/内存/MMU/I/O/陷阱)。
  2. hypervisor 用”直接执行 + 特权指令陷阱模拟”达到接近原生的性能。
  3. 虚拟 I/O 与半虚拟化减少陷阱开销;影子页表被硬件嵌套页表(EPT/NPT)取代。
  4. VM 的封装性(复制/迁移/隔离)催生数据中心整合与云计算。
  5. 虚拟机 = 操作系统级虚拟化的”终极形态”,与线程(CPU 虚拟化)、文件(存储虚拟化)一脉相承。

常见陷阱与注意事项

  • 把模拟与虚拟化混为一谈:完全模拟慢 100 倍;直接执行才是主流。
  • 忘记特权指令陷阱:没有陷阱机制,guest 能执行特权指令破坏宿主机。
  • 影子页表开销:guest 频繁改页表时维护开销大(硬件嵌套页表解决)。
  • 忽略 I/O 虚拟化开销:设备模拟是 VM 性能瓶颈之一(半虚拟化/直通缓解)。

思考题

  1. 问题:为什么”直接执行”能让 guest OS 以近全速运行?
    • 答案:guest 的大多数指令(算术、访存、控制流)在用户模式下直接执行,无需模拟;只有特权指令(相对稀少)才陷阱进 hypervisor 模拟——模拟占比小,总体接近原生速度。
  2. 问题:影子页表解决了什么问题?为什么后来被嵌套页表取代?
    • 答案:影子页表把”guest 虚拟→机器物理”合成为真实 MMU 使用的表,解决了”guest 页表不能直接用”的问题;但 guest 每次修改页表都要 hypervisor 拦截并重建影子表,开销高。硬件嵌套页表(EPT/NPT)在硬件中完成两级翻译,免去影子表维护。