ARM32页表

2020-10-25 23:05:31

随着我继续在不同的帖子中描述ARM32启动序列是如何工作的,有必要深入解释有关页表的基本内核概念以及它是如何在ARM32平台上实现的。

要理解分页设置,我们需要重复和扩展一些Linux分页行话。一些很好的背景知识是阅读Mel Gorman在他的“理解Linux虚拟内存管理器”一书中对Linux页表的描述。本书出版于2007年,基于梅尔2003年的博士论文。在那之后的13年里,发生了一些事情,但基本情况仍然存在。还有必要了解页表中的新层,例如Linux内核中当前使用的五层页表。

首先介绍一下入门知识:采用经典MMU的ARM32架构有2级页表,而最新的LPAE(大型物理地址扩展)MMU有3级页表。

只有一些ARMv7架构有LPAE,而且它是有条件启用的,也就是说,如果机器愿意,也可以使用经典的MMU,它们两者都有。默认情况下,在multiv7配置上没有启用它:您的机器必须在编译期间显式打开它。布局非常不同,同一个二进制映像永远不能在同一内核映像中同时支持经典和LPAE MMU。

ARMv7-A的早期实现(如Cortex A8和Cortex A9)不支持LPAE,而是在此体系结构的生命周期中引入的。由于这是编译时设置,默认情况下ARMv7的默认配置不能启用它,否则旧的实现会中断。默认情况下,所有ARMv8实现都启用了LPAE。

Linux内核中的抽象页表从PGD(页面全局目录)开始,经过P4D(第四级目录)、PUD(页面上层目录)、PMD(页面中间目录),再向下到将内存的各个页面从虚拟地址空间映射到物理地址空间的实际页表条目(PTE)。

Pgd,pgd_t,pgdval_t=页面全局目录-在符号swapper_pg_dir中可以找到为内核内存处理PGD的Linux内核主页表。

在引入PUD之后,引入了p4d,p4d_t,p4dval_t=Page Level 4 Directory来处理5级页表。现在很明显,我们需要用指示目录级别的数字替换PGD、PMD、PUD,我们不能再使用自然名称。这在ARM32上未使用。

PUD,PUD_t,pudval_t=Page UpDirecory是在其他抽象概念之后引入的,用于处理4级页表。与P4D一样,这在ARM32上没有使用。

Pfn=页帧编号物理内存中的每一页都有一个唯一的编号,地址0x00000000是第0页,地址0x00001000是第1页,依此类推(请参阅include/asm-Generic/Memory_Model.h)。

首先也是最重要的注意,除了名为Something-directory的PTE之外,所有这些都是。这是因为它们都包含指向下一层对象的几个指针。就像任何目录一样。所以每个Pgd,P4D…。Etc是一个指针数组。PTE尽管有一个单数形式,但也包含几个指针。

Linux内核将表现为存在5级页表。当然,这对于ARM32来说是过度设计的,因为ARM32有2到3个页表级别,但是我们需要迎合世界其他地方的需要。一个尺码适合所有人。实际上,代码的组织方式是使这些页表“折叠”,我们在可能的情况下通常会跳过中间翻译步骤。

关于页表层次结构,您需要知道的另一件事是,每个级别都包含许多指针,这些指针是转换块。在ARM32上,在PTE级别上,每个PTE总是有512个指针,每个指针转换一个4KB页(0x1000)的内存。

ARM32上的PTE包含512个指针,即物理页和虚拟页之间的转换。从通用内核虚拟内存管理来看,这种转换实际上如何在硬件中进行并不重要,它需要知道的是大小为0x1000(4KB)的页面是由单个PTE指针条目转换的。这样,PTE就是一个目录,就像页面层次结构中的其他所有内容一样。在本例中,PTE中的第一个指针将物理地址0x10000000转换为虚拟地址0xC0000000。

在PMD级别,经典的ARM32 MMU每个PMD有一个指针,每个PGD有2048个指针。P4D和PUD级别是“折叠”的,即未使用。每个PMD有一个指针似乎没有什么意义:我们在PGD的2048个指针中有级别1,在PTE的512个指针中有级别2。您理所当然地会问,拥有一个“三级”层次结构(中间目录每个PMD有一个指针)有什么意义。这是一种将Linux关于页面层次的思想与实际的ARM32体系结构相匹配的方法,稍后将对其进行说明。

Linux中的经典ARM32分页设置折叠P4D和PUD,每个PGD提供2048个指针,每个PMD提供1个指针,每个PTE提供512个指针。它使用层次结构的3个级别,而ARM32硬件只有两个级别。然而,正如我们将看到的,这是一个很好的契合。左边是对象关系,右边是表格的插图。

