Step 1: Intel VMX 简介与初始化 - equation314/RVM-Tutorial GitHub Wiki
在本阶段,我们将对虚拟化中的基本概念,以及 Intel VMX 指令扩展进行简单介绍。在代码部分,将实现 VMX 模式的使能与关闭。
代码:https://github.com/equation314/RVM-Tutorial/commit/cb8767256b9e444a778d480f75b96be0fba197e0
架构手册:Intel 64 and IA-32 Architectures Software Developer’s Manual (SDM) Vol. 3C, Chapter 23
- Host 模式:系统的最高特权级,运行 hypervisor。
- Guest 模式:比 host 模式特权级低,但能执行 OS 级别的特权指令,运行 guest OS 等需要 OS 级特权操作的软件。
- Hypervisor:也叫 virtual-machine monitors (VMM),运行在具有最高特权级的 host 模式,拥有硬件资源的完全控制权限,并管理其上运行的 guest 软件。
- Guest 软件:运行在 guest 模式的软件 (一般是一个操作系统,即 guest OS),比 hypervisor 特权级低,执行特定指令时会被 hypervisor 拦截 (intercept),在受控情况下访问硬件资源。
- VM entry:从 host 模式切换到 guest 模式,开始执行 guest 软件的代码。
- VM exit:从 guest 模式切换回 host 模式 (如执行特定指令、发生中断),开始执行 hypervisor 的代码。
- VCPU:由 hypervisor 虚拟出来的,运行 guest 软件所需的每 CPU 的私有状态。类似于传统 OS 中的线程,一个 guest OS 可具有多个 vCPU,vCPU 数量也可以多于物理 CPU 数量。
- Guest VM:即我们通常说的虚拟机,除了包含一个或多个 vCPU 外,还包含其他全局的系统状态,如 guest 物理内存、虚拟设备等。类似于传统 OS 中的进程,hypervisor 可创建多个 guest VM,各运行一个 guest OS。
Intel VMX (也叫 Intel VT-x),是 Intel 处理器提供的硬件虚拟化的技术。虽然同为 x86 架构,但 AMD 与 Intel 的硬件虚拟化技术不同,AMD 的被称为 SVM 或 AMD-V。
在处理器使能 VMX 后,将具有两种运行模式 (统称 VMX operation):
- VMX root operation:即 host 模式,运行 hypervisor。
- VMX non-root operation:即 guest 模式,运行 guest 软件。
VMX operation 可以和 x86 特权级相结合,如 VMX non-root ring-0 表示 guest 内核态,运行 guest OS;VMX non-root ring-3 表示 guest 用户态,运行 guest 用户程序。
为了配置 VMX non-root operation 的进入、运行、退出,以及保存 guest 的状态,VMX 中还有一个重要数据结构 Virtual-Machine Control Structure (VMCS),我们将在 Step 2 详细介绍。
此外 VMX 还提供了一些额外的指令,主要有:
指令 | 描述 |
---|---|
VMXON |
使能 VMX,进入 VMX (root) operation |
VMXOFF |
退出 VMX operation |
VMCLEAR |
清除指定 VMCS,使得其可以被激活 |
VMPTRLD |
在当前 CPU 上激活指定 VMCS,使其成为当前 VMCS |
VMREAD |
读当前 VMCS 字段 |
VMWRITE |
写当前 VMCS 字段 |
VMLAUNCH |
进入 VMX non-root operation (当前 VMCS 首次进入) |
VMRESUME |
进入 VMX non-root operation (当前 VMCS 非首次进入) |
VMCALL |
在 VMX non-root operation 执行,触发 VM exit,从而调用 hypervisor 的功能 (类似 syscall) |
基于 Intel VMX 实现的 hypervisor,其基本工作流程如下:
- Hypervisor 执行
VMXON
指令,使能 VMX,进入 VMX root operation。 - Hypervisor 配置 VMCS。
- Hypervisor 执行
VMLAUNCH
/VMRESUME
指令,发生 VM entry,切换到 VMX non-root operation,执行 guest 代码。 - Guest 软件在发生特定事件后,会导致 VM exit,自动切换回 VMX root operation,hypervisor 需要根据 VM exit 的原因采取进一步操作。
- Guest 执行完毕,hypervisor 执行
VMOFF
指令,退出 VMX 模式。
本节我们介绍如何使能 VMX 模式。
首先,我们需要检查当前硬件平台是否支持 VMX 指令扩展,这一步又可以细分为两种情况:
- CPU 不支持 VMX
- CPU 支持 VMX,但被 BIOS 禁用
对于 CPU 支持情况,是通过 CPUID
指令 查询 Processor Info and Feature Bits (EAX=1
) 中的相关位获知。
对于 BIOS 支持情况,需要查询 IA32_FEATURE_CONTROL
MSR 的以下两位:
- Bit 0 lock bit:如果设置,无法修改该 MSR;如果不设置,无法执行
VMXON
。 - Bit 2 enables VMXON outside SMX operation:如果不设置,无法 (在 SMX 模式外) 执行
VMXON
。
我们希望这两个位都已被设为了 1,否则就要重新设置。不过在 bit 0 为 1,而 bit 2 为 0 时,我们既无法使用 VMXON
指令,又无法修改该 MSR。此时说明 VMX 被 BIOS 禁用,需要在 BIOS 中手动开启。
在使能 VMX 之前,一般还需要检查相关系统寄存器的某些位设置是否符合要求,并检查 VMX 的一些子功能是否被支持,这不影响 VMX 的使能成功,但便于在使能失败或后续操作失败时排查原因。感兴趣的可以参见代码与架构手册。
在执行 VMXON
指令前,需要先将 CR4
的 VMXE
位 (13 位) 设为 1。然后创建 VMXON region,这是一块 4KB 大小 4KB 对齐的特殊内存,其物理地址将作为操作数提供给 VMXON
指令。VMXON region 还需要满足特定的格式:前 4 字节的低 31 位需要设置为 VMCS revision identifier,该值可以在 IA32_VMX_BASIC
MSR 的低 31 位中查到。
由于 VMXON
的作用范围只有当前 CPU,所以在多核系统中如果有多个 CPU 需要使能 VMX,则需要在相应 CPU 上各自执行一遍 VMXON
指令,并提供不同的 VMXON region。
如果已使能 VMX,并在 guest 运行结束后要关闭 VMX,应当先执行 VMXOFF
指令,再将 CR4
的 VMXE
位清除。
最后,总结一下使能 VMX 的一般流程:
- 执行
CPUID
指令,检查 CPU 支持情况。 - 查询或设置
IA32_FEATURE_CONTROL
MSR,检查 VMX 是否被 BIOS 禁用。 - 查询
IA32_VMX_BASIC
MSR,获取 VMCS revision identifier。 - 创建 VMXON region,写入 VMCS revision identifier。
- 设置
CR4
的VMXE
位。 - 传入 VMXON region 物理地址,执行
VMXON
指令。
本阶段主要工作是搭建了 rvm crate 的基本框架,并使能 VMX。其主要逻辑位于 rvm/src/arch/x86_64/vmx/mod.rs。
首先是检查 VMX 的支持情况,Rust 可使用 raw_cpuid 库来进行 CPUID
各功能的查询:
pub fn has_hardware_support() -> bool {
if let Some(feature) = raw_cpuid::CpuId::new().get_feature_info() {
feature.has_vmx()
} else {
false
}
}
VmxPerCpuState
(以及架构无关的 RvmPerCpu
) 是一个 per-CPU 结构,用于管理每个 CPU 私有的虚拟化相关状态 (如 VMXON region),由 rvm crate 的调用者创建。不过本项目只提供单核的 hypervisor 实现,无需特别考虑,直接在 hypervisor/src/hv/mod.rs 中 run
函数的栈上创建:
pub fn run() {
println!("Starting virtualization...");
println!("Hardware support: {:?}", rvm::has_hardware_support());
let mut percpu = RvmPerCpu::<RvmHalImpl>::new(0);
let res = percpu.hardware_enable();
println!("Hardware enable: {:?}", res);
}
启用 VMX 的具体逻辑位于 VmxPerCpuState::hardware_enable
函数,其中依次执行了以下步骤:
- 确保当前硬件支持 VMX,且还未被使能。
- 检查与配置
IA32_FEATURE_CONTROL
MSR。 - 检查
CR0
和CR4
的位设置是否符合要求 (SDM Vol. 3C, Appendix A.7, A.8)。 - 从
IA32_VMX_BASIC
MSR 读取 VMCS revision identifier,并检查其他位是否符合要求。 - 初始化 VMXON region。由于之后阶段配置 VMCS 时也需要分配一个类似的内存区域 (VMCS region),且格式与 VMXON region 一样,因此代码中用一个
VmxRegion
结构体统一这两种用途。 - 设置
CR4
的VMXE
位。 - 传入 VMXON region 物理地址,执行
VMXON
指令。
在 msr.rs 中,我们通过 Msr
结构体实现了对各种 MSR 寄存器 (Model-Specific Registers) 的读写。
此外,由于 rvm 是作为一个独立的库,并不具有内存分配功能。所以 rvm 中只会调用内存分配和释放的接口,而具体的实现在 hypervisor 调用时 rvm 时传入,这是通过 hal.rs 中的 trait RvmHal
来实现的。除了内存分配外,物理地址和虚拟地址的相互转换也是通过这种方式提供给 rvm 的:
// The Interfaces which the underlying software (kernel or hypervisor) must implement.
pub trait RvmHal {
/// Allocates a 4K-sized contiguous physical page, returns its physical address.
fn alloc_page() -> Option<PhysAddr>;
/// Deallocates the given physical page.
fn dealloc_page(paddr: PhysAddr);
/// Converts a physical address to a virtual address which can access.
fn phys_to_virt(paddr: PhysAddr) -> VirtAddr;
/// Converts a virtual address to the corresponding physical address.
fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr;
}
- 阅读 Intel SDM Vol. 3C, Chapter 23: Introduction to Virtual Machine Extensions (共 4 页)