• 正文
  • 相关推荐
申请入驻 产业图谱

一篇文章讲清楚MMU地址翻译!

09/22 08:04
189
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

ARMv8-A 用的是虚拟内存CPU 里跑的地址是虚拟地址,经过 MMU 翻译成物理地址才能访问内存。这个翻译过程硬件自动完成,MMU 会去内存里读翻译表(Translation Table),这一套动作叫 Table Walk。

MMU 还有一个关键作用:让多个任务同时跑在各自的虚拟地址空间里,彼此不感知对方的物理地址位置。每个程序看到的虚拟地址空间都是连续的,底层物理内存是不是连续它不关心。

翻译表长这样:

MMU 用虚拟地址的高位去索引翻译表的条目,确定访问的是哪个内存块。它还管内存访问权限、内存布局和缓存策略。

内核和用户空间的地址分离

每个进程有自己的翻译表,内核切进程时也要切页表。但内核的映射很少变。ARMv8 用几个寄存器来处理这个。

TTBR0_EL1 和 TTBR1_EL1 是翻译表基地址寄存器

TTBR0_EL1

    •  — 用户空间(EL0)的页表基址。用户地址高位全 0,范围

0x00000000_00000000 ~ 0x0000FFFF_FFFFFFFFTTBR1_EL1

    •  — 内核空间(EL1)的页表基址。内核地址高位全 1,范围

0xFFFF0000_00000000 ~ 0xFFFFFFFF_FFFFFFFF

虚拟地址虽然 64 位宽,但实际只用 48 位来映射物理地址。

EL2 和 EL3 只有 TTBR0,没有 TTBR1。意味着这两个异常级别只用 0x0 ~ 0x0000FFFF_FFFFFFFF 这一段。

内核空间和用户空间可以映射到同一片物理地址:

翻译控制寄存器 TCR_EL1 控制高位地址的位数。T0SZ 和 T1SZ 分别指定用户和内核空间的地址范围大小,具体值跟翻译粒度和页表起始地址有关。

TCR_EL1 还控制地址范围和翻译粒度:

IPS:最大输出物理地址大小(000=32 位,101=48 位)

TG1 / TG0:内核/用户空间的翻译粒度(00=4KB, 01=16KB, 11=64KB)

翻译表可以有三或四级。第一级从哪开始查,由翻译粒度、TCR_ELn 和 TxSZ 共同决定。

虚拟地址到物理地址

CPU 发出一个虚拟地址时,MMU 开始翻译。假设 n 位虚拟地址,高位的 VA[63:n] 必须全 0 或全 1,否则触发地址错误。低位做偏移。

举个单级查找的例子,假设 42 位虚拟地址、64KB 粒度:

    VA[63:42] = 1 用 TTBR1,= 0 用 TTBR0VA[41:29] 索引第一级翻译表(8192 个 64 位条目),MMU 读对应条目MMU 检查条目有效性和访问权限条目大小 512MB(8192 × 64),物理地址的 [47:29] 位来自条目VA[28:0] 作为偏移直接对应 PA[28:0]

单级查找分区太粗,实际用多级页表。一级描述符可以指向二级翻译表地址。下图是 stage1 + stage2 都是 64KB 粒度的两级查找:

多级流程:

    VA[63:42] 决定 TTBR1 还是 TTBR0VA[42:29] 索引第一级 8192 个条目第二级条目指向第三级翻译表地址VA[28:16] 索引第三级条目第三级条目指向 64KB 页PA[47:16] 来自第三级条目,VA[15:0] 直接对应 PA[15:0]

安全和物理地址

ARM 定义了两个物理地址空间:安全和非安全。非安全世界只能访问非安全空间。安全世界使能 MMU 后,通过翻译表可以同时访问两种空间。

这会引起缓存一致性问题。比如安全的 0x8000 和非安全的 0x8000 是不同的物理地址,但它们可以同时出现在缓存里。非安全访问安全内存会被阻止,但缓存里可能有两份数据。安全世界访问非安全内存时,需要用非安全方式访问。

多重虚拟地址空间

任何时候只有一个虚拟地址空间在使用。但三个异常级别有三组 TTBR,所以有三个并行的虚拟地址空间(EL0/1、EL2、EL3):

EL1/0 分安全和非安全。但物理寄存器只有一组 TTBR0_EL1、TTBR1_EL1、TCR_EL1,切换世界时需要安全监视器保存和恢复它们。

MMU 禁用时的行为

MMU 使能时翻译表控制内存属性。MMU 禁用时(如复位后),内存用默认属性:

指令获取默认是普通内存,可缓存属性由 SCTL_ELx.I 控制

    • I=0:非缓存,Outer ShareableI=1:可缓存,Write-Through Read-Allocate No Write-Allocate

Cache 读写简单理解:

读 miss:Read through(直接读内存)或 Read allocate(先加载到缓存再读) 写 hit:Write through(同时写缓存和内存)或 Write back(只写缓存,再 flush) 写 miss:Write allocate(先加载到缓存再写)或 No write allocate(直接写内存)

Outer Shareable:访问的数据会缓存到所有核心的缓存中。

配置和使能 MMU

写 MMU 控制寄存器要等上下文同步事件发生后才会生效:

MSR TTBR0_EL1, X0    // 设置 TTBR0
MSR TTBR1_EL1, X1    // 设置 TTBR1
MSR TCR_EL1, X2      // 设置 TCR
ISB                  // 确保上述配置在 MMU 使能前已生效
MRS X0, SCTLR_EL1    // 读系统控制寄存器
ORR X0, X0, #1       // 设置 [M] 位使能 MMU
MSR SCTLR_EL1, X0    // 写回
ISB

这里要求直接映射——保证写完 SCTL_EL1.M 后下一条指令能正确执行。

翻译表格式

Armv8-A 支持三种格式:

    AArch64 长描述符格式(主要用于 64 位)Armv7-A 长描述符格式(兼容 32 位)Armv7-A 短描述符格式(只在 EL0/EL1 stage1 用)

AArch64 长描述符格式和带大物理地址扩展的 v7 长描述符类似,增加了 0 级表索引,最多支持 48 位输入输出地址。

地址的 [63:48] 要么全 0 全 1,要么高 8 位用作 VA 标记——架构不支持完整的 64 位寻址。

AArch64 支持三种翻译粒度:4KB、16KB、64KB。粒度越大,需要的翻译表级数越少。芯片支持哪些粒度可以读 ID_AA64MMFR0_EL1 寄存器。

描述符格式

所有级别的翻译表都用同一种描述符格式。0 级描述符只能指向 1 级表,3 级描述符只能输出块地址,不能再指向下级表。

描述符 [1:0] 位标识类型:

    下一级表的地址(继续细分)内存块地址(大小可变)故障或无效

粒度影响

粒度大小决定翻译表级数和大小。如果 VA 范围限制在 39 位内,0 级表可以省略。

以 4KB 粒度为例,如果 TTBCR 配置低地址只跨越 1GB,0 级和 1 级都不需要,从 2 级开始查,到 3 级就到 4KB 页面了。

4KB 粒度

最多 4 级查找。48 位地址每级用 9 位索引(512 个条目),最后 12 位在 4KB 页内做偏移。

参考:

    https://blog.csdn.net/yhb1047818384/article/detail

相关推荐

作者就职于某500强公司,担任BSP工程师。具有丰富的嵌入式开发经验。专栏主要分享计算机基础,操作系统,Linux驱动开发,Arm体系与架构,C/C++,数据结构与算法等相关文章。欢迎关注我的公众号【嵌入式与Linux那些事】,一起学习交流。