
在裸机 Rust 中使用aarch64-paging构建 AArch64 恒等映射页表【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rustaarch64-paging是一个专为 AArch64 虚拟内存系统架构Virtual Memory System Architecture, VMSA设计的 Rust crate它允许你在不依赖操作系统的情况下用纯 Rust 代码创建、配置并激活页表。本文以 Google Android 团队所维护的 Rust 课程comprehensive-rust中 aarch64-paging 章节 为核心结合课程仓库中 Application Processors 示例 与 RTC 练习 的真实用法讲解它的核心 API、最小可运行示例以及它在裸机启动流程中的实际位置读完你将掌握用IdMap建立恒等映射、为内存区域设置访问属性并通过TTBR0_EL1激活页表的完整方法。背景为什么裸机程序需要 AArch64 页表在 Application processors 一节中课程指出与几乎没有 MMU 的微控制器如 Cortex-M 系列不同Cortex-A 这类应用处理器拥有 MMU 与多级特权等级Arm 的 exception level、x86 的 ring。课程使用 QEMU 的 aarch64virt虚拟开发板进行教学虽然virt板并不对应任何真实硬件但课程仍把它当作裸机环境来写操作系统级的启动代码。启动阶段面临的一个经典问题CPU 上电后处于EL3或经过引导后进入EL1此时虚拟地址与物理地址尚未建立映射关系。要访问 UART课程中为 PL011基地址0x900_0000、GIC基地址0x800_0000等外设就必须先建立页表。在课程中这一需求由 aarch64-rt 章节的initial_pagetable!宏以静态方式解决而aarch64-paging则提供了更通用的、动态的页表构建能力。aarch64-paging是什么正如课程文档所述借助aarch64-pagingcrate你可以按照 AArch64 虚拟内存系统架构来创建页表。它把 AArch64 描述符格式、页表层级level 0 ~ 3、块描述符/页描述符、地址转换规则等底层细节封装为类型安全的 Rust API让裸机开发者不必手写描述符位。它在真实世界中的一个典型用途是 Android 的Protected VM Firmwarepvmfw——文档明确注明该 crate 被用于 Android 的 Protected VM 固件中。课程将其收录在 Useful crates 章节下与zerocopy、buddy_system_allocator、tinyvec、spin并列是解决裸机编程常见问题的实用工具之一。核心 API 一览文档中的示例仅用了三个类型理解它们就掌握了这个 crate 的主要用法API作用IdMap构建**恒等映射identity mapping**页表即虚拟地址 物理地址的映射MemoryRegion描述一段连续的地址区域例如MemoryRegion::new(0x80200000, 0x80400000)表示从0x8020_0000到0x8040_0000共 2 MiBAttributes描述符属性集合如NORMAL、NON_GLOBAL、READ_ONLYIdMap::new(ASID, ROOT_LEVEL)的两个参数分别是ASIDAddress Space Identifier地址空间标识符用于区分不同地址空间示例中为1ROOT_LEVEL根层级页表的起始层级示例中为1。ROOT_LEVEL越小单个页表条目覆盖的地址范围越大level 1 的一个块描述符覆盖 1 GiB适合地址空间较大的场景。最小示例映射一段 2 MiB 只读内存原文档给出了一个完整的最小示例在课程源码中以rust,editable,compile_fail形式嵌入compile_fail是因为它需要目标硬件环境、无法在 mdbook Playground 中直接编译执行use aarch64_paging::{ idmap::IdMap, paging::{Attributes, MemoryRegion}, }; const ASID: usize 1; const ROOT_LEVEL: usize 1; // Create a new page table with identity mapping. let mut idmap IdMap::new(ASID, ROOT_LEVEL); // Map a 2 MiB region of memory as read-only. idmap.map_range( MemoryRegion::new(0x80200000, 0x80400000), Attributes::NORMAL | Attributes::NON_GLOBAL | Attributes::READ_ONLY, ).unwrap(); // Set TTBR0_EL1 to activate the page table. idmap.activate();逐行拆解这段代码的含义let mut idmap IdMap::new(ASID, ROOT_LEVEL);创建一个新的恒等映射页表。ASID 1为地址空间分配标识符ROOT_LEVEL 1表示页表从 level 1 开始后续会按需分配更深的层级。idmap.map_range(MemoryRegion::new(0x80200000, 0x80400000), ...)将[0x8020_0000, 0x8040_0000)这 2 MiB 的物理地址区间映射为相同的虚拟地址并附加三个属性Attributes::NORMAL正常内存可缓存属性Attributes::NON_GLOBAL非全局条目配合 ASID 使用避免不同地址空间的条目互相干扰Attributes::READ_ONLY只读权限防止该区域的写入。map_range返回Result示例通过.unwrap()处理错误——在真实的启动代码中应改用更稳健的错误处理例如记录日志后进入 panic 或关机的路径。idmap.activate()将生成的页表根地址写入系统寄存器TTBR0_EL1使页表真正生效。这一步是激活映射的关键在调用activate()之前上述map_range只是在内存中构建描述符结构并不会影响 CPU 的地址翻译。运行前提真实硬件或 QEMU文档特别在details中提醒了两点注意事项这与课程中其他可以在 Playground 中直接运行的示例形成了鲜明对比无法轻易单独运行这个示例因为它会操作TTBR0_EL1等特权系统寄存器、建立影响整个地址空间翻译的页表一旦出错整个 CPU 都会失去寻址能力必须在真实硬件或 QEMU 下运行它需要aarch64目标环境以及相应的特权级EL1 及以上普通的用户态进程或 Rust Playground 不具备这些条件。这也解释了为什么课程仓库中的所有aarch64-paging实际用例都出现在#![no_std]#![no_main]的裸机程序中例如 main_rt.rs 与 RTC 练习的 main.rs。仓库中的真实用法静态初始页表与El1Attributes虽然课程讲义页只展示了IdMap这一个入口但仓库中的示例和练习还展示了该 crate 的另一面——aarch64_paging::descriptor::El1Attributes与aarch64-rt的initial_pagetable!宏的组合用法。在 Application Processors 示例 中初始页表完全由 Rust 静态构建use aarch64_paging::descriptor::El1Attributes; /// Attributes to use for device memory in the initial identity map. const DEVICE_ATTRIBUTES: El1Attributes El1Attributes::VALID .union(El1Attributes::ATTRIBUTE_INDEX_0) .union(El1Attributes::ACCESSED) .union(El1Attributes::UXN); /// Attributes to use for normal memory in the initial identity map. const MEMORY_ATTRIBUTES: El1Attributes El1Attributes::VALID .union(El1Attributes::ATTRIBUTE_INDEX_1) .union(El1Attributes::INNER_SHAREABLE) .union(El1Attributes::ACCESSED) .union(El1Attributes::NON_GLOBAL); initial_pagetable!({ let mut idmap [0; 512]; // 1 GiB of device memory. idmap[0] DEVICE_ATTRIBUTES.bits(); // 1 GiB of normal memory. idmap[1] MEMORY_ATTRIBUTES.bits() | 0x40000000; // Another 1 GiB of device memory starting at 256 GiB. idmap[256] DEVICE_ATTRIBUTES.bits() | 0x4000000000; InitialPagetable(idmap) });这段代码在 aarch64-rt 章节有详细讲解它体现了几个与讲义页互补的要点El1Attributes是位标志集合通过union组合VALID有效位、ATTRIBUTE_INDEX_0/1选择内存属性编码分别对应设备内存与普通内存、ACCESSED访问位、UXN不可执行、INNER_SHAREABLE内部共享、NON_GLOBAL非全局等最后用.bits()得到原始描述符数值level 1 块描述符直接覆盖 1 GiBidmap[0]、idmap[1]、idmap[256]分别映射第 0、1、256 个 1 GiB 区间地址偏移0x40000000、0x4000000000与属性位直接做按位或拼出完整描述符这套设备内存/普通内存属性分离的模式与讲义页中Attributes::NORMAL | Attributes::READ_ONLY的思路一脉相承只是层级更低、更贴近寄存器。initial_pagetable!宏 让开发者用 Rust 代码定义静态页表替代此前在汇编中手工填写描述符的做法。它是aarch64-paging生态之外的补充组件但两者组合使用可以覆盖从启动瞬间的静态映射到运行期的动态map_range的完整生命周期。在课程练习中的位置RTC 驱动aarch64-paging还出现在课程的 RTC 驱动练习 中。该练习要求在 QEMU aarch64 virt 机器上为0x9010000处的 PL031 实时时钟编写驱动其解答代码 同样引入了aarch64_paging::descriptor::El1Attributes并定义了与上述示例几乎相同的DEVICE_ATTRIBUTES/MEMORY_ATTRIBUTES常量。从练习的 Cargo.toml 可以看到它的依赖声明方式[dependencies] aarch64-paging { version 0.12.1, default-features false } aarch64-rt 0.4.3 arm-gic 0.8.1 arm-pl011-uart 0.5.0 bitflags 2.11.1 chrono { version 0.4.44, default-features false } log 0.4.30 safe-mmio 0.3.0 smccc 0.2.3 spin 0.12.0 zerocopy 0.8.50两个值得注意的配置细节version 0.12.1课程仓库当前锁定的版本Application Processors 示例 与 RTC 练习 保持一致default-features false关闭默认特性。裸机no_std环境下通常无法使用依赖标准库的默认特性因此必须显式关闭这一模式同样适用于该示例中几乎全部依赖arm-pl011-uart、safe-mmio、smccc、spin、zerocopy等。练习的运行方式与讲义页一致在src/exercises/bare-metal/rtc目录下通过 Makefile 在 QEMU 中运行make qemu示例代码则通过make qemu_rt运行于src/bare-metal/aps/examples。使用建议与常见误区结合讲义与仓库代码使用aarch64-paging时有几点值得注意activate()之前不要访问被映射的地址页表构建与生效是两个阶段。如果映射尚未激活就访问对应虚拟地址会触发地址翻译故障translation fault在裸机环境中通常表现为同步异常或直接挂死。恒等映射不等于随意映射IdMap的语义是虚拟地址 物理地址适合启动早期和 DMA 场景。若需要虚拟地址与物理地址分离例如加载高于TTBR0可寻址范围的镜像则应使用该 crate 中其他支持非恒等映射的入口。属性要与内存类型匹配设备内存MMIO 寄存器区域不应标记为NORMAL可缓存否则写入外设寄存器可能被缓存合并导致驱动行为异常——这正是课程示例将设备内存与普通内存属性分开定义的原因。错误处理不要用unwrap一带而过讲义页的示例为了简短使用了.unwrap()但在真实固件中map_range失败如区域重叠、描述符冲突应当被记录并触发受控的异常路径。ROOT_LEVEL的选择影响页表规模与粒度ROOT_LEVEL 1意味着 512 个 level 1 描述符即可覆盖整个 512 GiB 地址空间每项 1 GiB适合地址跨度大的固件如果可寻址空间较小选择更大的根层级可以减少页表占用的内存。小结aarch64-paging把 AArch64 VMSA 的页表描述符、地址翻译与TTBR0_EL1等系统寄存器的操作封装成类型安全的 Rust API。通过IdMap的map_rangeactivate两步开发者可以在几十行代码内完成恒等映射的建立与激活配合aarch64-rt的initial_pagetable!宏与El1Attributes位标志可以覆盖从 CPU 上电到运行期动态管理的完整内存映射需求。它在 Android Protected VM Firmware 中的实际部署以及课程仓库中 aps 示例 和 RTC 练习 的反复使用都说明它是 AArch64 裸机 Rust 开发中值得掌握的基础设施之一。【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考