由于一个地址空间可能会对应很多页,从而导致每一个页表比较大。而每一个进程都有一个页表,则会给内存带来很大的压力。这里就是为了解决这个问题,如何让页表变得更小。

更大的页

我们可以让每一个页更大,如果每一个页可以对应更多的地址空间,那么页表需要存储的映射将会变少。这也意味着每一个页表会变得更小。

这种解决办法会带来新的问题,如果每个页太大,则会导致每个页所分配的内存用不完,导致内部碎片。

分页和分段混合

在单独采用页表时,一个进程的地址空间映射可能如下图:

image-20230419213904915

在上图中,一个地址空间会分为多个页,它单独对应一个页表。但是真正使用的空间却只有上图那些白色区域,而灰色区域是没有使用的,但是他们也被映射了具体的物理地址,而且这些映射还存在页表当中。

所以我们可以采用分段时的思想,不再将进程的整个空间分配单个页表,而是为进程的每个逻辑分段提供一个页表。

在上图的例子中,我们可以给代码段一个页表,给堆和栈分别一个页表。

在分段中,一个基址寄存器告诉我们每个段对应的物理地址,还有界限寄存器告诉我们段的大小。而在分段和分页混合的方案中,我们的基址寄存器存储的是该段的页表的物理地址。界限寄存器用于指示页表有有多少有效页。

存在的问题

如果存在一个大而稀疏的堆,那么还是要为他分配对应的物理空间,这些映射都要存储在页表当中,也会造成页表的浪费。

其次,这种杂合的方式会导致外部碎片产生。

多级页表

多级页表将线性页表变成了类似于树的东西。而且也是为了解决页表中存在着没有用的映射。

它的设计是,将页表分成页大小的单元。如果整个页的页表项都是无效的,就不为该页分配页表。为了追踪页表的页是否有效,以及有效页对应物理内存的位置,使用了一种页目录的新结构。

之前的页表与多级页表的对比如下图:

image-20230419222341075

上图左边,页表中存储了203以及204这两页对应的虚拟地址到物理地址的映射,但是这两页却没有存储任何内容。

而在右图,内存中存储了页目录,页目录标记了哪些页是使用过的,而此时的页表中也有多个页,我们只需要加载页表中两个页即可。这两个页存储了使用了的页的映射。

存在的问题:

如果TLB未命中,那么我们就需要从内存中访问两次,才可以找到虚拟地址到物理地址的映射。第一次用于访问页目录,从页目录中找到对应页表,第二次用于从页表中找到对应虚拟地址到物理地址的映射。