3. ARM架构MMU详解:ARMv7-A MMU、ARMv8-A MMU、页表层级
ARM架构的MMU(Memory Management Unit)是嵌入式Linux驱动开发中内存管理的核心硬件单元。它负责将处理器发出的虚拟地址(VA)转换为物理地址(PA),并提供内存访问权限控制、缓存策略管理等功能。本章将深入剖析ARMv7-A和ARMv8-A两种主流架构的MMU实现差异,以及关键的页表层级结构。
3.1 ARMv7-A MMU 架构
ARMv7-A架构(如Cortex-A8/A9/A15)采用传统的二级页表结构,支持短描述符(Short Descriptor)和长描述符(Long Descriptor)两种格式。在Linux内核中,通常使用长描述符格式以支持大物理地址空间(LPAE)。
3.1.1 核心特性
- 页表层级:支持一级页表(L1)和二级页表(L2),L1表项指向L2页表基址。
- 页大小:支持4KB、64KB、1MB(节)、16MB(超级节)等多种粒度。
- 域(Domain):提供16个域,用于实现粗粒度的内存访问控制。
- TLB:支持TLB锁定和广播式TLB维护(TLBIMVA)。
set_pte_at() 和 flush_tlb_page() 管理页表条目时,需要特别注意域寄存器的配置,错误的域设置可能导致不可预测的权限异常。
3.1.2 地址转换流程
ARMv7-A的地址转换过程分为两步:首先通过TTBRx(Translation Table Base Register)找到一级页表基址,然后根据虚拟地址的L1索引获取二级页表基址,最后通过L2索引找到最终的物理地址。
3.2 ARMv8-A MMU 架构
ARMv8-A架构(如Cortex-A53/A72/A76)引入了AArch64执行状态,MMU设计发生了根本性变化。它支持最多4级页表(L0~L3),并统一了页表描述符格式,不再区分短描述符和长描述符。
3.2.1 关键差异对比
| 特性 | ARMv7-A | ARMv8-A (AArch64) |
|---|---|---|
| 页表层级 | 2级(L1/L2) | 最多4级(L0~L3) |
| 页大小 | 4KB/64KB/1MB/16MB | 4KB/16KB/64KB/2MB/1GB |
| 虚拟地址宽度 | 32位 | 48位(可扩展至52位) |
| 转换表基址寄存器 | TTBR0/TTBR1 | TTBR0_EL1/TTBR1_EL1 |
| 域支持 | 16个域 | 已移除,改用VMSAv8-64权限模型 |
| 页表描述符格式 | 短描述符/长描述符 | 统一64位描述符 |
3.2.2 地址转换层级
ARMv8-A的地址转换使用翻译粒度(Translation Granule)概念,常见配置为4KB粒度下的4级页表。虚拟地址被划分为多个位段,分别索引L0到L3页表。
3.3 页表层级详解
3.3.1 页表描述符结构
ARMv8-A的页表描述符为64位,包含物理地址基址、属性位(如访问权限、内存类型、可执行性等)以及下一级页表指针。关键位域如下:
/* ARMv8-A 页表描述符关键位域(以L3页表为例) */
Bit[63:48] : 物理地址高位扩展(52位PA时使用)
Bit[47:12] : 物理页帧号(PFN),指向4KB页基址
Bit[11] : nG(非全局位)
Bit[10] : AF(访问标志位)
Bit[9:8] : SH(可共享属性)
Bit[7:6] : AP(访问权限)
Bit[5] : NS(非安全位)
Bit[4] : 保留
Bit[3] : PXN(特权执行永不)
Bit[2] : XN(执行永不)
Bit[1] : 表项类型(0=块/页,1=表)
Bit[0] : 有效位(1=有效)
3.3.2 页表层级与块映射
ARMv8-A支持在L1和L2级别直接映射大块内存(块映射),减少页表层级深度,提升TLB命中率。块映射的粒度如下:
| 页表层级 | 4KB粒度块大小 | 16KB粒度块大小 | 64KB粒度块大小 |
|---|---|---|---|
| L1 | 1GB | 512MB | 512MB |
| L2 | 2MB | 32MB | 512MB |
| L3 | 4KB | 16KB | 64KB |
ioremap() 和 dma_alloc_coherent() 在ARMv8-A上会自动选择最优映射粒度。
3.3.3 页表遍历硬件加速
ARMv8-A的MMU硬件自动执行页表遍历(Page Table Walk),无需软件干预。当TLB未命中时,硬件会从TTBRx寄存器指定的基址开始,逐级读取页表描述符,直到找到有效的页或块映射。如果遍历过程中遇到无效描述符或权限错误,MMU会触发缺页异常(Page Fault)或权限异常。
3.4 驱动开发中的MMU配置实践
3.4.1 页表创建与维护
在Linux内核中,驱动开发者通常不直接操作页表,而是通过内核提供的接口进行内存映射。但在某些场景(如IOMMU驱动、自定义内存管理模块)下,需要理解页表操作:
/* 内核页表操作接口示例(ARMv8-A) */
#include <linux/mm.h>
#include <asm/pgtable.h>
/* 创建页表映射 */
int create_mapping(unsigned long virt, phys_addr_t phys, size_t size)
{
pgd_t *pgd;
p4d_t *p4d;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
pgd = pgd_offset(current->mm, virt);
if (pgd_none(*pgd)) {
p4d = p4d_alloc(&init_mm, pgd, virt);
if (!p4d)
return -ENOMEM;
}
/* 逐级分配页表,直到PTE级别 */
pte = pte_alloc_map(&init_mm, pmd, virt);
if (!pte)
return -ENOMEM;
/* 设置页表条目 */
set_pte_at(&init_mm, virt, pte, pfn_pte(phys >> PAGE_SHIFT, PAGE_KERNEL));
return 0;
}
3.4.2 TLB维护操作
当修改页表后,必须使对应的TLB条目失效,否则处理器可能使用过时的缓存映射。ARMv8-A提供了多种TLB维护指令:
| 操作 | 指令 | 作用范围 |
|---|---|---|
| 使能单个虚拟地址 | TLBI VAE1IS, x0 |
当前PE上的指定VA |
| 使能ASID范围 | TLBI ASIDE1IS, x0 |
当前PE上指定ASID的所有条目 |
| 全局使能 | TLBI ALLE1IS |
当前PE上的所有TLB条目 |
| 广播使能 | TLBI VAE1, x0(不带IS后缀) |
所有PE上的指定VA |
IS(Inner Shareable)后缀的TLB指令,确保所有核的TLB一致性。在驱动中调用 flush_tlb_page() 或 flush_tlb_mm() 时,内核会自动选择合适的TLBI指令。
3.5 本章小结
ARMv7-A和ARMv8-A的MMU在设计理念上存在显著差异:ARMv7-A的二级页表和域模型更适合32位嵌入式系统,而ARMv8-A的四级页表和统一描述符格式为64位大地址空间和虚拟化提供了坚实基础。驱动开发者需要根据目标平台选择正确的页表操作接口,并理解硬件页表遍历和TLB维护的底层机制,才能编写出高效、稳定的内存管理代码。