在LPAE上,情况更简单:每个PGD有4个指针,每个指针覆盖1 GB(总共4 GB内存),每个PMD有512个指针,将每个1 GB分成2MB块,然后每个PTE有512个指针,把2MB块分成4KB块(页)。数学上应该是吻合的。当然,LPAE MMU可以将更多指针放入PGD,以覆盖高达1TB的内存。目前没有ARM32架构需要这个,所以我们将内核空间限制到最大4 GB。4 GB的内核空间内存应该足够每个人使用。用户空间则是另一回事。

LPAE页表还折叠了P4D和PUD,但在PMD级别上有一些意义:4个PGD条目,每个条目覆盖1 GB,覆盖整个32位地址空间,那么每个PMD有512个指针,每个PTE有512个指针。每个PTE条目将4KB的物理内存转换为虚拟内存。如果我们用所有512个可用条目填充PGD,它将正好跨越1TB的内存。

因此,当我们说经典的ARM32 MMU有2级页表时,我们以一种特殊的方式将其呈现给Linux,即“3级”,其中中间的一个是单指针,而LPAE MMU实际上有3级。给您带来的混乱,我们深表歉意。

指向目录中索引的指针是通过特殊的内联访问器获得的,这些内联访问器将虚拟地址作为名为pmd_off()的参数,并遍历整个层次结构以获得层次结构中右侧元素的偏移量:

静态内联pmd_t*pmd_off(struct mm_struct*mm,unsign long va){pmd_offset(pud_offset(p4d_offset(pgd_offset(mm,va),va);}。

在ARM32上,这个梯子的P4D和PUD部分将解析为零,在编译时进行“折叠”和优化。Struct mm_struct*mm参数是实际的内存管理器上下文(对于内核,这是init_mm),它存储指向页面全局目录(PGD)实际驻留的内存的指针。如果我们运行的是内核代码,这将是符号swapper_pg_dir。这就是include/linux/pgtable.h中实际发生的情况;

#DEFINE PGD_OFFSET(mm,address)PGD_OFFSET_PGD((Mm)->;pgd,(Address))(...)static inline pgd_t*pgd_Offset_pgd(pgd_t*pgd,无符号长地址){return(pgd+pgd_index(Address));};

在内核空间模式中,mm参数是init_mm,->;pgd指针指向swapper_pg_dir,这是内核内存空间根页表,等于实际物理全局页表在虚拟内存中的位置。所有索引指针访问器都按照这一原则操作。

页面目录的虚拟地址是PAGE_OFFSET+0x3000或PAGE_OFFSET+0x4000,PAGE_OFFSET取决于内核VMSPLIT,但通常是0xC0000000,即来自0xC0000000..0xFFFFFFFF的内核内存。在ARM32上,物理内存中的此基址设置在TTBR0(转换表基址寄存器)中。Linux对此地址使用符号swapper_pg_dir。LPAE的大小为0x5000字节,传统ARM MMU的大小为0x4000字节。如果内核从物理地址0xnnnn8000开始,则它位于0xnnnn8000-PG_DIR_SIZE,因此对于LPAE为0xnnn3000..0xnnn7FFF,对于任何其他ARM为0xnnn4000..0xnnn7FFF。最常见的位置是虚拟内存中的0xC0004000..0xC0007FFF。

PTE页表项和相关联的类型PTE_t和PTEVAL_t当然纯粹是一个Linux概念。这就说明了这一点。因为ARM32 MMU考虑的不是PTE,而是4KB页的粗糙2级或3级页表。如果Linux以一种更灵活的方式设计成允许一个粗页表为一个PTE,那将是很好的,但情况并不总是这样,对于旧的ARM32系统来说肯定不是这样的。

在此上下文中的“粗略”意味着我们从1级描述符开始,并看到对于这1MB的虚拟内存,我们使用的是一些较小的4K页面,因此我们必须看得更远:使用我在这里给出的修改后的虚拟地址遍历页表阶梯。ARM MMU也有一个只有1K的“精细”页面的概念。Linux不使用这些。现在还不行。

我们在这里处理page_size粒度,而Linux内存管理器期望PTE是一个页面大小且充满指针的页面,因此,例如,我们将调用arm_pte_alloc()来分配0x1000(4096)字节的新的2级页(或LPAE上的3级)表。这一点很简单。

在LPAE系统上也相当简单,因为PTE确实只有一页,并且由512个条目填充,每个条目64位/8字节,这意味着我们有512*8=4096字节,它是完美匹配的。此外,这64位还满足了Linux的内存管理器和提供一些MMU功能(如“脏”位)的体系结构之间的合同。LPAE MMU上的一个PTE是充满这些粗略的第三级描述符的一页(请记住,LPAE有三个级别的页)。这种良好的适应性在ARM64/Aarch64平台上也是如此。故事结束了。对他们来说太好了!

不是那么简单的是Linux如何在传统的ARM MMU上利用这4096个字节,这在include/asm/pgtable-2level.h中有描述。

在经典的MMU上所做的是,我们将课程页表索引放入1级描述符的修改的虚拟地址(MVA)字段中。该“粗页表索引”在1级描述符(其是半个PGD条目,稍后详述)的位10-31中,并且其对应于2级页表的物理地址的最高21位。课程页表索引的特殊之处在于它与内存中的页面不对应。相反,它相当于四分之一的页面。这是合乎逻辑的,因为2级页表是32位/4字节,我们需要其中的256个来覆盖1MB,这是1级描述符的粗细,因此我们需要256*4=1024=0x400字节。这是4KB页面的四分之一。因此,粗页表索引指向虚拟内存中由0x400:th块索引的位置。这有点让人困惑。

此外,我们还有以下问题:ARM经典MMU没有通用Linux虚拟内存管理器预先假定存在于体系结构中的所有“脏的”和“被访问的”/“年轻的”位。

您可以想象一下,告诉Linux虚拟内存管理器,我们每个PTE只有256个指针,在剩余的指针中放入一些元数据,但最终只使用了半个页面,并且为PTE浪费了大量内存。

在ARM32上实现的实际解决方案(我认为这是由Russell King发明的)是在一个页面中尽可能多地挤进去,如下所示:

告诉Linux虚拟内存管理器,我们有512个页描述符,Linux将其称为PTE中的“指针”,尽管我们很清楚硬件有256个。这是通过在include/asm/pgtable-2level.h中将PTRS_PER_PTE设置为512来实现的。

告诉它我们在PMD中映射了2MB的内存。这是通过在include/asm/pgtable-2level.h中将pmd_shift定义为21来实现的。尽管我们非常清楚1级描述符映射的是1MB而不是2MB的内存,但我们还是这样做了。我们还将页面中间目录设置为1对1目录,每个pmd只有一个指针:ptrs_per_pmd设置为1。

告诉它PGD包含2048个指针(将PTRS_PER_PGD设置为2048),而实际上0x4000字节的1级描述符有4096个指针,我们只是将它们成对分组。

每当通用内存管理器请求PGD条目时,我们都会引用具有两个1级页表描述符的PMD指针。

每当通用内存管理器请求PMD条目时,我们都会引用1对1实体。

每当通用内存管理器请求PTE条目时,我们都会引用两个2级页表描述符和两个各32位的元数据条目。

然后,我们使用这两组2级描述符占据半个页面。为了解决不同“访问的”或“年轻的”位的映射问题,我们使用剩下的半页来保存一些关于Linux请求的元数据,这样我们就可以对Linux虚拟内存管理器需要的特性进行一些前后模拟。

我们已经有效地向内核虚拟内存管理器提出,我们有一个具有三个页表级别的MMU:PGD(2048个指针)、PMD(一个指针)和PTE(512个指针),而实际上我们有两个,然后在通用虚拟内存管理器后面,我们进行此优化以使映射符合实际情况。

这并不是很糟糕:虚拟内存管理器很清楚,并不是整个世界都使用它支持的所有五个级别的页表,所以它已经被写入到“折叠”级别,就像我们的人造PMD一样。这个会工作得很好的。

唉,这不是一个非常简单的解决方案。但它非常高效,违反直觉,而且很复杂。由于操作系统开发在细节上可能相当困难,这是我们可以预期的。

经典的ARM32 MMU页表布局在两个级别使用32位描述符:全局页表(TTB,Linux称之为PGD,页面全局目录)中的两个一级描述符被分组到一个PMD(页面中间目录)中。有4096个1级描述符成对分组以形成PMD,因此有2048个PMD。然后,256+256=512个一级页表指针被分组到一个PTE(页表条目)中,其正上方有一些元数据。Linux看到的是一个管理2MB的PMD和一个管理256个4K页面的PTE,也就是2MB。这样一来,PTE正好填满了一页内存,从而简化了事情。4K级别-2描述符被称为“粗略”。

另一方面,LPAE页表设置比传统的MMU更简单、更深入:描述符是64位宽,并且包含Linux需要的所有位。覆盖第一个4 GB物理内存(0x00000000-0xFFFFFFFF)的64位PUD条目通常位于0xC0003000,指向位于0xC0004000的PMD条目,这些条目是64位的,但每个条目覆盖2MB,因此它们覆盖与经典MMU相同的虚拟内存空间量:我们有512个64位条目,而不是1024个32位“粗略”条目,每个条目覆盖1MB,每个条目覆盖2MB。这使得1个PMD条目对应于一个2级页面描述符。

2级描述符指向3级描述符,该描述符也是64位宽,覆盖4KB。在第三级,512个4KB的描述符覆盖2MB,并且恰好填满了一个4KB的内存页。这种描述符布局符合Linux关于PUD、PMD和PTE的想法,并使事情更直观。ARM64/Aarch64也使用这种风格的页面描述符。

LPAE页表布局比传统的MMU更简单、更直观。在三个级别中的每个级别上的64位指针,其中一个描述符对应于一个PUD,一个PMD具有512个指向PTE的指针,并且一个PTE正好由512个翻译指针组成。