Learn The Architecture Memory Management 译文
1、概述
本文档介绍了ARMv8-A架构内存管理的关键——内存地址转换,包括虚拟地址(VA)到物理地址(PA)的转换、页表(或称地址转换表)格式以及TLBs(Translation Lookaside Buffers)管理。
对于任何进行底层bootloader或者驱动代码开发的人员来说,这部分内容都是非常实用的,尤其是对进行MMU(Memory Management Unit)编码的人员。
本文档可以帮助你解到VA如何转换成PA的、如何识别不同的地址空间、地址转换时地址空间是如何映射的以及TLB相关的操作。
2、什么是内存管理?
内存管理描述了如何访问系统内存。每次操作系统或者应用程序尝试访问内存时,都是硬件负责进行内存管理的,对于应用程序而言,内存管理是一种动态分配内存区域的方式。
2.1、为什么需要内存管理?
因为操作系统和应用程序需要大量的内存来运行,同时,应用程序往往运行在虚拟地址空间,需要实际映射到物理地址空间。
3、虚拟地址和物理地址
使用虚拟地址的一个好处是,操作系统可以控制应用程序的内存布局,操作系统决定虚拟地址是否可见、是否允许访问。这种机制允许操作系统采取沙箱机制管理(即应用程序之间的隔离)应用程序,同时实现了对硬件的抽象。
还有一个好处是,物理内存上不连续的内存区域,在虚拟内存地址空间中可以是连续的。
对于应用程序开发人员而言,他们不需要关心具体的物理内存地址是否可以访问。
实际上,每一个应用程序都维护一个独立的虚拟内存地址空间,它们被映射到不同的物理内存区域。操作系统在进行应用程序切换时负责重构虚拟内存地址空间,这就意味着当前应用程序的虚拟地址总是映射到正确的物理地址。
虚拟地址通过映射的方式转换为物理地址。虚拟地址和物理地址的映射关系保存在页表(page tables,或称translation tables地址转换表)中,如下图所示:
页表保存在内存中,由操作系统或者hypervisor(虚拟化技术)管理。页表不是静态的,而是根据应用程序对内存的需求及时更新的,页表更新的同时,也改变了虚拟地址和物理地址的映射关系。
4、MMU
MMU负责地址转换,包括:
1)table walk unit,负责从内存中读取页表;
2)TLBs,负责缓存最近使用的页表项;
需要强调的是,应用程序发起的任何内存地址访问都是虚拟地址。虚拟地址被发送给MMU,MMU首先在TLBs中查找是否有对应的页表项,如果没有找到对应的页表项,那么table walk unit会从内存中读取合适的页表项(table entry)。
访问物理内存之前,必须完成虚拟地址到物理地址的转换。对于访问缓存数据而言,同样需要提前完成VA-2-PA的转换,因为ARMv6以后的处理器都将缓存数据通过物理地址的方式(physically tagged)缓存,因此,必须在cache lookup之前完成VA-2-PA转换。ARM CPU包含数据缓存和指令缓存,可以理解为片内物理内存,各自拥有独立的物理地址段。
4.1、Table Entry 页表项
虚拟地址空间被划分为以页(32位系统下页大小通常为4KB)为单位的内存页,每一个内存页在页表中占据一个页表项,即Table Entry。页表项和内存页是一一对应的,Entry 0对应block 0(0号内存页),Entry 1对应block 1,依此类推。每一个页表项包含对应的物理地址以及访问属性。
4.2、Table lookup 页表查找
地址转换必然要涉及到页表查找,当发生地址转换时,虚拟地址被分为两部分:
上图是一个一级页表(ARMv8硬件上支持4级页表,Linux内核默认使用3级页表)查找过程图。
虚拟地址的上半部分——Which entry,用来指定页表项,该页表项中保存了虚拟地址对应的物理地址。
虚拟地址的下半部分——Offset in block,表示页内偏移地址,在转换过程中保持不变。
4.3、Mutilevel translation 多级页表转换
多级页表实际上是将虚拟内存地址空间划分为多个相同大小的页存大页,再将每个内存大页划分为多个相同大小的内存小页。
ARMv8最大支持4级页表,编号0~3。
使用多级页表的好处是,内存大页可以加速地址转换的效率,比如提高TLBs缓存的有效性;同时,内存小页有给应用程序提供了对虚拟内存地址空间的细粒化管理。但是与此同时,TLBs缓存内存小页的有效性就比较差。
为了降低这种开销,操作系统需要在内存大页的效率和内存小页的映射之间进行权衡,决定最合适的页表层级。
5、ARMv8-A虚拟地址空间
ARMv8-A架构有几个独立的虚拟地址空间,如下图所示:
上图显示了三种虚拟地址空间:
1)NS.EL0 and NS.EL1 (Non-secure EL0/EL1)
2)NS.EL2 (Non-secure EL2)
3)EL3
每一种虚拟地址空间都是独立的,而且拥有各自的页表设置(页表层级和页表),我们通常称这种页表设置为——translation regimes。同样的,Secure EL0/EL1/EL2也有各自的虚拟地址空间,只不过上图没有展示出来。
5.1、地址大小
ARMv8-A架构是64位架构,但是并不意味着其地址位宽为64位。
5.1.1 虚拟地址位宽
虚拟地址以64位的格式保存,LDR和STR指令通常将地址保存在X寄存器中,但是这并不意味着X寄存器中所有的地址都是有效的。
下图演示了AArch64架构虚拟地址空间布局:
EL0/EL1的虚拟地址空间分为两部分——内核空间和用户空间,内核空间在高地址,用户空间在低地址。内核空间和用户空间各自维护相互独立的页表,这也意味着它们的地址映射也是相互独立的。
ARMv8架构目前最大支持48位虚拟地址。
5.1.2、物理地址位宽
物理地址位宽由芯片决定,最大支持52位。ID_AA64MMFR0_EL1寄存器记录了芯片支持的物理地址位宽。对于ARM Cortex-A系列处理器而言,通常为40位或者44位。
ARMv8.0-A架构最大支持48位物理地址,ARM8.2-A架构最大支持52位物理地址。
5.1.3、合适的物理地址位宽 IPA
如果你在页表项(页表项中记录的是物理地址)中指定一个超过芯片最大物理地址的地址,那么MMU会产生一个物理地址大小异常。
IPA地址空间大小可以像虚拟地址空间一样构建。VTCR_EL2.TOSZ寄存器控制地址大小,理论上最大可以配置到和芯片支持的物理地址空间一样的大小。
5.2、地址空间标识 ASID
许多现代的操作系统让所有的应用运行在同样的地址区域,这就是我们提到的用户地址空间。实际上,不同的应用需要不同的地址映射。比如,VA 0x8000实际转换的物理地址取决于当前正在运行的应用,即每个应用程序自己维护一个页表。
理想状态下,我们希望不同的应用的页表项共存于TLBs中,防止上下文切换时TLB中没有当前应用(准确地讲应该是指进程)的页表项。但是处理器怎么知道不同应用的VA 0x8000对应的物理地址呢?在ARMv8-A架构中,答案是Address Space Identifier (ASIDs)。
对于EL0/EL1虚拟地址空间,通过页表项属性字段的nG位标记页表为Global(G)或者Non-Global(nG)。比如,内核地址映射为全局页表,应用程序地址映射为非全局页表。不管当前执行的是哪个应用程序,全局页表都是生效的;非全局页表只有在特定应用执行时才生效。
非全局页表项在TLBs中使用ASID标记。在进行TLB查找时,将当前选择的ASID与TLB页表项中的ASID进行比较。如果不匹配,则表示当前TLB页表项无效。下图显示了全局页表和局部页表,以及ASID标记:
上图展示了不同应用的TLB页表项可以在缓存中共存,由ASID决定哪一个页表项生效。
ASID保存在两个TTBRn_EL1寄存器之中,通常用户空间使用TTBR0_EL1寄存器。因此,TTBRn_EL1寄存器值的更新会同时更新ASID和当前生效的页表项。
需要注意的是,当HCR_EL2.E2H置位时,ASID标记在EL2模式同样生效。
5.3、虚拟机标识 VMID
通过VMID(Virtual Machine Identifier)允许不同的虚拟机页表共存于cache中,类似于ASID。
5.4、Common not Private (CnP)
试想,在一个多处理器系统中,一个处理器上的ASIDs和VMIDs是否可以同样应用在其他处理器上呢?
对于ARMv8.0-A架构而言,答案是完全没有必要。因为应用程序不需要ASID跨处理器生效。比如,ASID 5在A处理器上可能是计算机程序,在B处理器上是浏览器。这一点说明,一个处理器创建的TLB页表项不能用在其他处理器上,即每个处理器维护自己的页表。
但是ARMv8.2-A架构在TTBR(Translation Table Base Register)寄存器中引入了Common not Private(CnP)位,通过设置CnP位,软件可以使ASIDs和VMIDs跨处理器生效,即允许一个处理器创建的页表项在另外一个处理器上同样生效。
6、地址转换控制
6.1、页表格式
以下是集中页表项的格式:
每一个页表项都是64位,最低两位表示页表层级。
顶层页表没有Table描述符,底层页表没有Page描述符。
7、页大小 Translation Granule
页是虚拟地址空间分配的最小单位。ARMv8-A架构支持4KB/16KB/64KB几种大小的页。
ID_AA64MMFR0_EL1寄存器设置了芯片支持的页大小,所有ARM Cortex-A系列处理器都支持4KB和64KB页。
4KB和16KB页最大支持48位虚拟地址,如果要支持52位虚拟地址,必须使用64KB页。
TCR_EL1寄存器有两个字段分别控制内核空间和用户空间页大小。
7.2、虚拟地址的各级页表序号
以4KB页为例。
7.2、控制地址转换的寄存器
SCTLR_ELx
M - 使能MMU
C - 使能数据缓存和unified缓存(所有处理器共用的缓存)
EE - 页表字节序
TTBR0_ELx和TTBR1_ELx
BADDR - 起始页表项的物理地址
ASID - 局部页表的地址空间标识
TCR_ELx
PS/IPS - 最大物理地址位宽
TnSZ - 页表覆盖的地址空间大小
TGn - 页大小
SH/IRGN/ORGN - MMU页表遍历是的缓存性和共享性
TBIn - 禁止页表遍历到指定的页表项
MAIR_ELx
Attr - 一级页表的类型和缓存性
7.3、关闭MMU
当MMU禁用时,所有的地址都flat-mapped,可以理解为没有虚拟地址。
8、TLB维护
TLB缓存最近使用的页表项,从而允许接下来的页表项查找复用缓存的页表项,而不需要重新加载页表。
当内存中的页表项发生改变时,必须丢弃TLB中缓存对应的页表项,从而保证页表项的一致性。
当发生如下错误时,TLBs不应该缓存页表项:
1)发生地址转换错误(unmapped address)
2)发生地址发小错误(超出地址范围)
3)访问权限错误
8.1、TLB操作方法
使用TLBI指令丢弃TLB中的页表项,其语法如下:
TLBI <type><level>{IS|OS} {, <xt>}
type字段表示丢弃哪个页表项:
All - 所有页表项
VA - 匹配xt字段中的VA和ASID的页表项
VAA - 匹配xt中的VA的页表项,对所有ASID生效
ASID - 匹配xt字段中ASID的页表项
level字段表示虚拟地址空间类别
E1 - EL0/1虚拟地址空间
E2 - EL2虚拟地址空间
E3 - EL3虚拟地址空间
IS | OS字段设置内部共享或者外部共享
IS - 广播到Inner Shareable domain的其他核
OS - 广播到Outer Shareable domain的其他核
Xt字段设置VA和ASID
举例说明:
STR X1, [X5] //写入页表入口 DSB ISH //设置内存屏障 TBLI VAAE1IS, X0 //丢弃EL0/1中匹配VA地址的页表项 DSB ISH //解除内存屏障 ISB //指令屏障,同步处理器上下文
9、地址转换指令
使用地址转换指令(AT指令),可以查询指定VA对应的PA,结果写入PAR_EL1(Physical Address Register)寄存器。
高级别的运行模式可以使用AT指令查询低级别运行模式的地址转换。
需要注意的是,使用AT指令发生地址转换异常时,不会抛出任何异常,只会在PAR_EL1寄存器中记录下来。