3. MMU初始化流程:系统寄存器配置(TTBR0/TTBR1)、MAIR属性设置、SCTLR使能MMU

MMU(内存管理单元)的初始化是嵌入式系统启动过程中最关键的步骤之一。在ARMv8-A架构中,MMU的启用必须遵循严格的寄存器配置顺序:首先设置内存属性寄存器(MAIR),然后配置页表基地址寄存器(TTBR0/TTBR1),最后通过系统控制寄存器(SCTLR)使能MMU。任何步骤的遗漏或顺序错误都将导致系统异常或不可预测的行为。

3.1 MMU初始化总体流程

下图展示了MMU初始化的标准三阶段流程,每个阶段对应一组关键寄存器的配置:

MMU初始化三阶段流程 MMU初始化三阶段流程 阶段1:MAIR配置 设置内存属性编码 (Attr0~Attr7) 阶段2:TTBR配置 设置页表基地址 (TTBR0/TTBR1) 阶段3:SCTLR使能 设置M/C/I位 使能MMU ⚠ 关键顺序约束 1. 必须先配置MAIR,再配置TTBR,最后使能SCTLR 2. 使能MMU前必须确保页表已位于可访问的内存区域 3. 建议在使能MMU后立即执行ISB指令同步上下文 MAIR TTBR0/1 SCTLR

3.2 MAIR寄存器配置:定义内存属性

MAIR(Memory Attribute Indirection Register)是MMU初始化时第一个必须配置的寄存器。它定义了8个内存属性编码(Attr0~Attr7),每个编码对应一个内存区域的缓存策略和访问特性。页表描述符中的AttrIndex字段通过索引MAIR编码来决定该页面的实际内存属性。

3.2.1 MAIR寄存器格式

位段 字段名 描述
[63:56] Attr7 属性编码7,用于页表AttrIndex=7
[55:48] Attr6 属性编码6,用于页表AttrIndex=6
[47:40] Attr5 属性编码5,用于页表AttrIndex=5
[39:32] Attr4 属性编码4,用于页表AttrIndex=4
[31:24] Attr3 属性编码3,用于页表AttrIndex=3
[23:16] Attr2 属性编码2,用于页表AttrIndex=2
[15:8] Attr1 属性编码1,用于页表AttrIndex=1
[7:0] Attr0 属性编码0,用于页表AttrIndex=0

3.2.2 常用内存属性编码

每个Attr字段的8位编码中,低4位定义设备内存类型,高4位定义正常内存的缓存策略。以下是嵌入式系统中常用的属性配置:

属性名称 Attr编码 内存类型 缓存策略 典型用途
Normal_WBWA 0xFF Normal Write-Back, Write-Allocate DDR内存、堆栈
Normal_NCNB 0x44 Normal Non-Cacheable, Non-Bufferable DMA缓冲区(一致性)
Device_nGnRnE 0x00 Device nG(非聚集), nR(非重排), nE(早写) MMIO寄存器
Device_nGnRE 0x04 Device nG(非聚集), nR(非重排), E(早写允许) 以太网MAC寄存器

MAIR配置示例代码(ARMv8-A汇编):

/* 配置MAIR_EL1寄存器 */
/* Attr0: 0xFF - Normal, WB, WA, R/W Allocate */
/* Attr1: 0x44 - Normal, Non-Cacheable, Non-Bufferable */
/* Attr2: 0x00 - Device, nGnRnE */
/* Attr3: 0x04 - Device, nGnRE */
/* Attr4~Attr7: 0x00 (未使用) */

ldr x0, =0x00040044FF
msr mair_el1, x0
isb

3.3 TTBR0/TTBR1寄存器配置:页表基地址

TTBR(Translation Table Base Register)寄存器保存了页表的物理基地址。ARMv8-A架构使用两个独立的页表基地址寄存器:

  • TTBR0_EL1:用于低地址空间(用户空间),虚拟地址范围由TCR.T0SZ决定
  • TTBR1_EL1:用于高地址空间(内核空间),虚拟地址范围由TCR.T1SZ决定

3.3.1 TTBR寄存器格式

位段 字段名 描述
[63:48] ASID 地址空间标识符,用于TLB区分进程
[47:1] BADDR 页表物理基地址(64KB对齐)
[0] Reserved 保留位,必须为0

3.3.2 页表基地址配置步骤

  1. 确定页表层级:根据TCR配置选择页表层级(L0~L3)
  2. 分配页表内存:在物理内存中分配页表空间(必须64KB对齐)
  3. 填充页表条目:写入页表描述符,设置物理地址映射和访问权限
  4. 写入TTBR:将页表基地址写入TTBR0/TTBR1
  5. 执行DSB+ISB:确保页表写入完成并同步

