ARM 汇编入门(十二):MMU、页表与虚拟内存全解析

摘要:上一篇讲完 EL0--EL3 异常模型、VBAR、ESR、GIC 和 ERET,这一篇进入让"虚拟地址"真正落地的硬件------AArch64 MMU 。核心就四件事:TTBR0/1_EL1 选地址空间、TCR_EL1 定义切分与粒度、MAIR_EL1 定义内存属性、多级页表把 VA 翻译成 PA。再叠加 TLB 维护、Break-Before-Make、Access Flag 惰性提交、KPTI、大页与 Contiguous Hint,就是 Linux/裸机 boot 里最容易踩坑的一整块。


一、TTBR0 / TTBR1:天然的内核/用户地址空间切分

AArch64 用最高位决定走哪张根表:

  • VA63:56 == 0x00 → 用 TTBR0_EL1(用户空间,低地址)
  • VA63:56 == 0xFF → 用 TTBR1_EL1(内核空间,高地址)
  • 两者都不匹配 → Address Size Fault

好处:

  • 用户/内核页表物理分离,上下文切换只换 TTBR0_EL1,TTBR1_EL1 不动
  • AP 位直接表达权限:用户页 AP=01/11(EL1+EL0 可访问),内核页 AP=00(仅 EL1)
  • Spectre/Meltdown 后 Linux 加 KPTI:EL0 时把 TTBR1 换成只映射异常向量的 trampoline 表,进 EL1 再切回全内核表

二、TCR_EL1:切分点与粒度

  • T0SZ :TTBR0 空间大小 = 2^(64−T0SZ) 字节。Linux 常用 T0SZ=25 → 512 GB 用户空间(0x0000...0000 ~ 0x0000_007F_FFFF_FFFF)
  • T1SZ:TTBR1 空间同理,从最高位往下长
  • TG0 / TG1 :粒度 00=4KB、01=64KB、10=16KB(4KB 最通用,64KB 降 walk 深度适合 HPC/NUMA)
  • IPS:物理地址位宽
  • SH0/SH1、ORGN0/IRGN0:缓存共享与内外属性默认策略

4KB 粒度 + T0SZ=25 时,L0 只用少数几项(因为高 17 位被 T0SZ 砍掉),L0→L1→L2→L3 四级 walk,每级 512 项 × 8 字节 = 4KB 一页。


三、VA→PA 手算示例(4KB 粒度,T0SZ=25)

翻译 0x0000_0040_1234_5678:

  1. Top-bit:VA63:56=0x00 → 走 TTBR0
  2. L0 索引 VA47:39 = 0b000000010 = 2 → 读 L02
  3. L1 索引 VA38:30 = 0 → 若 L10 是 1GB Block,直接 PA=Block_base|VA29:0;否则 Table→L2
  4. L2 索引 VA29:21 = 0b010010001 = 145 → 2MB Block 则 PA=Block_base|VA20:0
  5. L3 索引 VA20:12 = 0b001000101 = 69 → 4KB Page,PA=Page_base|VA11:0(offset 0x678)

最坏 4 次串行内存读 ≈ 40 周期;命中 TLB 后接近单周期。


四、MAIR_EL1:8 个属性槽,描述符用 AttrIndx2:0 选

每个 8 位编码,典型内核配置:

Index 编码含义 用途
0 Device-nGnRnE MMIO 寄存器,严格顺序、不可重排
1 Normal, Inner/Outer Write-Back, RA/WA 普通缓存 RAM
2 Normal, Non-Cacheable DMA buffer、一致性旁路
3~7 留作 Device-nGRE / GRE / Normal NC 等变体
  • Device 内存:访问按程序序,对同一外设不重排;不同外设间允许重排(nGnRnE 最严,GRE 最松)
  • Normal 内存 :允许乱序+推测执行,跨核/跨外设必须自己加 DMB/DSB/ISB

页表描述符里 AttrIndx 3 位指向 MAIR 槽位,配合 SH(shareability)和 AP(权限)一起决定最终访问语义。


五、描述符格式(4KB 粒度,8 字节/项)

  • bits1:0:
    • 00/10 = Invalid
    • 11 = Table(指向下一级表基址)
    • 01 在 L3 = Page;在 L1/L2 = Block(1GB / 2MB)
  • AP7:6 :00=EL1 RW/EL0 无、01=EL1+EL0 RW、10=EL1 RO、11=EL1+EL0 RO
  • UXN54/PXN53:用户/特权不可执行
  • AF10:Access Flag,首次访问触发 AF fault,软件置 1(惰性提交,省 TLB 预热)
  • SH9:8:0=None, 1=Inner, 2=Outer, 3=Inner Share
  • Contiguous52:16 个连续 4KB L3 页可合并成 64KB TLB 项
  • nG11:非全局(按 ASID 隔离)

Block 映射能砍掉一级 walk:L1 放 1GB Block、L2 放 2MB Block,Linux 内核镜像和大匿名映射大量用 2MB 大页。


六、启动身份映射(Identity Map)最小骨架

