从零到负一

01. ARMv8 MMU的基础知识

2022/04/01

这篇笔记主要记录ARMv8 MMU的学习,学习资料主要是下面两个文档:

  1. Armv8-A Address Translation
  2. ARM Cortex-A Series Programmer's Guide for ARMv8-A

第1个其实是将2中不同章节和地址转换相关的内容放在一起,因此免去了查找不同章节内容的痛苦。相比于2,我会更多地使用1中内容。

ARMv8有多个Exception/Excution/Security Level,这篇笔记主要关注NS + EL1 + AArch64的组合。

MMU的基本功能

MMU地功能就是实现VA->PA地转换,我们会创建一系列的映射表来实现这个功能。至于为什么要用虚拟地址我就不解释了,网上随便搜搜就能找到一大堆的资料。

下图显示了从CPU开始,虚拟地址是如何进行转换并被使用的,这里除了牵涉MMU外,还有TLB以及各级的Cache。TLB也是一种特殊的Cache,我就不在这里介绍了,以后专门开个系列笔记介绍Cache吧。
MMU AT

上图显示的是VA->PA在架构方面是如何实现的,下图显示的是地址空间如何进行VA->PA的转换。我们可以看出,不同区域的虚拟地址可以映射到同一区域的物理空间。同一区域的虚拟地址也可以映射到不同区域的物理空间。

MMU VA_2_PA

MMU相关的两个重要寄存器

TTBRx

虽然ARMv8支持64bit的地址空间,但实际上最多只可以使用48bit的地址空间。[63 : 48]这16位是不会作为地址被使用的。在绝大多数情况下,这16位要么是0xFFFF要么是0x0000,因此64bit的地址空间被分成了3部分(如上图所示)。
如果[63 : 48] = 0xFFFF,那么它对应的虚拟地址将使用TTBR1_ELx寻找最低一级页表;如果[63 : 48] = 0x0000,那么它对应的虚拟地址将使用TTBR0_ELx寻找最低一级的页表。

TTBRx存的就是最低一级页表的物理地址。注意,除了EL1,这个寄存器也有EL2EL3的版本,但只有TTBR0的版本。

TCR_ELx

这个寄存器是设置页表的一个重要的寄存器(它将控制所有级别的页表),它可以设置有效虚拟地址的位数(除了48bit外,还有42bit,39bit等),granule的大小(4/16/64KB)等等。

TCR_EL1

上图中,TxSZ决定64 - 有效虚拟地址的位数;TGx决定转换颗粒的大小;IRGN/ORGN设置页表的cacheability;SH设置页表的shareability等。总之,这个寄存器主要是对页表进行设置。

VA->PA转换的实例

下图一个2级页表、granule = 64KB、有效虚拟地址 = 42bit的例子,我们来看看有哪些需要注意的地方。
VA->PA

  1. TTBRx和页表中保存的页表地址都是物理地址;
  2. 虚拟地址的最高位决定是去TTBR0还是TTBR1寻找页表;
  3. 页表entry的数量 = granule size / 8B = 64KB / 8B = 8K;
  4. 物理地址的位数可以和虚拟地址的位数不一样 - VA = 42bit,PA = 48bit,PA的大小由TCR_ELx中的IPS决定。

页表描述符

这篇笔记讨论的描述符都是基于这种格式 - Armv8-A AArch64 Long Descriptor format,一共有3类有效的描述符,请看下图:
描述符

  1. 指向页表的描述符;
  2. block entry的描述符 - block简单来说就是人为的合并/简化高级的页表,在Linux开启MMU前创建的临时页表用了2MB的block;
  3. table entry的描述符 - 可以理解成PTE。

注意,页表中存储的地址都是物理地址,不管是页表的地址还是最后映射的地址,都是物理地址。MMU的硬件会处理好这些物理地址。

在上图中,描述符都有attributes的位,下图显示了这些位的具体定义。要看完整的描述符的定义,还需要看Arm Architecture Reference Manual,具体内容请参考 - D8.3 Translation table descriptor formats
描述符位
其中的IndexMAIR_ELn的索引,用于确定Cache policies,其它主要用于设置access permission, shareability等等。

Granule对页表的影响

页表的结构主要受两个因素影响 - 1.虚拟地址的位数;2. granule的值。我们下面看看VA = 48bit, granule = 4KB的一个例子:

页表结构

下面我简单介绍下这个结构是如何构成的,

  1. granule = 4KB,这个决定了最后一部分是VA[11:0] = 12bit
  2. 因为granule = 4KB,所以每个页表的大小是4KB。每个页表描述符是64bit,因此有512个entry,需要9bit
  3. 如果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内核遇到相关内容,我再做总结。

CATALOG
  1. 1. MMU的基本功能
  2. 2. MMU相关的两个重要寄存器
    1. 2.1. TTBRx
    2. 2.2. TCR_ELx
  3. 3. VA->PA转换的实例
    1. 3.1. 页表描述符
    2. 3.2. Granule对页表的影响
    3. 3.3. 页表的属性