3. 内存管理单元(MMU)基础:页表结构、TLB与缓存操作
好,咱们进入第三章。MMU,说白了就是CPU的地址翻译官。你写的程序里用的虚拟地址,怎么变成物理内存上的真实地址?全靠它。我早年做第一个Cortex-A项目时,上来就把MMU关了跑裸机,结果一开MMU系统直接挂——后来才发现是页表配置错了。嗯,这章咱们就把这块啃透。
3.1 页表结构:4KB与64KB粒度
ARMv8的页表,用的是多级页表。说白了就是查表找地址,一级一级往下翻。粒度有两种常用:4KB和64KB。我个人习惯,大部分场景用4KB,内存紧张时考虑64KB。
3.1.1 4KB粒度页表结构
4KB粒度下,页表分4级:
- Level 0: 覆盖整个地址空间,每个表项指向下一级表
- Level 1: 每个表项覆盖1GB区域
- Level 2: 每个表项覆盖2MB区域
- Level 3: 每个表项覆盖4KB区域——这就是最终页
你想想看,查一次地址要经过4次内存访问,效率是不是有点低?所以ARM引入了TLB来缓存最近用过的翻译结果。这个后面会讲。
3.1.2 64KB粒度页表结构
64KB粒度下,页表只有3级:
- Level 0: 覆盖整个地址空间
- Level 1: 每个表项覆盖64GB区域
- Level 2: 每个表项覆盖64KB区域——最终页
我在项目中遇到过,用64KB粒度时页表本身占的内存更少,但内部碎片会多一些。适合大块连续内存的场景。
| 特性 | 4KB粒度 | 64KB粒度 |
|---|---|---|
| 页表级数 | 4级 | 3级 |
| 最小页大小 | 4KB | 64KB |
| 页表内存占用 | 较大 | 较小 |
| 内部碎片 | 少 | 多 |
| 适用场景 | 通用、细粒度控制 | 大块连续内存、减少页表层级 |
3.2 转换表描述符
页表里的每一项,叫描述符。它不光存地址,还带了一堆属性。我刚开始看ARM手册时,被这些位域搞得头晕。后来总结了一下,其实就三类:
- 无效描述符: 这一项没映射,访问就触发缺页异常
- 表描述符: 指向下一级页表(非叶子节点)
- 块描述符/页描述符: 指向物理内存块(叶子节点)
描述符里还有几个关键字段:
- AttrIndx[2:0]: 指向MAIR(内存属性寄存器)的索引,决定这块内存的缓存策略
- NS: 非安全空间位,安全世界和非安全世界切换时用
- AP[2:1]: 访问权限位,控制读/写权限
- SH[1:0]: 共享属性,决定多核间的缓存一致性
- AF: 访问标志位,硬件访问后自动置1
3.3 TLB与缓存操作
TLB,全称Translation Lookaside Buffer。说白了就是页表的硬件缓存。每次查页表都要走内存,太慢了,TLB把最近用过的翻译结果存下来,下次直接命中。
但TLB有个坑——它不会自动感知页表的变化。你改了页表,TLB里还是旧数据。这时候就需要手动操作TLB。
3.3.1 TLB失效操作
ARMv8提供了几条指令来管理TLB:
// 使所有TLB条目失效(EL1下)
TLBI VMALLE1
// 使特定虚拟地址的TLB条目失效
TLBI VAE1, x0 // x0里放虚拟地址
// 使特定ASID的TLB条目失效
TLBI ASIDE1, x0 // x0里放ASID
// 同步指令,确保TLB操作完成
DSB ISH
ISB
3.3.2 缓存操作
ARMv8的缓存分L1、L2、L3。操作缓存时,要清楚你在哪个异常级别。常用的缓存操作指令:
- DC CIVAC: 清理并使无效(Clean and Invalidate)——写回脏数据并失效
- DC CVAC: 只清理(Clean)——写回脏数据
- DC IVAC: 只使无效(Invalidate)——丢掉缓存数据
- IC IALLU: 使所有指令缓存失效
// 清理并使无效一个缓存行(按虚拟地址)
DC CIVAC, x1
// 同步
DSB SY
// 使指令缓存失效
IC IALLU
DSB SY
ISB
3.4 内存属性与域控制
内存属性,说白了就是告诉MMU这块内存该怎么对待。ARMv8用MAIR(Memory Attribute Indirection Register)来定义属性。
3.4.1 MAIR寄存器
MAIR有8个槽位,每个槽位8位,定义一种内存属性。我常用的配置:
// 设置MAIR
// 槽位0:正常内存,写回策略,读分配
// 槽位1:设备内存,非缓存
// 槽位2:正常内存,直写策略
LDR x0, =0x00FF44EE // 示例值,具体按需配置
MSR MAIR_EL1, x0
| 槽位 | 属性 | 典型用途 |
|---|---|---|
| Attr0 | 正常内存,写回,读分配 | 普通数据区 |
| Attr1 | 设备内存,非缓存 | MMIO、外设寄存器 |
| Attr2 | 正常内存,直写 | 帧缓冲区、共享内存 |
| Attr3 | 正常内存,不可缓存 | DMA缓冲区 |
3.4.2 域控制
域控制,在ARMv8里其实已经弱化了。早期ARM有域控制寄存器,用来做内存访问权限的粗粒度控制。到了v8,域控制基本被页表里的AP位和PXN/UXN位替代了。
不过有个概念要记住:XN(Execute Never)位。标记了不可执行的页面,防止代码注入攻击。我在做安全启动时,把关键数据区都设了XN,防止被恶意代码执行。
3.5 知识体系总览
下面这张图,把MMU的核心模块串起来了。你看一遍,心里就有谱了。
这张图从左到右,就是一次完整的内存访问流程。虚拟地址进来,先查TLB。命中就直接拿物理地址。没命中就走页表查询,一级一级解析描述符,最后拿到物理地址和内存属性。嗯,流程清楚了,后面写代码心里就有底了。