实践建议:在配置TTBR之前,建议先通过TCR寄存器设置页表大小和粒度。典型的配置是TCR使用4KB粒度、40位输入地址空间(T0SZ=24),此时TTBR指向L1页表基地址。

3.4 SCTLR寄存器:使能MMU

SCTLR(System Control Register)是MMU初始化的最后一步。该寄存器的M位控制MMU的全局使能,C位控制数据缓存,I位控制指令缓存。

3.4.1 SCTLR关键位域

位 名称 描述 推荐值
[0] M MMU使能位:0=禁用,1=使能 1
[1] A 对齐检查使能 0
[2] C 数据缓存使能 1
[3] WXN 写权限不可执行 1
[12] I 指令缓存使能 1
[25] EE 端序配置(0=小端,1=大端) 0

3.4.2 使能MMU的完整代码序列

/* 步骤1:配置TCR寄存器(页表控制) */
ldr x0, =0x80823520    /* T0SZ=24, TG0=4KB, SH0=Inner, ORGN=WBWA, IRGN=WBWA */
msr tcr_el1, x0
isb

/* 步骤2:配置MAIR寄存器(内存属性) */
ldr x0, =0x00040044FF
msr mair_el1, x0
isb

/* 步骤3:配置TTBR0(页表基地址) */
/* 假设页表基地址在x1寄存器中 */
msr ttbr0_el1, x1
isb

/* 步骤4:读取当前SCTLR值,修改M/C/I位 */
mrs x0, sctlr_el1
orr x0, x0, #(1 << 0)    /* M位:使能MMU */
orr x0, x0, #(1 << 2)    /* C位:使能数据缓存 */
orr x0, x0, #(1 << 12)   /* I位:使能指令缓存 */
msr sctlr_el1, x0
isb

/* 步骤5:使能后立即执行地址转换验证 */
/* 读取当前PC的物理地址(可选调试步骤) */
at s1e1r, x30    /* 将x30(链接寄存器)的虚拟地址转换为物理地址 */
isb

⚠ 重要安全提示:

  • 使能MMU前,必须确保当前执行地址(PC)所在的页面在页表中已正确映射,且映射属性允许执行
  • 使能MMU后,所有指令地址立即通过MMU转换,如果页表映射错误将立即触发异常
  • 建议在使能MMU前先建立恒等映射(identity mapping),确保转换过程平滑过渡
  • 使能MMU后,必须使用ISB指令刷新流水线,确保后续指令使用新的MMU配置

3.5 完整初始化流程总结

下表总结了MMU初始化的三个关键阶段及其寄存器配置要点:

阶段 寄存器 配置内容 同步指令 常见错误
1. 属性定义 MAIR_EL1 设置8个Attr编码,定义内存类型和缓存策略 ISB Attr编码与页表描述符不匹配
2. 页表基址 TTBR0_EL1 / TTBR1_EL1 写入页表物理基地址(64KB对齐) DSB + ISB 基地址未对齐或页表未初始化
3. 使能MMU SCTLR_EL1 设置M位=1,可选设置C/I位 ISB 未建立恒等映射导致跳飞
MMU寄存器配置依赖关系 MMU寄存器配置依赖关系 TCR_EL1 MAIR_EL1 TTBR0/1_EL1 SCTLR_EL1 📋 配置依赖说明 • TCR → MAIR:TCR的IRGN/ORGN字段影响MAIR中Normal属性的缓存策略 • TCR → TTBR:TCR的T0SZ/TG0决定TTBR页表的层级和粒度 • MAIR + TTBR → SCTLR:SCTLR使能前必须完成所有属性与页表配置

3.6 调试与验证方法

MMU初始化完成后,建议通过以下方法验证配置的正确性:

  • 读取寄存器回读:通过mrs x0, mair_el1等指令读取寄存器值,确认写入正确
  • 地址转换测试:使用AT指令(Address Translation)将已知虚拟地址转换为物理地址,验证映射正确性
  • TLB刷新验证:使能MMU后执行TLBI指令刷新TLB,确保旧映射被清除
  • 异常向量验证:故意访问未映射地址,确认系统能正确触发Page Fault异常

调试代码示例:验证地址转换

/* 验证虚拟地址0x80000000的物理映射 */
ldr x0, =0x80000000    /* 待转换的虚拟地址 */
at s1e1r, x0           /* 执行地址转换 */
isb
mrs x1, par_el1        /* 读取转换结果寄存器 */
/* 检查PAR_EL1[0]位:0=转换成功,1=转换失败 */
tbnz x1, #0, fault_handler