4. MMU初始化流程:系统启动时MMU关闭、页表创建、MMU使能、缓存一致性设置

MMU(Memory Management Unit,内存管理单元)的初始化是系统启动过程中最关键的步骤之一。在嵌入式Linux或裸机系统中,MMU的配置直接决定了虚拟地址到物理地址的映射关系、内存访问权限以及缓存策略。本章将详细拆解MMU初始化的四个核心阶段:启动时MMU关闭状态、页表创建、MMU使能以及缓存一致性设置。

核心要点: MMU初始化必须遵循严格的顺序——先关闭MMU和缓存,再构建页表,最后使能MMU并配置缓存策略。任何顺序错误都可能导致系统崩溃或数据不一致。

4.1 系统启动时MMU关闭状态

当CPU复位或上电时,MMU默认处于关闭状态。此时CPU直接使用物理地址访问内存和外设寄存器。这一阶段通常由启动代码(Boot ROM或SPL)执行,主要完成以下工作:

  • 设置异常向量表:将异常向量表放置在物理地址0x0或特定地址。
  • 初始化关键外设:如时钟、DDR控制器、串口等。
  • 配置栈指针:为C语言运行环境准备栈空间。
  • 跳转到主初始化函数:通常为C语言编写的mmu_init()。
实践提示: 在ARM Cortex-A系列处理器中,复位后默认使用SVC模式,且MMU、I-Cache、D-Cache均处于关闭状态。此时所有内存访问均为“强序”(Strongly Ordered),不进行任何缓存或重排。
系统启动时MMU关闭状态下的内存访问路径 CPU 核心 MMU (关闭状态) 物理内存 / DDR 物理地址 0x0000_0000 外设寄存器 物理地址 0xE000_0000 说明:MMU关闭时,CPU发出的所有地址均为物理地址,直接访问内存或外设,无地址转换、无缓存、无权限检查。 此时系统运行在“平坦内存模型”下,所有地址一一对应物理空间。

4.2 页表创建

页表是MMU进行地址转换的依据。在ARMv7-A架构中,页表分为一级页表(L1)和二级页表(L2)。一级页表包含4096个条目,每个条目覆盖1MB地址空间;二级页表进一步将1MB细分为4KB页面。

4.2.1 页表结构选择

页表级别 条目数 覆盖范围 粒度 典型用途
一级页表 (L1) 4096 4GB 1MB (段映射) 大块内存映射、外设映射
二级页表 (L2) 256 / 页表 1MB 4KB (页映射) 精细权限控制、DMA缓冲区

4.2.2 页表创建步骤

  1. 分配页表内存:在物理内存中分配16KB对齐的一级页表空间(4096条目 × 4字节)。
  2. 清零页表:将所有条目初始化为0(无效映射)。
  3. 填充段映射:对于1MB对齐的大块内存区域,直接设置段描述符。
  4. 创建二级页表:对于需要4KB粒度的区域,分配二级页表并填充页描述符。
  5. 设置属性:为每个条目配置访问权限、缓存策略、可执行位等。
关键注意事项: 页表本身必须位于可缓存的内存区域,否则每次TLB未命中都会导致极慢的页表遍历。通常将页表放置在DDR的不可缓存区域或内部SRAM中。
ARMv7-A 页表层级结构 一级页表 (L1) 4096 条目 × 4字节 覆盖 4GB 地址空间 条目[0]: 段描述符 → 1MB 条目[1]: 段描述符 → 1MB 条目[2]: 指向二级页表 条目[3]: 无效 (fault) ... 二级页表 (L2) 256 条目 × 4字节 覆盖 1MB 地址空间 页[0]: 4KB 可读写 页[1]: 4KB 只读 页[2]: 4KB 不可缓存 ... 物理内存 0x0000_0000 0x0010_0000 0x0020_0000 0x0030_0000 ... 一级页表条目可以指向1MB物理段(段映射),也可以指向二级页表(页映射)。二级页表将1MB细分为256个4KB页面。 TTBR0/1 寄存器指向一级页表基地址,CP15协处理器控制页表切换。

4.3 MMU使能

页表创建完成后,即可使能MMU。使能过程需要精确控制指令顺序,避免出现地址转换不一致的情况。

4.3.1 使能步骤

  1. 设置TTBR0/TTBR1:将一级页表基地址写入CP15的c2寄存器。
  2. 设置域访问控制:配置CP15的c3寄存器,设置域权限。
  3. 设置系统控制寄存器:配置CP15的c1寄存器,使能MMU、I-Cache、D-Cache。
  4. 同步指令:执行DSB和ISB指令,确保配置生效。
  5. 跳转到虚拟地址:使用PC相对跳转或长跳转,进入虚拟地址空间。
典型代码示例(ARM汇编):
@ 假设一级页表基地址存储在 r0 中
@ 设置 TTBR0
MCR p15, 0, r0, c2, c0, 0

@ 设置域访问控制:所有域为“客户”模式
MOV r1, #0xFFFFFFFF
MCR p15, 0, r1, c3, c0, 0

@ 读取系统控制寄存器
MRC p15, 0, r2, c1, c0, 0

@ 使能 MMU (bit0)、I-Cache (bit12)、D-Cache (bit2)
ORR r2, r2, #0x1       @ 使能 MMU
ORR r2, r2, #0x1000    @ 使能 I-Cache
ORR r2, r2, #0x4       @ 使能 D-Cache

