ARMv7 MMU 详解
专有名词:
(1)虚拟地址相关概念
- 虚拟页(Virtual Page,VP):把虚拟内存按照页表大小进行划分。
- 虚拟地址(Virtual Address,VA):处理器看到的地址,也叫线性地址。
- 虚拟页号(Virtual Page Number,VPN):用于定位页表的PTE。
(2)物理地址相关概念 - 物理页(Physical Page):把物理内存按照页表的大小进行划分。
- 物理地址(Physical Address,PA):物理内存划分很多块,通过物理内存进行定位。
- 物理页号(Physical Page Number,PPN):定位物理内存中块的位置,或者叫RPN(Real Page Numbe)。
(3)页表相关的基本概念 - 页表(Page Table):虚拟地址与物理地址映射表的集合,ARM 资料中称为 Transliation Tables 。
- 页表条项(Page Table Entry,PTE):虚拟地址与与物理地址之间的映射关系和属性,ARM 中称为 Transliation Tables Entry。
(4)MMU相关概念: - L1:一级页表。
- L2:二级页表。
- TLB:MMU中的一块专用高速缓存,也称快表。
物理/虚拟地址
关于内存管理中线性地址和逻辑地址都是x86的概念,主要用于内存分段机制,而在arm平台上,没有分段机制,线性地址、逻辑地址和虚拟地址都是同一个概念,都统称为虚拟地址。
① 物理地址:是硬件真实使用的地址,格式为:物理页号+页偏移量
② 虚拟地址:是操作系统和应用程序使用的地址,格式为:L1虚拟页号+L2虚拟页号+页偏移量
MMU 内存管理
访问硬件内存需要的是物理地址,而操作系统和应用程序是用的是虚拟地址,所以需要有一种机制,将进程持有的虚拟地址转换成物理地址,同时还支持访问属性的控制,防止非法访问,向 CPU 报告异常。虚拟地址空间的大小等于 CPU 的线性地址宽度,32 位系统中默认是 4G。
MMU (memory management unit)。 在ARM的体系结构中,MMU可以使用内存中保存的页表来进行虚拟地址到物理地址的转换,每个用户进程都有自己的页表,内核空间公用一个页表,此外MMU还可以控制cache的策略,内存的属性以及访问权限的设置。
**TLB(Translation Lookaside Buffer)**是一块高速缓存,缓存最近查找过的VA对应的页表项。Table Walk Unit 是表查找单元,会自动对多级页表进行遍历,查找物理地址页,形成物理地址。基本情况如下:
- RM 处理器发出地址访问,如果TLB中有需要查表的VA,那么就直接返回真实的物理地址。
- 如果 TLB Miss那么就要使用 Table Walk 单元去主存中查找表获取物理地址,然后通过 Cache去访问。
- Cache 如果命中,就返回实际物理地址下的数据,否则,也就是最糟糕的情况,会去访问主存的数据。
虚拟页和物理页:
MMU 管理虚拟地址空间时,按照页为单位来进行管理。在ARMv7的MMU中页大小 一共有4种规格16M(Super Section)、1M(Section) 、64K(Large Page),4K(Small Page)。虚拟地址的页和物理地址的页是等大的,页大小可以通过CP15寄存器进行配置,越小的页意味着内存的颗粒度越小,内存使用时的浪费会越小,但也意味着使用的TLB行越多。越大的也内存的颗粒度越大,内存的使用浪费也可能月大,但使用的TLB行越少。
- supersections:每个section 包含 16MB 空间,这种配置下通常只需要一级页表转换,一般只会在支持 PAE 扩展的处理器中使用
- sections:每个 section 包含 1MB 的空间,在内核中会用到,只需要一级页表转换
- large pages:64K 的大页面,通常实现为二级页表转换
- small pages:最常见的 4K 页面,通常实现为二级页表转换
页表和页表项:
MMU 在进行地址转换时,需要一些信息,存放这些信息的就是页表,L1是存储在连续的物理地址上的,L2每个页表则可以是地址非连续的。每个页表中存在多个页表项,这个页表的物理首地址称为页表基地址。页表存储在物理内存地址空间中,且一个页表项对应着一个物理页(可能是物理内存,也可能是存放二级页表)。
- L1页表:假设TTBCR N=0则占用连续 16K 空间作为整个一级页表区域
- L1页表项:每个页表项占用 4 字节, 共16K/4=4096项,保存二级页表基地址或section地址
- L2页表:每个页表占用连续 1K 空间作为二级页表区域
- L2页表项:每个页表项占用 4 字节,共1K/4=256项,其中保存最终的物理地址的一部分
在Linux中每个用户进程,都有它独立的虚拟地址空间,拥有于独立的页表,但是对内核中的进程来说,他们共用代码段和数据空间,所以内核的页表“基本”是固定的。在用户进程切换时,通过将进程页表的物理基地址设置到协处理器CP15 中的 TTBRx 寄存器的Translation Table Base Address区域,此后 MMU 会通过该地址自动去物理地址空间中找到对应的页表,完成虚拟地址到物理地址的映射。在仅使用TTBR0的情况下为了减少用户进程和内核进程的切换时页表切换带来的开销,一般会使用共享内存,每个进程的地址空间(包含用户空间和内核空间)都被定义在同一个页表中。这意味着用户进程和内核共享L1页表,其中内核部分的映射(一般是较高的地址空间)在所有进程间是共享的。这样当一个用户进程进入内核状态时不需要修改TTBR0中的页表基址。
TLB快表:
TLB (Translation Lookaside Buffer) 旁路转换缓冲,它是MMU的专属全相联cache,用于临时存放虚拟地址到物理地址映射所需要的信息。
MMU在优先访问TLB查找物理地址,如果在TLB中,则称为TLB命中,从 TLB 中直接获取物理地址对内存进行访问,如果不在TLB中,则称为TLB失效。此时MMU将进行 translation table walking,即通过访问页表来获取物理地址。并将该虚拟地址的信息存入TLB,以便下次使用。
VA:虚拟地址的一部分Bits组成,用于搜索;
ASID:Address Space ID,用于多进程切换时不需要清理TLB中所有数据;
PA:物理地址一部分Bits组成;
Attributes:属性;
MMU寄存器:
此处只分析Small Page 和 Section 两种页类型下的短描述符,短描述符可能有两级页表组成,第一级页表为 L1,第二级为 L2。
TTBCR(转换表基地址控制寄存器):
ARmv7中有两个TTBR用来指明页表位置,一般情况是在系统初始化的时候会设置TTBCR中的N,然后在使用虚拟地址时,MMU检查送入的VA[31:32-N]是否为0,决定使用哪个TTBR。
EAE:与 Large Physical Address Extension 相关,暂时不关心。
PD0、PD1:与 Security Extensions 相关,暂时不关心。
N[2:0]:指示TTBR0页表基址寄存器基址位宽,TTBR0页表尺寸,以及在虚拟地址送入MMU后确定使用 TTBR0 还是 TTBR1 作为页表基址寄存器,页表在每个进程中是固定大小的,所以N的大小孩影响系统资源使用。
-
N = 0:则使用 TTBR0 指定的基地址作为页表入口的地址;
-
N > 0:指示TTBR0页表基址寄存器基址位宽,同时指示使用 TTBR0 还是 TTBR1 作为页表基址寄存器,以及 TTBR0 页表尺寸;
- N==0,仅使用 TTBR0,L1页表最大占用16K内存。
- N>0,如果虚拟地址[31:32-N]为0,则使用 TTBR0;其他情况使用TTRB1。这种情况下,N 指示了TTBR1的页表地址,也指示了 TTBR0 的页表大小。
TTBR0能表示的虚拟地址范围是 [31-N:20],例如 N=2,那么VA [31:29]为 0 的时候,使用 TTBR0 查找页表基地址,否则使用用 TTRB1查找页表基地址,按照地址空间来划分,0x0000_0000 ~ 0x3FFF_FFFF 1G的虚拟地址使用TTRB0下的页表基地址查找L1页表项, 0x4000_000 ~ 0xFFFF_FFFF 3G的虚拟使用 TTBR1查找L2页表项。
TTBCR 是 CP15 的寄存器,访问 TTBCR 的指令为:
MRC p15, 0, <Rt>, c2, c0, 2 //Read TTBCR into Rt
MCR p15, 0, <Rt>, c2, c0, 2 //Write RT to TTBCR
TTBR0和TTBR1(转换表基址寄存器):
TTBR寄存器是用于存放页表基地址(必须是物理地址),在ARmv7 中一共有 2个这样的寄存器,分别是TTBR0和TTBR1,32Bits带有多核处理器扩展的寄存器如下所示。
TTBR0
x=(14 - (TTBCR.N))
Bits[31:x]:L1页表基地址,配置的Transliation Tables Base Address对齐方式 2^(x-1)
NOS:指示了做 Table walk 的那个内存的属性
- 0 Outer Shareable.
- 1 Inner Shareable.
outer memory 会存在多 PE(多处理器) 之间的共享行为,需要考虑 cache 回写的策略,TTBR0.S == 0 时,该bit无效;
S:指示内存共享属性与页表转换的关系;
● 0 Non-shareable.
● 1 Shareable.
RNG:指示 Outer Cache 属性与页表转换的关系;
IRGN[6,0]:指示 Inner Cache 属性与页表转换的关系;
- 0b00 Normal memory, Outer/Inner Non-cacheable(数据的读写都直接与内存进行,不会存储在任何级别的缓存中)
- 0b01 Normal memory, Outer/Inner Write-Back Write-Allocate Cacheable(写入数据时,在缓存中分配空间,更新到缓存中,在合适的时机以缓存行为单位一起写回内存)
- 0b10 Normal memory, Outer/Inner Write-Through Cacheable(写操作同时写入到缓存和内存)
- 0b11 Normal memory, Outer /Inner Write-Back no Write-Allocate Cacheable(写数据时,缓存中已经分配空间则写入缓存,后期写回内存,如果缓存中未分配空间则不写缓存,直接写入内存)
访问 TTBR0 的指令为:
MRC p15, 0, <Rt>, c2, c0, 0 //Read 32-bit TTBR0 into Rt
MCR p15, 0, <Rt>, c2, c0, 0 //Write Rt to 32-bit TTBR0
TTBR1
L2页表基地址空间Bit[31:14],这意味着,配置进 TTBR1 的 Transliation Tables Base Address 的物理地址,必须 16KB 对齐,2^14 = 16K;
访问 TTBR0 的指令为:
MRC p15, 0, <Rt>, c2, c0, 1 //Read 32-bit TTBR1 into Rt
MCR p15, 0, <Rt>, c2, c0, 1 //Write Rt to 32-bit TTBR1
L1 一级页表项
L1页表项的连续物理空间大小是由TTBCR.N控制,例如N=0时,VA[31:20]用12Bits来表示页表基地址下的页表项索引个数,2^12 = 4096,每个页表项占用 4Batys,所以 TTBR0 的页表最大为 4096*4 = 16K。
MMU 在获取到L1页表项之前,是不知道虚拟地址和物理地址之间采用的是何种方式进行映射的,通过 TTBRx 获取到页表基地址,然后通过 VA[31-N:20] 产生的索引,找到页表下的虚拟地址对应的 L1页表项,通过L1页表项的 [1:0] 位可以确认映射使用的方式:
- 00:无效参数
- 01:page table,表示当前虚拟地址的映射的页大小可能是 4K 或者 64K,使用二级映射,[31:10]范围表示L2描述符的地址,具体使用的是 4K 还是 64K的 page会体现在L2页表项中的 Bit[1:0]
- 10:section 或者 supersection,表示当前映射可能是 1M 或者 16M,具体通过bit18进行区分,使用L1映射,描述符对应物理页面以及相应属性
多种页表的映射方式在一个进程中是可以共存。
- Level 2 Descriptor Base Address:二级页表物理基地址
- Section Base Address:1MB段基地址
- Supersection Base Address:16MB段基地址
- SBZ:无效属性字段,设置位0
- AP:即访问权限,ARMv7中优先推进使用 AP 或L2中的APx进行权限控制
- Domain:访问权限控制,与cp15下的c3 DACR寄存器有关,ARMv7不建议使用域进行控制,建议把DACR寄存器设置为用户模式。
- TEX:设置内存区域类型
- B:是否设置 写缓冲
- C:是否设置 cache
- nG:指定页表项是global 的还是 process-specific的
○ nG == 0 内存转换页表是全局属性, 该内存区域可供所有进程访问
○ nG == 1 内存转换页表是非全局属性,亦即是进程相关的,该内存区域仅供当前 ASID 的进程所使用。 - S:共享设置项
- bit[18]:决定段页表项是 1MB页表项 还是 16MB页表项
- bit[1:0]:决定页表项的类型
L2 二级页表项
二级页表的大小是固定的,VA[19:12] 用 8bits 决定L2页表下的页表项索引个数2^8=256,每个页表项占4Batys,L2页表固定大小为:256*4 = 1K。
每种格式由物理地址部分和属性部分组成,格式有如下三种:
- 小页表项:地址部分指向 64KB 对齐的物理基地址
- 大页表项:地址部分指向 4KB 对齐的物理基地址
- 无效页表项,当访问该页表项时,将触发指令取指异常或取数据异常
XN:指的是 Execute Never,如果往这里取地址执行,那么会导致异常发生;通常,Device memory 类型的区域会配置成为 XN。
其他字段见一级页表项。
虚拟内存映射:
通过上面的介绍合理的配置 TTBCR/TTBR0/TTBR1 可以分配并指定页表让 MMU 来做 Table Walk,下面这张图很好的展示了 MMU 中四种页的转换方式:
一级页表转换
实际程序中,进程在使用前需要先申请一块连续的物理内存放置 L1页表,假设 TTBCR.N=0,此时使用 VA[31:20] 表示L1页表项的索引,12bits 最大能表示 4096 个索引,每个页表项占用 4Bytes,那么 L1 页表占用需要 4096 * 4 = 16K 的内存。
转换步骤如下:
- 从 TTBCR 寄存器中读取 N,然后再读取 TTBR0 中的 L1页表基址 TTBR0[31:14-N]
- 将虚拟地址 VA[31-N:20] 和 TTBR0[31:14-N] 组合,对于 Section 则是段物理基地址,对于二级转换情况则是 L2页表基地址。
二级页表转换
进程在使用到二级页表转换时还需申请一块连续的物理内存放置 L2 页表,页表的大小固定,使用虚拟地址 VA[19:12] 共8bits可以表示 256 个索引,每个二级页表项占用4Byts,则每个L2页表大小为 1K,每个L2页表项指向的区域是4K对齐的(每个L2页表项可管理的空间为一个page大小),在L1最大的情况下一共有 4096 个这样的 L2,那么一个 L2 页表能表示的内存大小为:4096 * 1KB = 4MB,每个二级页表有 256 个索引,那么可以表示的最大地址范围就是 4096 * 256 * 4M = 4GB。
还有一种方式表示,每个L1页表项可以管理的内存空间大小为 256 * 4K = 1M(一个L1页表项可管理256个L2页表项,每个L2页表项可管理4K内存空间),所以在最大情况下可以有 4096 个这样的L1页表项,可以管理的空间为 4096 * 1M = 4G。
转换步骤:
- 在L1页表转换的基础上找到L2页表的基地址
- 根据L2页表基地址并集合虚拟地址的[19:12]找出虚拟地址 对应的L2页表项
- 将虚拟地址[11:0]和L2页表项的物理地址部分结合得出具体的物理地址
问题扩展:
内存区域权限控制:
每个内存区域都有自己的权限,不符合访问权限的内存访问都会引发异常。如果是数据访问则引发数据异常。如果是指令访问,且该指令在执行前没有被 flush,将引发预取指异常。引发的 异常原因 将会被设置在 CP15 的 the fault address and fault status registers
内存区域权限 由 AP、APX 和 Domain(域) 共同控制,如下:
- AP/APX:字段 AP 和 APX 的不同组合将形成不同的 访问权限,如图所示。Privileged 指的是 CPU 处于 svc 等状态,而 Unprivileged 则为 CPU 处于 user 状态。
TLB快表优化机制ASID:
通过上面的介绍,我们知道在MMU进行虚拟地址转换时第一步是从TLB中进行查找,如果命中则直接返回虚拟地址对应的物联地址,否则需要到内存上查找页表,如果是二级页表转换的,就需要查找2次内存,效率就会差很多。但是TLB中能缓存的个数是有限的从几十到几百不等,与具体芯片型号有关,试想一下,每次进行用户进程的切换都需要切换页表,同时把TLB中的快表给清空,否则同一个虚拟地址不就被定位到上一个进程的物理地址下了么,此时会出现访问异常,但是如果每次进程切换都清除TLB,然后做Table Walk找到VA对应的PA,最后在同步到TLB中,此时效率有就会很低。于是为了提高效率就增加了ASID,ASID是8bits 的从0~255,ASID 由操作系统分配,并且不是固定的,当前进程的 ASID 值被写在 ASID 寄存器 (使用CP15 c3访问),TLB 在更新页表项时也会将 ASID 写入 TLB,如此一来在进程切换后就只需要检查TLB中和当前进程 ASID 所对应的TLB快表项了,其实也就是让TLB中功能装多个进程的快表项。但是 ASID 最大值是 255的,在进程较多的情况下会出现不够用,这就需要策略调整,例如回收不活动线程的ASIB,然后TLB进行一次清除,所有进程重新建立快表项。
其实 ARM TLB 包含了Globa l 和 process-specific 两种快表项,主要由 L1/L2 页表项中的中的 nG 比特位控制:
- nG = 0 内存转换页表是全局属性, 该内存区域可供所有进程访问
- nG = 1 内存转换页表是指定进程使用,该内存区域仅供当前 ASID 的进程所使用。
这样就可以将内核的页表项设置为全局的,让所有用户进程在TLB中都可用,用户进程则的页表项设置为指定进程访问,通过ASID来区分不同进程,这就可以减少上下文切换时必须进行的TLB刷新操作的数量,从而提高性能。
既有TTBR0何须TTBR1:
不使用:在ARM32架构的操作系统中,不使用 TTBR1寄存器。此时,用户空间和内核空间共用一个页表。也就是说用户空间和内核空间都使用TTBR0来记录页表地址,这样可以避免一个问题,就是进行用户空间和内核空间的切换时,可以避免切换页表带来的性能损耗。但与此同时也带来一个问题,用户空间的每个进程都拥有内核页表部分副本。
使用:ARM64架构的操作系统中,虚拟地址空间非常大。用户空间和内核空间都是 256T,意味着页表需要占用的内存会很多。用户空间地址高位为0,内核空间地址高位为1。这样的特性满足 TTBR1寄存器的使用条件。根据 用户空间地址和内核空间地址的不同,选择对应的TTBR寄存器。这样就不需要为每个进程维护一份内核页表副本。