x86_64 UEFI系统内存未按预期恒等映射问题咨询
我正在开发一款小型x86_64 UEFI操作系统,获取内存映射并退出Boot Services后,打印内存映射时发现所有条目均将某一物理地址映射到虚拟地址0,内存映射前几项显示物理地址重复且虚拟地址全为0。
我使用的内存描述符结构如下:
#[repr(C)] #[derive(Clone, Copy)] pub struct MemoryDescriptor { pub t: MemoryType, pub physical_start: *const c_void, pub virtual_start: *const c_void, pub number_of_pages: u64, pub attribute: u64, } #[repr(u32)] #[derive(Clone, Copy, Debug)] pub enum MemoryType { ReservedMemoryType = 0, LoaderCode, LoaderData, BootServicesCode, BootServicesData, RuntimeServicesCode, RuntimeServicesData, ConventionalMemory, UnusableMemory, ACPIReclaimMemory, ACPIMemoryNVS, MemoryMappedIO, MemoryMappedIOPortSpace, PalCode, PersistentMemory, UnacceptedMemoryType, MaxMemoryType, }
我已确认内存映射的遍历逻辑正确(按GetMemoryMap返回的描述符大小递增指针,而非结构体大小),但仍无法理解该现象。UEFI规范说明内存会被恒等映射,这是否意味着内存映射中物理地址应与虚拟地址相等?我是否遗漏了什么?该问题在QEMU及真机(Surface Pro 8)上均会出现。我还尝试通过CR3地址查看页表,但似乎操作有误或页表映射异常,因为仅Level 4表的首个条目存在,且指向的Level 3表无有效条目。
核心原因:内存描述符结构体定义不符合UEFI规范
UEFI标准的EFI_MEMORY_DESCRIPTOR结构中,32位的内存类型字段后必须紧跟一个32位的预留字段,而你定义的MemoryDescriptor直接在MemoryType(u32类型)后衔接了physical_start(u64类型),没有补充这个预留字段,导致结构体内存布局完全错位,读取到的所有字段值都是错误的——这就是虚拟地址全为0、物理地址重复的根本原因。
具体修正步骤
- 调整
MemoryDescriptor结构体定义,补充UEFI要求的预留字段:
#[repr(C)] #[derive(Clone, Copy)] pub struct MemoryDescriptor { pub t: MemoryType, pub reserved: u32, // 新增UEFI规范要求的32位预留字段 pub physical_start: *const c_void, pub virtual_start: *const c_void, pub number_of_pages: u64, pub attribute: u64, }
- 验证结构体大小:修正后的结构体大小应与
GetMemoryMap返回的描述符大小一致(UEFI规范中该结构固定为40字节)。 - 重新读取内存映射:修正结构后,物理地址与虚拟地址会显示正确的恒等映射(UEFI在Boot Services阶段确实保证物理地址和虚拟地址一一对应)。
页表异常的补充说明
之前观察到的页表异常是内存映射读取错误导致的误解:UEFI在Boot Services阶段维护的页表是完整的恒等映射,只要内存描述符读取正确,后续基于正确映射构建自定义页表即可。如果需要检查CR3,注意要在退出Boot Services前读取,且访问页表时需将页表的物理地址转换为虚拟地址(因为UEFI的恒等映射此时仍生效)。
内容的提问来源于stack exchange,提问作者gillo04

