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)。
驱动开发提示:在ARMv7-A平台上,驱动代码中通过 set_pte_at() 和 flush_tlb_page() 管理页表条目时,需要特别注意域寄存器的配置,错误的域设置可能导致不可预测的权限异常。

3.1.2 地址转换流程

ARMv7-A的地址转换过程分为两步:首先通过TTBRx(Translation Table Base Register)找到一级页表基址,然后根据虚拟地址的L1索引获取二级页表基址,最后通过L2索引找到最终的物理地址。

ARMv7-A 二级页表地址转换流程 虚拟地址 (VA) 一级页表 (L1) 二级页表 (L2) 物理地址 (PA) TTBRx 寄存器指向 L1 基址 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位描述符
架构迁移注意:从ARMv7-A迁移到ARMv8-A时,驱动代码中直接操作页表描述符的汇编部分需要重写。ARMv8-A的页表描述符位域定义完全不同,且不再支持域寄存器,权限控制完全由页表条目中的AP(Access Permission)和PXN(Privileged Execute Never)位控制。

3.2.2 地址转换层级

ARMv8-A的地址转换使用翻译粒度(Translation Granule)概念,常见配置为4KB粒度下的4级页表。虚拟地址被划分为多个位段,分别索引L0到L3页表。

ARMv8-A 4级页表地址转换(4KB粒度) 虚拟地址 (VA) [47:0] L0[47:39] L1[38:30] L2[29:21] L3[20:12] 偏移[11:0] L0 页表 L1 页表 L2 页表 L3 页表 物理地址 (PA) TTBR0_EL1 指向 L0 页表基址;每级页表包含 512 个 8 字节描述符

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
性能优化要点:在驱动中映射大块连续物理内存(如帧缓冲、DMA缓冲区)时,应优先使用L2块映射(2MB粒度),这能显著减少页表占用空间和TLB miss次数。Linux内核的 ioremap() 和 dma_alloc_coherent() 在ARMv8-A上会自动选择最优映射粒度。

3.3.3 页表遍历硬件加速

ARMv8-A的MMU硬件自动执行页表遍历(Page Table Walk),无需软件干预。当TLB未命中时,硬件会从TTBRx寄存器指定的基址开始,逐级读取页表描述符,直到找到有效的页或块映射。如果遍历过程中遇到无效描述符或权限错误,MMU会触发缺页异常(Page Fault)或权限异常。

ARMv8-A 硬件页表遍历流程 TLB未命中 读取L0表项 读取L1表项 读取L2表项 读取L3表项 获取物理地址

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
TLB维护最佳实践:在多核系统中,优先使用带 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维护的底层机制,才能编写出高效、稳定的内存管理代码。