页结构

页结构

请求分页式的页结构主要包含以下部分:

  • 页帧:被分成一页大小的逻辑地址
  • 页框:被分成一页大小的物理地址
  • 页表:页帧映射到页框的表格
  • 标志位:标志页帧是否已成功映射到页框,0 否 1 是

其中页表包含以下位:

  • 物理块号:指出该页在主存中的占用块(1 表示访问)
  • 状态位:指出该页是否已经调入主存(1 表示调入)
  • 访问字段:记录该页一段时间内被访问的次数或多久未被访问
  • 修改位:表示该页调入主存后是否被修改
  • 辅存地址:该页在磁盘上的地址

由于作业在磁盘上保留一份备份,若此次调入主存中后未被修改置换该页时不需要再将该页写回磁盘,减少系统开销;如果该页被修改,必须将该页写回磁盘上,保证信息的更新和完整。

页表的实现

实现虚拟地址到物理地址转换最容易想到的方法是使用数组,对虚拟地址空间的每一个页,都分配一个数组项。但是有一个问题,考虑 IA32 体系结构下,页面大小为 4KB,整个虚拟地址空间为 4GB,则需要包含 1M 个页表项,这还只是一个进程,因为每个进程都有自己独立的页表。因此,系统所有的内存都来存放页表项恐怕都不够。

相像一下进程的虚拟地址空间,实际上大部分是空闲的,真正映射的区域几乎是汪洋大海中的小岛,因次我们可以考虑使用多级页表,可以减少页表内存使用量。实际上多级页表也是各种体系结构支持的,没有硬件支持,我们是没有办法实现页表转换的。

为了减少页表的大小并忽略未做实际映射的区域,计算机体系结构的设计都会靠虑将虚拟地址划分为多个部分。具体的体系结构划分方式不同,比如 ARM7 和 IA32 就有不同的划分。Linux 操作系统使用 4 级页表:

Linear Address

图中 CR3 保存着进程页目录 PGD 的地址,不同的进程有不同的页目录地址。进程切换时,操作系统负责把页目录地址装入 CR3 寄存器。地址翻译过程如下:

  • 对于给定的线性地址,根据线性地址的 bit22~bit31 作为页目录项索引值,在 CR3 所指向的页目录中找到一个页目录项。

  • 找到的页目录项对应着页表,根据线性地址的 bit12~bit21 作为页表项索引值,在页表中找到一个页表项。

  • 找到的页表项中包含着一个页面的地址,线性地址的 bit0~bit11 作为页内偏移值和找到的页确定线性地址对应的物理地址。

这个地址翻译过程完全是由硬件完成的。

页表转化失败

在地址转换过程中,有两种情况会导致失败发生。

  • 第一,要访问的地址不存在,这通常意味着由于编程错误访问了无效的虚拟地址,操作系统必须采取某种措施来处理这种情况,对于现代操作系统,发送一个段错误给程序;或者要访问的页面还没有被映射进来,此时操作系统要为这个线性地址分配相应的物理页面,并更新页表。

  • 第二,要查找的页不在物理内存中,比如页已经交换出物理内存。在这种情况下需要把页从磁盘交换回物理内存。

CPU 的 Memory management unit(MMU)cache 了最近使用的页面映射。我们称之为 translation lookaside buffer(TLB)。TLB 是一个组相连的 cache。当一个虚拟地址需要转换成物理地址时,首先搜索 TLB。如果发现了匹配(TLB 命中),那么直接返回物理地址并访问。然而,如果没有匹配项(TLB miss),那么就要从页表中查找匹配项,如果存在也要把结果写回 TLB。