@ 写入系统控制寄存器
MCR p15, 0, r2, c1, c0, 0

@ 数据同步屏障
DSB

@ 指令同步屏障
ISB

@ 跳转到虚拟地址空间中的函数
LDR r3, =virtual_main
BX r3
陷阱提示: 使能MMU后,程序计数器(PC)仍然指向物理地址。必须立即执行一个跳转指令,将PC切换到虚拟地址空间。如果跳转目标地址与当前物理地址不一致,将触发页错误。
MMU使能流程 步骤1: 设置TTBR0 写入一级页表基地址 步骤2: 设置域访问控制 CP15 c3 寄存器 步骤3: 使能MMU和缓存 CP15 c1 寄存器 步骤4: DSB + ISB 同步指令 步骤5: 跳转到虚拟地址 PC切换到虚拟地址空间 使能MMU后,所有后续指令地址和内存访问都将经过MMU转换。DSB确保之前所有内存访问完成,ISB刷新指令流水线。

4.4 缓存一致性设置

缓存一致性是MMU初始化中最容易被忽视但后果最严重的环节。当MMU使能后,CPU会使用缓存来加速内存访问,但这也带来了缓存与物理内存之间数据不一致的风险。

4.4.1 缓存类型与策略

缓存类型 作用 一致性风险 典型配置
I-Cache (指令缓存) 缓存指令,减少取指延迟 自修改代码(SMC) 使能 + 写通(Write-Through)
D-Cache (数据缓存) 缓存数据,减少访存延迟 DMA与CPU共享数据 写回(Write-Back)或写通
L2 Cache 统一缓存指令和数据 多核共享数据 写回 + 硬件一致性

4.4.2 缓存一致性设置步骤

  1. 使能前清空缓存:在使能MMU之前,确保I-Cache和D-Cache被无效化(Invalidate)。
  2. 配置页表属性:在页表条目中设置C(Cacheable)和B(Bufferable)位。
  3. 设置内存类型:使用MAIR(Memory Attribute Indirection Register)定义内存属性。
  4. 配置共享属性:设置Shareable位,控制多核一致性。
  5. DMA缓冲区特殊处理:将DMA缓冲区设置为不可缓存或使用缓存维护指令。
页表属性配置示例:
/* 页表描述符格式 (ARMv7-A 段描述符) */
/* bit[1:0] = 0b10 表示段描述符 */
/* bit[3:2] = 域 (Domain) */
/* bit[4]   = 可执行 (XN) */
/* bit[5:4] = 访问权限 (AP) */
/* bit[6]   = 共享 (Shareable) */
/* bit[7:6] = 缓存策略 (TEX) */
/* bit[8]   = 不可缓存 (C) */
/* bit[9]   = 写通/写回 (B) */

/* 普通内存:写回、可缓存、可共享 */
#define MEM_ATTR_WRITEBACK    (0x1 << 2)  /* TEX=0, C=1, B=1 */
/* 外设内存:不可缓存、强序 */
#define PERIPH_ATTR           (0x0 << 2)  /* TEX=0, C=0, B=0 */
/* DMA缓冲区:不可缓存、不可共享 */
#define DMA_BUF_ATTR          (0x0 << 2)  /* TEX=0, C=0, B=0 */
DMA缓冲区一致性陷阱: 如果DMA缓冲区被设置为可缓存,CPU读取时将命中缓存,而DMA直接写入物理内存,导致CPU读到旧数据。解决方案:将DMA缓冲区映射为不可缓存(Strongly Ordered),或在每次DMA传输前后执行缓存维护指令(如DC CIVAC)。
缓存一致性设置与DMA交互 CPU D-Cache (可缓存区域) 缓存维护指令 DC CIVAC / DC ISW 物理内存 DMA缓冲区 DMA控制器 直接访问物理地址 缓存一致性关键策略 1. DMA缓冲区映射为不可缓存(Strongly Ordered),避免缓存一致性问题。 2. 若必须使用缓存,则在DMA传输前执行 Clean(写回),传输后执行 Invalidate(无效化)。 3. 使用硬件一致性(如ARM的ACE/CHI总线)可自动维护缓存一致性,但需要SoC支持。

4.5 完整初始化流程总结

下表总结了MMU初始化的完整流程、关键操作和常见错误:

阶段 关键操作 寄存器/指令 常见错误
1. 关闭MMU 确保MMU位为0,清空缓存 CP15 c1, DC ISW 未清空缓存导致使能后数据错乱
2. 创建页表 分配内存、填充描述符 TTBR0/TTBR1 页表未对齐、属性配置错误
3. 使能MMU 设置TTBR、域控制、系统控制 MCR, DSB, ISB 未同步指令、跳转地址错误
4. 缓存配置 设置MAIR、页表缓存位 MAIR0/1, 页表属性 DMA缓冲区误设为可缓存
调试建议: 在MMU使能后的第一时间,读取CP15的c2寄存器(TTBR0)和c1寄存器(系统控制),确认MMU已正确使能。同时检查页表条目,确保虚拟地址到物理地址的映射符合预期。

MMU初始化是系统启动的基石,任何疏忽都可能导致系统崩溃或数据损坏。通过严格遵循关闭MMU → 创建页表 → 使能MMU → 配置缓存一致性的顺序,并特别注意DMA缓冲区的缓存策略,可以构建一个稳定高效的内存管理系统。