SV39 页表项
页表项,用于表示映射信息,其英文为 page table entry(简称为 PTE)。
页表项如下所示:
63 54 53 28 27 19 18 10 9 8 7 6 5 4 3 2 1 0
+----------+----------+----------+----------+-----+-+-+-+-+-+-+-+-+
| reserved | PPN[2] | PPN[1] | PPN[0] | RSW |D|A|G|U|X|W|R|V|
+----------+----------+----------+----------+-----+-+-+-+-+-+-+-+-+
10 26 9 9 2 1 1 1 1 1 1 1 1
其中:
V
:有效位,表示页表项是否 valid。R, W, X
:这些位分别表示页面是否可读、可写和可执行。当所有三个都为零时,PTE 指向页面表的下一级;否则,它是一个 leaf PTE。U
:用户位,表示对应虚拟页面是否可在用户态下访问。G
:全局位,表示对应的页面是否是全局的。对于 non-leaf PTE,全局设置意味着页表后续级别中的所有映射都是全局的。请注意,未能将全局映射标记为全局只会降低性能,而将非全局映射标记为全局则是一个致命错误。A
:访问位,表示对应的页面是否被访问过。D
:脏位,表示对应的页面是否被写过。RSW
:为内核程序预留,硬件不会对此做任何其他操作。
下表列出了所有可能的 R, W, X
组合:
X | W | R | Meaning |
---|---|---|---|
0 | 0 | 0 | Pointer to next level of page table. |
0 | 0 | 1 | Read-only page. |
0 | 1 | 0 | Reserved for future use. |
0 | 1 | 1 | Read-write page. |
1 | 0 | 0 | Execute-only page. |
1 | 0 | 1 | Read-execute page. |
1 | 1 | 0 | Reserved for future use. |
1 | 1 | 1 | Read-write-execute page. |
SV39 的具体翻译过程
在页表文档中,我们大致介绍了翻译的流程,所以这里我们只举例中间的某一层页表翻译过程。
由于页表是按照页大小对齐的,所以页表的地址后 12 位全零,因此很多时候不用特别指定。
假设我们要翻译某一层的虚拟地址 (VPN),记为 \(v\),页表起始位置位于 \(pt\)(后 12 位全零)。
由于每一层都是 9 位,而页表项都是 8 bytes,因此对应的页表项起始位置位于 \(pt+v \times 8\),这样我们就拿到了页表项。页表项将会指向一个地址 \(p\)(只知道 12-55 位,其他为可以认为全零),12-55 位可以通过三个 PPN 相拼得到。
接着,我们进行一些访问合法性的验证(如验证有效位,用户态下验证是否用户是否可访问等),如果没有通过验证,就会产生异常。
最后,检查页表是否为叶子节点。如果 RWX
都是零,则不是叶子节点,其指向的地址 \(p\)(前面已经给出的计算方法)为下一级页表的起始位置;否则,页表项位叶子节点,其指向的地址加上剩下未映射的部分即为映射到的物理地址。
延伸内容:大页
我们注意到映射很多个页面时,会占用很多个页表项。如果有很多个页面是连续的(如 \(2^9\) 个对齐且连续的页面,这在 buddy allocation 中很常见),那么其实不需要这么多页表项。
RISCV 提供了大页映射的方案,可以映射 2 MiB 以及 1 GiB 的页面。对于 1 GiB 的大页,只需要在第一级页表上设置大页的 RWX
权限即可,不需要为下面的所有页面设置单独的页表项。同理,对于 2 MiB 的大页,只需要在第二级页面设置权限即可。