笔者愚钝,在页表自映射这地方花了很长时间,幸得大佬指点迷津才得以理解。
# 目的
在普通的分页机制中,页目录和页表只在物理内存中出现,而程序只能通过虚拟地址对内存进行操作。这就导致了操作系统难以访问和修改它们。
于是产生了页表自映射机制来解决这一问题。自映射通过 将页目录和页表映射到固定的虚拟地址,使得:
内核可直接通过虚拟地址修改页表,无需临时映射或关闭分页,像访问普通内存一样操作页表即可。
# 实现
共有 1024
个二级页表(以下简称页表),每个页表记录了 1024
条映射关系,大小为 4KB
。这些页表物理上不连续,但映射到虚拟空间是连续的,共占 4MB
的空间(并且 4MB
对齐)
注意到,页目录记录了 1024
个页表的物理页号,它和页表在格式上完全一致。故为了节省 4KB
的空间,我们将页目录也作为这 4M
中的一个页表。既然 页目录同时作为页表 ,那么它需要 有一个页目录项来记录自己(作为页表)的物理页号。这便是页目录的自映射
于是乎,页目录便有了三重身份:页目录、页表、普通的物理页。
# 地址计算
页目录应该位于这 4MB
中的什么位置呢?自映射的页目录项又应该在什么位置呢?这是两个值得探讨的问题,笔者当时正是在此处陷入困境
推导过程可用一句话概括:由一级偏移量 得到 二级偏移量 和 页内偏移量
设页表空间的虚拟基址为 PTbase
,页目录的虚拟基址为 PTbase
。由于页表空间是 4MB
对齐的,必有
PTbase = (PTbase >> 22 ) << 22 // 即 PTbase 的低 22 位全部为 0 |
因此 PDbase
的高 10 位与 PTbase
相同。根据虚拟地址结构,这便是 页目录自映射项的偏移量(一级偏移量,按字偏移)
由于 页目录同时也作为页表,所以其二级偏移量也是 PTbase[31 : 22]
(按字偏移)
PDbase
是 4KB
对齐的,所以低 12 位为 0。得到公式:
PDbase = PTbase | (PTbase >> 10) |
而自映射项的虚拟地址,只需要加上页内偏移量即可。
由于页目录同时也作为普通页,所以自映射项的偏移量与一级、二级偏移量相同,但是 按字节偏移。
PDEself = PTbase | (PTbase >> 10) | (PTbase >> 20) |