MMU 开启瞬间 PC 还在物理地址跑,所以先建 VA==PA 的恒等映射:

  • 一张 L0 表(4KB,512 项)
  • L00 → L1 表
  • L1 里放 4 个 1GB Block 覆盖 0x0 ~ 0xFFFF_FFFF:
    • 0x0000_0000--0x3FFF_FFFF → Device-nGnRnE(AttrIndx=0)
    • 0x4000_0000--0x7FFF_FFFF → Normal RW Cache(AttrIndx=1, AP=00)
    • 0x8000_0000--0xBFFF_FFFF → Normal RO+PXN(AttrIndx=1, AP=10, PXN=1)
    • 0xC000_0000--0xFFFF_FFFF → Normal RW Non-Cacheable(AttrIndx=2)

然后:

复制代码
MSR MAIR_EL1, X0
MSR TCR_EL1,  X1
MSR TTBR0_EL1, X2
DSB  ISH
MSR SCTLR_EL1, X3     ; 置 M=1 (bit0), C=1, I=1
ISB

顺序铁律:先刷 TLBI、DSB 同步,再开 M;开 M 后立刻 ISB,否则后面指令还按旧翻译取指。


七、TLB 维护与 Break-Before-Make(BBM)

改活页表项(改 PA、改权限、改属性)必须按序:

  1. 写 invalid 描述符到该表项
  2. DSB ISH
  3. TLBI VAE1IS, Xt(Xt = ASID<<48 | (VA>>12))
  4. DSB ISH
  5. 写新 valid 描述符
  6. DSB ISH + ISB

跳过任何一步 → 别的核 TLB 里可能同时命中新旧映射,行为未定义。只读收紧(RW→RO)理论上可放宽,但工程默认全走 BBM 最稳。

常用 TLBI:

  • TLBI VMALLE1IS:本 ASID 全失效
  • TLBI VAE1IS, Xt:单页失效
  • TLBI ASIDE1IS, Xt:按 ASID 全失效
  • TLBI IPAS2E1IS:Stage-2(Hypervisor 用)

八、Access Flag 惰性提交

描述符 AF=0 时,第一次访问触发 Data Abort,ESR_EL1 EC=0b100100(0x24),DFSC=0b001001。Handler 做:

  1. 读 FAR_EL1 拿坏 VA
  2. 从 TTBR0 走页表找到 L3 项
  3. 原子 ORR 置 bit10(AF=1)
  4. TLBI VAE1IS + DSB + ISB
  5. ERET 重执行原指令

线性扫 64KB(16 个 4KB 页)正好 16 次 AF fault------这就是 Linux 懒分配页表的来源:建表时全 AF=0,用到才付 TLB 预热成本。


九、Stage-2 与 Hypervisor 一句话

EL2 开 Stage-2 后,VA→IPA(Stage-1,TTBR_EL1)→PA(Stage-2,VTTBR_EL2)。KVM 里每个 VM 一套 Stage-2,设备直通改 Stage-2 权限就能做内存隔离,不用动 guest 自己的页表。


十、本文核心复盘

  • TTBR0(低 VA,用户)/ TTBR1(高 VA,内核)按 VA63:56 自动选,切换只换 TTBR0
  • TCR_EL1:T0SZ/T1SZ 定空间大小,TG0/TG1 定 4K/16K/64K 粒度
  • MAIR_EL1 八槽属性,描述符 AttrIndx 选 Device / Normal WB / Normal NC
  • 4KB 粒度四级 walk,L1/L2 Block 做大页,Contiguous52 合并 16×4KB→64KB TLB 项
  • AP/UXN/PXN/AF/SH/nG 组合出权限与缓存语义
  • 开 MMU 顺序:建表→MAIR/TCR/TTBR→DSB→SCTLR.M=1→ISB
  • 改活映射必须 Break-Before-Make:invalid→DSB→TLBI→DSB→new→DSB+ISB
  • AF=0 触发 EC=0x24/DFSC=0x9,handler 置 AF 位再 TLBI 重执行
  • KPTI 用 trampoline TTBR1 挡住 EL0 对内核空间的推测读
相关推荐
wuyk55517 小时前
嵌入式故障诊断-Bug002-局部大数组压栈高温必死机
stm32·单片机·嵌入式硬件·mcu·物联网·51单片机·嵌入式实时数据库
Dev_XH21 小时前
【成品设计】基于沁恒单片机的电动汽车充电站智慧调度系统
stm32·单片机·嵌入式硬件·物联网
Zw-awa1 天前
嵌入式OLED表驱动菜单:用一张表、一个栈和一个窗口重写表驱动菜单
stm32
蜀黍@猿1 天前
ARM 汇编入门(十):标量浮点与 VFP 指令全解析
汇编·arm开发·stm32·mcu
不脱发的程序猿1 天前
STM32和VS1053制作电子琴
stm32·单片机·嵌入式硬件·midi·vs1053
Zw-awa2 天前
C语言函数指针与宏实战:从回调、表驱动菜单到X-Macro,讲透嵌入式代码解耦
stm32
无敌贵点大王2 天前
从寄存器层面重学STM32(9)——DMA理论
stm32·单片机·嵌入式硬件
Zw-awa3 天前
用两个按键做一个OLED菜单:先把选择和执行分开
stm32
Good白10193 天前
嵌入式:1.嵌入式入门|初识嵌入式、处理器、ARM与STM32
arm开发·stm32·嵌入式硬件