Linear page table 又叫 virtual page table,是一种方便虚拟机监控器 (VMM) / 操作系统 (OS) / 应用程序访问页表的技巧。Xen、64 位 Linux 内核、JOS 操作系统中都用到了这个设计。这里以 x86_32 系统为例,简单介绍一下它的实现和使用,如有错误敬请指出。

一般情况下,如果 OS 需要访问某个页表,需要将它映射到自己的虚拟空间中,然后再访问。这样带来两个问题,一是访问比较繁琐,需要临时的页映射;二是对于 exo-kernel 这种 fork 等行为都是在用户态程序实现的系统,可能会增加一下安全上的问题。因为用户程序在 fork 的时候需要访问自己的页表,而这时候除非操作系统提供另一些权限控制更精确的系统调用,否则就很难让不可信的应用程序访问自己的页表且不做有害的改动。

Linear page table 很好的解决了这两个问题。它的实现很简单,只需要在页目录中增加一项 VPT (virtual page table entry),和一般的页目录项不同的是,这个 VPT 指向的是页目录本身

这样带来了什么好处呢?借用一下 MIT 6.828 课件上的图片来更好的说明这个问题:

增加了 VPT 后,通常的物理地址 -> 虚拟地址的转换还是没变。和之前唯一的不同在于虚拟地址的页目录索引号 (PDX) 为之前设置的 VPT 的时候。

举个例子来说,假如现在要访问的虚拟地址是 (VPT << 22) | (VPT << 12),即这里的 PDX 和 PTX 都等于 VPT 的时候,整个转换过程是怎么样的呢(假设 TLB miss 的情况)?首先根据 CR3 中的物理地址,硬件开始查找页目录中的第 VPT 项,然后根据这一项中的物理地址,找到了下一级「页表」。注意这时候硬件以为自己得到的页表地址,实际上访问的还是页目录本身。同样,在这个「页表」中找到第 VPT 项指出去的最终页,得到了最终页的物理地址。因为 PTX 还是等于 VPT,所以最后得到的物理地址还是页目录的。

也就是说,通常的页表访问的顺序是 CR3->页目录->页表->最终页,现在访问这个特殊地址的过程则成了 CR3->页目录->页目录->页目录,通过 VPT 这一项在页目录上绕了两圈后返回。

接下来,再来看看如何通过这个机制来访问某个页表,假如现在要访问第 i 个页目录项指向的页表上的第 j 项,那么我们应该构造这样一个特殊地址:

(VPT << 22) | (i << 12) | (j * 4)

即 PDX = VPT, PTX = i, offset = j * 4。通过这个地址就能得到需要的页表项了,另外由于 (i << 12) | (j * 4) = (i * 1024 + j) * 4,定义 vpn 为虚拟页的编号,vpn = i * 1024 + j,则这个地址可以转换为

(VPT << 22) + vpn * 4

在 JOS 中,就是把 VPT 定义为一个 uint32_t 的数组,然后 vpt[vpn] 就是第 vpn 个虚拟页的页表项了。前面提到的另一个问题,如果要让用户以只读权限访问页表,又应该怎么做呢?很简单,在页目录中为用户设置另一个只读项,指向页目录自己就行了。