Skip to content

4. 内存虚拟化

equation314 edited this page Sep 16, 2020 · 3 revisions

Extended Page Table

不同于传统 OS 的物理地址和虚拟地址,hypervisor 涉及到的地址有三种:

  1. Host 物理地址
  2. Host 虚拟地址
  3. Guest 物理地址

其中前两类与传统 OS 中的基本一致,只是多了一个 Guest 物理地址。讲道理还应有个 Guest 虚拟地址,不过其基本都由 Guest OS 来管理,一般情况下 hypervisor 不需要用到。但也有例外,比如要读取 Guest 发生 VM Exit 时的指令,但 Guest 启用了分页机制,此时的 Guest RIP 就是以 Guest 虚拟地址的形式给出的,需要根据 Guest 的 CR3 寄存器手动模拟页表。

传统 OS 中,虚拟地址到物理地址的转换由页表完成,在 Intel VMX 中,也提供了一个类似的表,用于 Guest 物理地址到 Host 物理地址的转换,即 EPT (Extended Page Table)。EPT 与页表的结构几乎一模一样,也支持多级,只是表项中的一些 flag 不同。

下表总结了页表和 EPT 的主要不同点:

Page Table Extended Page Table
地址转换 虚拟地址 → 物理地址 Guest 物理地址 → Host 物理地址
设置基址 CR3 寄存器 VMCS 的 EPT pointer 字段
访问不存在的页 Page Fault 异常 (#PF) VM Exit:EPT violation

Guest 物理内存管理

为了管理 Guest 的物理内存,RVM 提供了一个结构体 DefaultGuestPhysMemorySet,其中实现了添加、删除 EPT 映射,读、写 Guest 物理内存,处理缺页等基本操作。

不过,Host OS 在调用 RVM 时,也可不使用默认的 Guest 物理内存管理器,而通过实现 GuestPhysMemorySetTrait 来自定义。 例如在 zCore 中就给 VmAddressRegion 套了一层该 trait,使得可以直接复用 VmAddressRegion 来管理 Guest 物理内存。

pub trait GuestPhysMemorySetTrait: core::fmt::Debug + Send + Sync {
    /// Physical address space size.
    fn size(&self) -> u64;

    /// Add a contiguous guest physical memory region and create mapping,
    /// with the target host physical address `hpaddr` (optional).
    fn map(&self, gpaddr: GuestPhysAddr, size: usize, hpaddr: Option<HostPhysAddr>) -> RvmResult;

    /// Remove a guest physical memory region, destroy the mapping.
    fn unmap(&self, gpaddr: GuestPhysAddr, size: usize) -> RvmResult;

    /// Read from guest address space.
    fn read_memory(&self, gpaddr: GuestPhysAddr, buf: &mut [u8]) -> RvmResult<usize>;

    /// Write to guest address space.
    fn write_memory(&self, gpaddr: GuestPhysAddr, buf: &[u8]) -> RvmResult<usize>;

    /// Called when accessed a non-mapped guest physical adderss `gpaddr`.
    fn handle_page_fault(&self, gpaddr: GuestPhysAddr) -> RvmResult;

    /// Page table base address.
    fn table_phys(&self) -> HostPhysAddr;
}

创建 Guest 对象时,也需要同时传入一个实现了 GuestPhysMemorySetTrait 的结构:

impl Guest {
    /// Create a new Guest.
    pub fn new(gpm: Arc<dyn GuestPhysMemorySetTrait>) -> RvmResult<Arc<Self>> {
        // ......
    }
}

此外,在 Host OS 实现 GuestPhysMemorySetTrait 时,可能需要直接对 EPT 进行操作,为此 RVM 还提供了 RvmPageTableIntoRvmPageTableFlags 这两个 trait,用于 EPT 的映射以及表项 flag 之间的转换。

相关代码:

Host 物理内存分配

RVM 没有 Host 物理内存管理功能,需要由 Host OS 提供。共需提供以下三个功能:

  • 分配一个物理页帧
  • 回收一个物理页帧
  • Host 物理地址到 Host 虚拟地址的转换

这些函数在 RVM 中是一个空的实现,通过 Rust 的 FFI (Foreign Function Interface) 接口导出,调用 RVM 的 Rust 应用需要补全这些实现。

为了便于调用者的实现,这里稍微用了一点 Rust 过程宏,并支持一定的错误检查。例如在 zCore 中对这几个函数的实现可以这么写:

#[rvm::extern_fn(alloc_frame)]
fn rvm_alloc_frame() -> Option<usize> {
    hal_frame_alloc()
}

#[rvm::extern_fn(dealloc_frame)]
fn rvm_dealloc_frame(paddr: usize) {
    hal_frame_dealloc(&paddr)
}

#[rvm::extern_fn(phys_to_virt)]
fn rvm_phys_to_virt(paddr: usize) -> usize {
    paddr + PHYSICAL_MEMORY_OFFSET
}

其中属性宏 #[rvm::extern_fn(alloc_frame)] 展开后等价于:

#[export_name = "rvm_alloc_frame"]
extern "Rust" fn foo() -> Option<usize> {
    // ......
}

相关代码:

应用于 zCore

使用 VMAR 进行物理内存管理

在 zCore 和原版 Zircon 中,Guest 物理内存都由 VmAddressRegion 管理。为了适配 RVM 提供的接口,做了一个简单的包装:

struct GuestPhysMemorySet {
    vmar: Arc<VmAddressRegion>,
}

impl GuestPhysMemorySetTrait for GuestPhysMemorySet {
    fn size() { /* ...... */ }
    fn map() { /* ...... */ }
    fn unmap() { /* ...... */ }
    fn read_memory() { /* ...... */ }
    fn write_memory() { /* ...... */ }
    fn handle_page_fault() { /* ...... */ }
    fn table_phys() { /* ...... */ }
}

而 trait 中函数的实现都可以直接调用 VmAddressRegion 中的函数。

注意到这里的 VmAddressRegion 不能是原来的 root 或 kernel VMAR,而是一个新的 guest VMAR,通过 VmAddressRegion::new_guest() 创建。该类型 VMAR 与其他类型 VMAR 的主要区别在于其 page_table 字段,guest VMAR 需要用 EPT 而不是普通页表。

相关代码:

统一 EPT 和普通页表的接口

为了能让访问 EPT 和访问普通页表的接口统一,zCore 中对 RVM 中的 EPT 结构做了一个简单的包装,并在 kernal_hal 中实现了统一访问页表的 PageTableTrait,然后分别让普通页表和 EPT 实现该 trait:

// Normal page table in defined kernal_hal
pub struct PageTable {
    table_phys: PhysAddr,
}

impl PageTableTrait for PageTable {
    fn map() { /* ...... */ }
    fn unmap() { /* ...... */ }
    fn protect() { /* ...... */ }
    fn query() { /* ...... */ }
    fn table_phys() { /* ...... */ }
}

// Wrapper of EPT
pub struct VmmPageTable(ArchRvmPageTable);

impl PageTableTrait for VmmPageTable {
    fn map() { /* ...... */ }
    fn unmap() { /* ...... */ }
    fn protect() { /* ...... */ }
    fn query() { /* ...... */ }
    fn table_phys() { /* ...... */ }
}

相关代码: