微信公众号:二进制人生
专注于嵌入式linux开发。问题或建议,请发邮件至hjhvictory@163.com。
更新:2019/2/9,转载请注明出处。
内存管理系列文章:
内容整理自网络和自己的认知,旨在学习交流,请勿用于商业用途。
linux内存管理(一)开篇介绍
linux内存管理(二)两种内存架构和linux三种内存模型
linux内存管理(三)linux内存管理三级结构
linux内存管理(四)分页机制概述
linux内存管理(五)内存源头
linux内存管理(六)linux分页机制的演进
linux内存管理(八)页表最初的初始化--从内核启动的第一段代码谈起
我们在前面介绍了linux的分页机制,叙述了linux目前所采用的四级分页机制。然而linux在最开始并非采用四级分页机制,是随着硬件内存的扩大从最初的二级扩展成三级,再到目前的四级。我们今天来探索linux分页机制的演遍过程。本文转载自https://blog.csdn.net/gatieme/article/details/52402861,对很多表述不通的地方做了修改。
Linux最初的二级页表
Linux最初是在一台i386机器上开发的,这种机器是典型的32位X86架构,支持两级页表。
对于两级页表,一个32位虚拟地址被分成3部分:
| 字段 | 描述 | 位数 |
|---|---|---|
| PGD | 指向页目录1024个项中的一个 | 位31~20 |
| PTE | 指向页表中512项中的一个 | 位20~12 |
| page offset | 4KB页中的偏移 | 位11~0 |
当在进行地址转换时,
对于x86而言,CR3寄存器中存放着页目录(page directory, PGD)的地址;对于arm而言,页目录的地址存放在全局变量swapper_pg_dir(关于该变量在第八篇有叙述)。
1 从虚拟地址中抽出高10位作为偏移, 即可定位到该地址对应的页目录项;
2 页目录项中存放着描述该地址的二级页目录的地址,根据该地址即可访问二级页目录,再加上从虚拟地址中抽取中间10位作为页表项偏移, 即可定位到描述该地址的页表项;
3 页表项中存放着该地址对应的页的物理地址,加上从虚拟地址中抽取的最后12位,就可以找到页内的任一字节,即最终完成从虚拟地址到物理地址的转换。
我们可以看下页表项存放的内容:

页表项的高20位用于描述该页的物理地址,而低12位用于描述一些有用的信息,至于是什么信息后面会告诉你。
为何只要20位就可以描述该页的物理地址?因为一页的大小是4K,也就是说所有页的物理地址都是4K的整数倍。
从上述过程可以看出,对虚拟地址的分级解析过程,实际上就是不断深入页表层次,逐渐定位到最终地址的过程,所以这一过程被叫做page talbe walk。
Linux的三级页表
当X86引入物理地址扩展(Pisycal Addrress Extension, PAE)后,可以支持大于4G的物理内存(36位),但虚拟地址依然是32位,原先的页表项不适用,它实际多4 bytes被扩充到8 bytes,这意味着,每一页现在能存放的pte数目从1024变成512了(4k/8)。相应地,页表层级发生了变化,Linus新增加了一个层级,叫做页中间目录(page middle directory, PMD), 变成:
| 字段 | 描述 | 位数 |
|---|---|---|
| PGD | 指向页目录4个项中的一个 | 位31~30 |
| PMD | 指向二级页目录中512项中的一个 | 位29~21 |
| PTE | 指向页表中512项中的一个 | 位20~12 |
| page offset | 4KB页中的偏移 | 位11~0 |
实际的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位的虚拟地址空间。如下:
| 字段 | 描述 | 位数 |
|---|---|---|
| PML4 | 指向一个PDPT | 位47~39 |
| PGD | 指向PDPT中4个项中的一个 | 位38~30 |
| PMD | 指向二级页目录中512项中的一个 | 位29~21 |
| PTE | 指向页表中512项中的一个 | 位20~12 |
| page offset | 4KB页中的偏移 | 位11~0 |
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。
每天进步一点点……
图 二进制人生公众号





