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),不进行任何缓存或重排。
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 页表创建步骤
- 分配页表内存:在物理内存中分配16KB对齐的一级页表空间(4096条目 × 4字节)。
- 清零页表:将所有条目初始化为0(无效映射)。
- 填充段映射:对于1MB对齐的大块内存区域,直接设置段描述符。
- 创建二级页表:对于需要4KB粒度的区域,分配二级页表并填充页描述符。
- 设置属性:为每个条目配置访问权限、缓存策略、可执行位等。
关键注意事项: 页表本身必须位于可缓存的内存区域,否则每次TLB未命中都会导致极慢的页表遍历。通常将页表放置在DDR的不可缓存区域或内部SRAM中。
4.3 MMU使能
页表创建完成后,即可使能MMU。使能过程需要精确控制指令顺序,避免出现地址转换不一致的情况。
4.3.1 使能步骤
- 设置TTBR0/TTBR1:将一级页表基地址写入CP15的c2寄存器。
- 设置域访问控制:配置CP15的c3寄存器,设置域权限。
- 设置系统控制寄存器:配置CP15的c1寄存器,使能MMU、I-Cache、D-Cache。
- 同步指令:执行DSB和ISB指令,确保配置生效。
- 跳转到虚拟地址:使用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切换到虚拟地址空间。如果跳转目标地址与当前物理地址不一致,将触发页错误。
4.4 缓存一致性设置
缓存一致性是MMU初始化中最容易被忽视但后果最严重的环节。当MMU使能后,CPU会使用缓存来加速内存访问,但这也带来了缓存与物理内存之间数据不一致的风险。
4.4.1 缓存类型与策略
| 缓存类型 | 作用 | 一致性风险 | 典型配置 |
|---|---|---|---|
| I-Cache (指令缓存) | 缓存指令,减少取指延迟 | 自修改代码(SMC) | 使能 + 写通(Write-Through) |
| D-Cache (数据缓存) | 缓存数据,减少访存延迟 | DMA与CPU共享数据 | 写回(Write-Back)或写通 |
| L2 Cache | 统一缓存指令和数据 | 多核共享数据 | 写回 + 硬件一致性 |
4.4.2 缓存一致性设置步骤
- 使能前清空缓存:在使能MMU之前,确保I-Cache和D-Cache被无效化(Invalidate)。
- 配置页表属性:在页表条目中设置C(Cacheable)和B(Bufferable)位。
- 设置内存类型:使用MAIR(Memory Attribute Indirection Register)定义内存属性。
- 配置共享属性:设置Shareable位,控制多核一致性。
- 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)。
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缓冲区的缓存策略,可以构建一个稳定高效的内存管理系统。