这篇笔记主要记录ARMv8 MMU的学习,学习资料主要是下面两个文档:
Armv8-A Address Translation
ARM Cortex-A Series Programmer's Guide for ARMv8-A
Armv8-A Address Translation
中的内容都来自于ARM Cortex-A Series Programmer's Guide for ARMv8-A
,但它把后者不同章节的相关内容放在了一起,因此免去了查找不同章节内容的痛苦。相比于后者,我会更多地使用前者中的内容。注意,ARMv8有多个Exception/Excution/Security Level,但这篇笔记只关注NS + EL1 + AArch64
的组合。
MMU的基本功能
MMU的功能就是实现VA->PA
的地址转换,我们会创建一系列的映射表来实现这个功能。至于为什么要用虚拟地址我就不解释了,网上随便搜搜就能找到一大堆的资料。 下图显示了从CPU开始,虚拟地址是如何进行转换并被使用的,这里除了涉及MMU外,还有TLB以及各级的Cache。TLB也是一种特殊的Cache,我就不在这里多介绍了,以后专门开个系列介绍Cache吧。
上图显示的是VA->PA
在架构层面如何实现的,下图显示的是地址空间如何进行VA->PA
的转换。我们可以看出,不同区域的虚拟地址可以映射到同一区域的物理空间,同一区域的虚拟地址也可以映射到不同区域的物理空间。
除了地址的映射,MMU还有一个重要的作用就是修改各个内存空间的权限、Cache策略等等。
MMU相关的两个重要寄存器
TTBRx
虽然ARMv8支持64bit的地址空间,但实际上最多只可以使用48bit的地址空间。[63 : 48]
这16位是不会作为地址被使用的。在绝大多数情况下,这16位要么是0xFFFF
要么是0x0000
,因此64bit的地址空间被分成了3部分 - user space
, kernel space
和reserved
。如果[63 : 48] = 0xFFFF
,那么它对应的虚拟地址将使用TTBR1_ELx
来寻找最低一级页表;如果[63 : 48] = 0x0000
,那么它对应的虚拟地址将使用TTBR0_ELx
来寻找最低一级的页表。
TTBRx
存的就是最低一级页表的物理地址。除了EL1
,这个寄存器也有EL2
和EL3
的版本。TTBR1
只有EL0
和EL1
,其它两种ELx
只能使用TTBR0
。
TCR_ELx
这个寄存器是设置页表的一个重要的寄存器(它将控制所有级别的页表),它可以设置有效虚拟地址的位数(除了48bit外,还有42bit,39bit等),颗粒度(granule)的大小(4/16/64KB)等等。
上图中,TxSZ
决定64 - 有效虚拟地址的位数
, TGx
决定转换颗粒度的大小, IRGN/ORGN
设置页表的cacheability, SH
设置页表的shareability等。总之,这个寄存器主要是对页表进行设置。
VA->PA转换的实例以及页表的相关属性
下图是一个2级页表、granule = 64KB、有效虚拟地址位 = 42的例子,我们来看看有哪些需要注意的地方。
TTBRx
和页表中保存的页表地址都是物理地址;- 虚拟地址的最高位决定是去
TTBR0
还是TTBR1
寻找页表; - 页表entry的数量 = granule size / 8B = 64KB / 8B = 8K(在这个例子中,页的大小是64KB,页的大小也可能是其它值);
- 物理地址的位数可以和虚拟地址的位数不一样 - VA = 42bit,PA = 48bit,PA的大小由
TCR_ELx
中的IPS
决定。
关于地址转换的例子和 Linux 中页表的定义可以参考【原创】(一)ARMv8 MMU及Linux页表映射。
页表描述符
这篇笔记讨论的描述符都是基于这种格式 - Armv8-A AArch64 Long Descriptor format,一共有3类有效的描述符,请看下图:
- 指向页表的描述符;
- block entry的描述符 - block简单来说就是人为的合并/简化高级的页表,在Linux开启MMU前创建的临时页表用了block;
- table entry的描述符 - 可以理解成PTE。
注意,页表中存储的地址都是物理地址,不管是页表的地址还是最后映射的地址,都是物理地址。MMU的硬件会处理好这些物理地址。
在上图中,描述符都有attributes的位,下图显示了这些位的具体定义。要看完整的描述符的定义,还需要看Arm Architecture Reference Manual
,具体内容请参考 - D8.3 Translation table descriptor formats
。
其中的Index
是MAIR_ELn
的索引,用于确定Cache policies,其它主要用于设置access permission, shareability等等。
Granule对页表的影响
页表的结构主要受两个因素的影响 -
- 虚拟地址的位数
- granule的值
我们下面看看VA = 48bit, granule = 4KB
的一个例子:
下面我简单介绍下这个结构是如何构成的,
granule = 4KB
,这个决定了最后一部分是VA[11:0] = 12bit
;- 因为
granule = 4KB
,所以每个页表的大小是4KB
。每个页表描述符是64bit
,因此有512个entry,需要9bit
; - 如果
VA
只有39bit,那么我们就直接忽略第0级页表,直接从第1级页表开始;
页表的属性
在页表描述符中,我们可以对不同区域的内存进行cache, memory, access等属性的设置。这些设置都具有继承性,因此,在低级页表描述符中设置的参数会覆盖之后几级设置的参数。因为Lower attributes
只在最后一级页表出现,因此它设置的属性其实是不会被覆盖的。除了页表映射的内存空间,我们也可以设置页表本身的属性。比如,页表是否cacheable,是否shareable等等,这些都是通过TCR_EL1
进行设置 - IRGN/ORGN
, SH0/1
。注意,我们设置页表的cache/memory等属性,必须和其所在的内存空间的属性一样。
到这里,MMU的基础知识基本就介绍完了。还有一些知识点,比如TLB, Cache以及不同EL的映射我就不介绍了。前两部分我会专门开一个Cache相关的专题进行介绍,至于不同EL的映射,就暂时先放一放吧,把精力投入到Linux内核上去。
参考资料
Armv8-A Address Translation
ARM Cortex-A Series Programmer's Guide for ARMv8-A
- 【原创】(一)ARMv8 MMU及Linux页表映射