axaddrspace 技术文档
路径:
components/axaddrspace类型:库 crate 分层:组件层 / 客户机地址空间与嵌套页表 版本:0.3.0文档依据:当前仓库源码、Cargo.toml、README.md、src/address_space/mod.rs、src/npt/mod.rs及其在axvm/axdevice中的使用关系
axaddrspace 是 Axvisor 虚拟化栈中的 guest 地址空间管理核心件。它最容易被误读成“普通虚拟地址空间库”,但从当前源码看,它的主线语义实际上是:以 GuestPhysAddr 为输入侧地址,维护一套面向嵌套页表的地址空间模型,把 GPA/IPA 映射到宿主物理页帧,并向设备仿真、vCPU fault 处理和宿主 API 暴露统一的地址类型与访问边界。
1. 架构设计分析
1.1 设计定位
axaddrspace 有三层职责同时存在:
- 地址与范围类型层:提供
GuestPhysAddr、GuestVirtAddr、HostPhysAddr、HostVirtAddr以及系统寄存器、端口等设备地址类型。 - 地址空间管理层:通过
AddrSpace<H>维护一组内存区域及其页表映射。 - 嵌套页表适配层:通过
npt模块把ax-page-table-multiarch组合成面向 Stage-2/NPT/EPT 语义的统一封装。
最关键的一点是:AddrSpace 内部的“虚拟地址侧”并不是一般 OS 语义下的 GVA,而是 GPA/IPA。这点从源码可以直接看出:
AddrSpace的范围字段是GuestPhysAddrRangemap_linear()/map_alloc()的地址参数是GuestPhysAddrNestedPageFaultInfo记录的是fault_guest_paddr
因此,本 crate 更准确的定位应是“guest physical address space manager”,而不是通用进程虚拟地址空间库。
1.2 模块划分
| 模块 | 作用 | 关键内容 |
|---|---|---|
addr | 地址与范围类型 | GuestVirtAddr、GuestPhysAddr、HostPhysAddr、HostVirtAddr 及各种 range |
address_space | 地址空间主逻辑 | AddrSpace<H>、Backend、映射/撤销/翻译/缺页处理 |
npt | 嵌套页表封装 | NestedPageTable<H>、L3/L4 选择、map/query/protect |
device | 设备地址抽象 | DeviceAddrRange、AccessWidth、Port、SysRegAddr |
memory_accessor | guest 内存访问接口 | GuestMemoryAccessor |
hal | 页帧与地址转换抽象 | AxMmHal |
frame | 物理页帧 RAII | PhysFrame<H> |
lib.rs | 重导出与错误转换 | NestedPageFaultInfo、mapping_err_to_ax_err() |
这套划分说明 axaddrspace 既是“地址空间管理器”,也是“虚拟化公共地址语义库”。
1.3 关键类型体系
地址类型
addr.rs 定义了四类核心地址:
HostVirtAddrHostPhysAddrGuestVirtAddrGuestPhysAddr
其中真正贯穿主线的是 GuestPhysAddr。GuestVirtAddr 虽然存在,但在当前 crate 中更多承担类型区分和个别架构适配用途,而不是 AddrSpace 的主要索引类型。
AddrSpace<H>
AddrSpace 是本 crate 的核心结构,内部由三部分组成:
va_range: GuestPhysAddrRangeareas: MemorySet<Backend<H>>pt: NestedPageTable<H>
它把“可见 GPA 范围”“内存区域集合”“底层嵌套页表”绑定在一起,成为 VM GPA 空间的统一描述体。
Backend<H>
后端映射策略当前分为两类:
Linear:GPA 与 HPA 之间是固定偏移映射Alloc:由 hypervisor 分配宿主物理页帧,可选择预填充或懒分配
这对应虚拟化场景中最常见的两条路径:
- 直通或恒等型内存区
- 由 hypervisor 独立托管的 guest RAM
NestedPageTable<H>
npt 模块把底层页表实现封装成统一枚举:
- 非 x86_64 平台可支持
L3或L4 - x86_64 仅支持
L4
并提供统一的:
new(level)map()unmap()map_region()unmap_region()protect_region()query()
从调用方角度看,它屏蔽了各架构具体 EPT/Stage-2 页表的类型差异。
1.4 GPA 地址空间建模主线
AddrSpace 的生命周期可以概括为:
其建模思想非常清晰:
MemorySet负责管理区域级语义和BackendNestedPageTable负责真正的页表映射AddrSpace负责把两者合成为对外统一 API
1.5 映射与撤销路径
map_linear()
map_linear() 用于建立线性映射:
- 检查范围是否落在
va_range - 检查 GPA、HPA 和大小是否 4K 对齐
- 计算固定 offset
- 创建
Backend::new_linear(offset) - 通过
MemorySet::map()更新区域与页表
这条路径适合 MMIO、预留内存或直通区。
map_alloc()
map_alloc() 用于建立由 hypervisor 托管的普通 guest RAM:
- 同样检查范围和对齐
- 构造
Backend::new_alloc(populate) populate=true时立刻分配并填充页表populate=false时建立惰性区域,等待缺页时补帧
这条路径是普通 VM RAM 的主流模式。
unmap() / clear()
unmap()撤销某一段 GPA 区域clear()清空全部区域Drop自动调用clear()
这说明 AddrSpace 对内存生命周期采用显式释放 + 析构兜底的双重策略。
1.6 缺页处理
handle_page_fault() 是另一个关键入口。它的语义不是“像普通 OS 一样处理用户态页错误”,而是:
- 检查 fault GPA 是否在地址空间范围内
- 查找该 GPA 所属
MemoryArea - 检查访问权限是否被原区域 flags 覆盖
- 将处理下发给对应
Backend
这意味着:
AddrSpace自己不决定如何补页,它只做区域查找和权限前置判断。- 真正的懒分配逻辑在
Backend::Alloc中。 - 若区域不存在或权限不匹配,则返回
false,由更上层把它视为真正的 nested page fault。