Linux内核4级页表的演进

转自:http://larmbr.me/2014/01/19/the-evolution-of-4-level-page-talbe-in-linux/

 

Linux内存管理中core VM代码中,关于页表(page tables)管理的代码是个重点,是虚拟内存(Virtual Memory, VM)的基石,本文探讨Linux的页表实现及发展过程。

页表概览

在虚拟内存中,页表是个映射表的概念, 即从进程能理解的线性地址(linear address)映射到存储器上的物理地址(phisical address)。很显然,这个页表是需要常驻内存的东西, 以应对频繁的查询映射需要(实际上,现代支持VM的处理器都有一个叫TLB的硬件级页表缓存部件,本文不讨论)。

设想一个典型的32位的X86系统,它的虚拟内存用户空间(user space)大小为3G, 并且典型的一个页表项(page table entry, pte)大小为4 bytes,每一个页(page)大小为4k bytes。那么这3G空间一共有(3G/4k=)786432个页面,每个页面需要一个pte来保存映射信息,这样一共需要786432个pte!

如何存储这些信息呢?一个直观的做法是用数组来存储,这样每个页能存储(4k/4=)1K个,这样一共需要(786432/1k=)768个连续的物理页面(phsical page)。而且,这只是一个进程,如果要存放所有N个进程,这个数目还要乘上N! 这是个巨大的数目,哪怕内存能提供这样数量的空间,要找到连续768个连续的物理页面在系统运行一段时间后碎片化的情况下,也是不现实的。

Linux的页表实现

上面这种理论上的讨论显然不是实际情况。由于程序存在局部化特征, 这意味着在特定的时间内只有部分内存会被频繁访问,具体点,进程空间中的text段(即程序代码), 共享库都是固定在进程空间的某个特定部分,这样导致进程空间其实是非常稀疏的, 于是,从硬件层面开始,页表的实现就是采用分级页表的方式,Linux内核当然也这么做。所谓分级简单说就是,把整个进程空间分成区块,区块下面可以再细分,这样在内存中只要常驻某个区块的页表即可,这样可以大量节省内存

Linux最初的二级页表

Linux最初是在一台i386机器上开发的,这种机器是典型的32位X86架构,支持两级页表。如下图:

一个32位虚拟地址如上图划分。当在进行地址转换时,

  • 结合在CR3寄存器中存放的页目录(page directory, PGD)的这一页的物理地址,再加上从虚拟地址中抽出高10位叫做页目录表项(内核也称这为pgd)的部分作为偏移, 即定位到可以描述该地址的pgd;

  • 从该pgd中可以获取可以描述该地址的页表的物理地址,再加上从虚拟地址中抽取中间10位作为偏移, 即定位到可以描述该地址的pte;

  • 在这个pte中即可获取该地址对应的页的物理地址, 加上从虚拟地址中抽取的最后12位,即形成该页的页内偏移, 即可最终完成从虚拟地址物理地址的转换。

从上述过程中,可以看出,对虚拟地址的分级解析过程,实际上就是不断深入页表层次,逐渐定位到最终地址的过程,所以这一过程被叫做page talbe walk

至于这种做法为什么能节省内存,举个更简单的例子更容易明白。比如要记录16个球场的使用情况,每张纸能记录4个场地的情况。采用4+4+4+4,共4张纸即可记录,但问题是球场使用得很少,有时候一整张纸记录的4个球场都没人使用。于是,采用4 x 4方案,即把16个球场分为4组,同样每张纸刚好能记录4组情况。这样,使用一张纸A来记录4个分组球场情况,当某个球场在使用时,只要额外使用多一张纸B来记录该球场,同时,在A上记录"某球场由纸B在记录"即可。这样在大部分球场使用很少的情况下,只要很少的纸即困记录,当有球场被使用,有需要再用额外的纸来记录,当不用就擦除。这里一个很重要的前提就是:局部性

Linux的三级页表

当X86引入物理地址扩展(Pisycal Addrress Extension, PAE)后,可以支持大于4G的物理内存(36位),但虚拟地址依然是32位,原先的页表项不适用,它实际多4 bytes被扩充到8 bytes,这意味着,每一页现在能存放的pte数目从1024变成512了(4k/8)。相应地,页表层级发生了变化,Linus新增加了一个层级,叫做页中间目录(page middle directory, PMD), 变成:

 

  
  
  
  
  1. 31 29 20 11 0
  2. +-----+---------------+--------------+--------------+
  3. | PGD | PMD | PTE | page offset |
  4. +-----+---------------+--------------+--------------+

实际的page table walk依然类似,只不过多了一级。

现在就同时存在2级页表和3级页表,在代码管理上肯定不方便。巧妙的是,Linux采取了一种抽象方法:所有架构全部使用3级页表: 即PGD -> PMD -> PTE。那只使用2级页表(如非PAE的X86)怎么办?

办法是针对使用2级页表的架构,把PMD抽象掉,即虚设一个PMD表项。这样在page table walk过程中,PGD本直接指向PTE的,现在不了,指向一个虚拟的PMD,然后再由PMD指向PTE。这种抽象保持了代码结构的统一。

Linux的四级页表

硬件在发展,3级页表很快又捉襟见肘了,原因是64位CPU出现了, 比如X86_64, 它的硬件是实实在在支持4级页表的。它支持48位的虚拟地址空间[1](不过Linux内核最开始只使用47位)。如下:

 

  
  
  
  
  1. 47 38 29 20 11 0
  2. +------+------------+------------+------------+--------------+
  3. | PML4 | PGD | PMD | PTE | page offset |
  4. +------+------------+------------+------------+--------------+

Linux内核针为使用原来的3级列表(PGD->PMD->PTE),做了折衷。即采用一个唯一的,共享的顶级层次,叫PML4[2]。这个PML4没有编码在地址中,这样就能套用原来的3级列表方案了。不过代价就是,由于只有唯一的PML4, 寻址空间被局限在(239=)512G, 而本来PML4段有9位, 可以支持512个PML4表项的。现在为了使用3级列表方案,只能限制使用一个, 512G的空间很快就又不够用了,解决方案呼之欲出。

在2004年10月,当时的X86_64架构代码的维护者Andi Kleen提交了一个叫做4level page tables for Linux的PATCH系列,为Linux内核带来了4级页表的支持。在他的解决方案中,不出意料地,按照X86_64规范,新增了一个PML4的层级, 在这种解决方案中,X86_64拥一个有512条目的PML4, 512条目的PGD, 512条目的PMD, 512条目的PTE。对于仍使用3级目录的架构来说,它们依然拥有一个虚拟的PML4,相关的代码会在编译时被优化掉。 这样,就把Linux内核的3级列表扩充为4级列表。这系列PATCH工作得不错,不久被纳入Andrew Morton的-mm树接受测试。

不出意外的话,它将在v2.6.11版本中释出。但是,另一个知名开发者Nick Piggin提出了一些看法,他认为Andi的Patch很不错,不过他认为最好还是把PGD作为第一级目录,把新增加的层次放在中间,并给出了他自己的Patch:alternate 4-level page tables patches。Andi更想保持自己的PATCH, 他认为Nick不过是玩了改名的游戏,而且他的PATCH经过测试很稳定,快被合并到主线了,不宜再折腾。

不过Linus却表达了对Nick Piggin的支持,理由是Nick的做法conceptually least intrusive。毕竟作为Linux的扛把子,稳定对于Linus来说意义重大。

最终,不意外地,最后Nick Piggin的PATCH在v2.6.11版本中被合并入主线。在这种方案中,4级页表分别是:PGD -> PUD -> PMD -> PTE

参考:

  • http://lwn.net/Articles/106177/
  • 代码:
    • include/asm-generic/pgtable-nopud.h
    • include/asm-generic/pgtable-nopmd.h
    • arch/x86/include/asm/pgtable-2level*.h
    • arch/x86/include/asm/pgtable-3level*.h
    • arch/x86/include/asm/pgtable_64*.h

    * * * * * * 全文完 * * * * * *

[1]: 一开始就实现全64位的虚拟空间,一是实际用不到这么大的空间,二是技术实现上也有困难,所以首先推出X86_64架构CPU的AMD只只支持48位(256TB),但虚拟地址仍使用64位,只不过对高16位作位扩展,跟第48位值一样。这样方便以后扩展,另外一个副作用是:自然地分出用户与内核态两个空间。不多详述。

[2]: Page Map Layer 4, 这是首先引入X86_64 CPU的AMD的叫法。

 

你可能感兴趣的:(Linux内核4级页表的演进)