浅析操作系统的分页表

缘起

先讨论为什么需要页表,在Linux操作系统中,使用的是虚拟内存,因为对于一个应用程序,数据在物理磁盘上存放是零散的,而且部分数据还可能暂时存储在外部磁盘上,在需要使用的时候进行数据交换,我们通过一种技术,让程序自己认为可以拥有连续的内存(一个连续完整的地址空间),这种技术的高处是使得大型程序编写更容易,对真正的物理内存使用也更有效。

虚拟内存不只是“用磁盘空间来扩展物理内存”的意思——这只是扩充内存级别以使其包含硬盘驱动器而已。把内存扩展到磁盘只是使用虚拟内存技术的一个结果,它的作用也可以通过覆盖或者把处于不活动状态的程序以及它们的数据全部交换到磁盘上等方式来实现。对虚拟内存的定义是基于对地址空间的重定义的,即把地址空间定义为“连续的虚拟内存地址”,以借此“欺骗”程序,使它们以为自己正在使用一大块的“连续”地址。

页表(page table)是一种数据结构,它用于计算机操作系统中的虚拟内存系统,其存储了虚拟地址到物理地址间的映射。虚拟地址在访问进程中是唯一的,而物理地址在硬件(比如内存)中是唯一的。

注意:

那些需要快速访问或者反应时间非常一致的嵌入式系统,和其他的具有特殊应用的计算机系统,可能会为了避免让运算结果的可预测性降低,而选择不使用虚拟内存。

什么是页表

页表(page table)是一种数据结构,它用于计算机操作系统中的虚拟内存系统,其存储了虚拟地址到物理地址间的映射。虚拟地址在访问进程中是唯一的,而物理地址在硬件(比如内存)中是唯一的。

在操作系统中使用虚拟内存,每个进程会认为使用一块大的连续的内存。事实上,每个进程的内存散布在物理内存的不同区域。或者可能被调出到备份存储中(一般在硬盘)。当一个进程请求自己的内存,操作系统负责把程序生成的虚拟地址,映射到实际存储的物理内存上。操作系统在分页表中存储虚拟地址到物理地址的映射。每个映射被称为页表项(page table entry,PTE)。

页表CPU转换过程

CPU的内存管理单元(memory management unit MMU)存储最近用过的映射缓存,来自操作系统分页表。被称为转译后备缓冲器(translation lookaside buffer, TLB)。TLB是一个索引缓存。

当需要将虚拟地址转换为物理地址时,首先搜索TLB。如果找到匹配(TLB命中),则返回物理地址并继续存储器访问。然而,如果没有匹配(称为TLB未命中),则存储器管理单元或操作系统TLB未命中处理器通常会查找页表中的地址映射以查看是否存在映射(页面遍历)。如果存在,则将其写回TLB(这必须完成,因为硬件通过虚拟存储器系统中的TLB访问存储器),并且重启错误指令(这也可以并行发生)。此后续转换将找到TLB命中,并且内存访问将继续。

转换失败

有两种原因导致分页表查找失败。第一种,如果该地址没有可用的转换,这意味该虚拟地址的存储器访问是无效的。这通常是程序错误导致,操作系统需要处理这个问题。现代操作系统会发送一个段错误信号给出错程序。

当物理内存中不存在这个页,也会引起分页表查找失败。如果请求的页面被调出物理内存,给其他页腾出空间,会引起这个错误。这种情况下,页被分配到存储在介质上的辅助存储,例如硬盘。(这种辅助存储,或叫备用存储,如果是一个硬盘分区或者交换文件, 经常称之为交换分区,如果是文件,叫做分区文件或页文件。)这时候,分页需要从硬盘放回到物理内存中,这类操作通常会导致一种称为系统抖动(thrashing)的情况,通过局部性原理(principle of locality)来预测将来可能会访问的块,避免出现系统抖动。

当物理内存没满的时候,这是一个简单操作。页被写回物理内存,页表和转换备用缓冲会更新,指令重启。然而,当物理内存已满,一个或多个页要被调、为请求的页面腾出空间时候。页表需要更新,标识出那些在物理内存被调出的页,然后标识那些从硬盘调入物理内存的页。TLB也需要更新,包括去掉调出的页,重启指令。页的调入调出请见页置换算法

分页表数据

最简单的分页表系统通常维护一个帧表和一个分页表。帧表处理帧映射信息。更高级系统中,帧表可以处理页属于的地址空间,统计信息和其他背景信息。

分页表处理页的虚拟地址和物理帧的映射。还有一些辅助信息,如当前存在标识位(present bit),脏数据标识位或已修改的标识位,地址空间或进程ID信息。

辅助存储,比如硬盘可以用于增加物理内存。页可以调入和调出到物理内存和磁盘。当前存在标识位可以指出那些页在物理内存,哪些页在硬盘上。页可以指出如何处理这些不同页。是否从硬盘读取一个页,或把其他页从物理内存调出。

脏数据标识位可以优化性能。一个页从硬盘调入到物理内存中,读取后调出,当页没有更改不需要写回硬盘。但是如果页在调入物理内存后被修改,会标记其为脏数据,意味着该页必需被写回备用存储。这种策略需要当页被调入到内存后,后备存储保留一份页的拷贝。有了脏数据标志位,一些页随时会同时存在于物理内存和后备存储中。

如果不是单地址空间操作系统,地址空间或进程id信息是必要的,内存管理系统需要知道页对应的进程。两个不同意图的进程可以使用两个相同的虚拟地址。分页表必需为两个进程提供不同的虚拟内存。用虚拟内存页关联进程ID页可以帮助选择要调出的页,当页关联到不活跃进程上,特别是那些主要代码页被调出的进程,相比活跃进程需要页的可能性会更小。

分页表类型

有一些不同的分页表类型,适合不同应用场景。本质上,准系统分页表必需存储虚拟地址,物理地址排在之后,还有一些可能的地址空间信息。

倒排分页表

倒排分页表(IPT)被认为是一个标准RAM的TLB的off-chip扩展。不同于一个真实的分页表,IPT不需要处理所有当前映射。操作系统要准备处理丢失,就像mips风格的软件填充TLB。

IPT把分页表和帧表结合成一个数据结构。其核心是一个固定大小的表,表的行数等于内存中的帧数。如果有4000个帧,倒排分页表有4000行。每个行的表项对应虚拟内存页码,物理页页码(不是物理地址),一些其他数据,创建碰撞链。

从所有IPT内核结构中搜索表项效率很低,所以我们用哈希表来映射虚拟地址(或可能需要的地址空间/PID信息)对应的IPT索引,这里会使用冲突链。哈希表被称作哈希锚点表。哈希方式没对覆盖率做通常的优化,原始速度更理想。当然,哈希表会有冲突。由于这个哈希方式,我们使用时候会经历很多冲突,所以表中VPN创建的每个表项,会检查是否是已被检索的表项或冲突。

在搜索映射时候,使用哈希锚点表。如果没有表项存在,会出现一个页错误。否则,表项被找到。依赖于架构,表项被重新放入TLB,内存指令重启,或跟踪冲突链直到结束,然后出现页错误。

这种模式种,一个虚拟地址可以分成2部分。前半段是虚拟页码,后半段是页中的偏移量。

这种设计的主要问题在于哈希方式cache命中率较弱。基于树的设计,忽略在分页表表项中置换相邻位置的相邻页。但是一个倒排分页表把表项离散,会破坏引用的空间局部性。为了减少这个问题,操作系统会使哈希表空间最小,平衡增长的未命中比率。通常会有一个哈希表,在物理内存中连续,共享给所有进程。内存碎片会导致预处理分页表不现实,所以用一个预处理标识消除不同进程的页的歧义。从进程中去掉分页表项或许有点慢,操作系统会忽略重用预处理标识值,延迟面对这个问题,选择忍受大量内存浪费,用于映射预处理哈希表。

多级分页表

倒排分页表为所有物理内存中的帧,创建一个映射列表。但是这个可能会比较浪费。相反,我们通过保持一些覆盖当前虚拟内存区块的分页表,创建一个包含虚拟页映射的分页表数据结构。比如,我们创建1024个小于4K的页,覆盖4M的虚拟内存。

这非常有用,因为通常虚拟内存的顶部和底部用于正在运行的进程,顶部通常用于文本和数据段,底部用于堆栈,空闲内存居中。多级分页表会保持少量较小分页表,仅覆盖内存顶部和底部,只有确实需要时候才创建新的。每种较小分页表被一个主分页表链接再一起,有效的创建一个树型数据结构。需要不只2级,可能有多级。

这种模式下,一个虚拟地址可以分成三部分:根分页表的索引,子分页表的索引,子分页表偏移量。多级分页表也叫做分级分页表。

虚拟分页表

已经提到了,在虚拟地址空间中为每个虚拟页创建分页表结构,最终会导致浪费。但是我们可以把分页表放入虚拟内存,允许虚拟内存管理分页表内存,来避免过多空间被涉及。

但是一部分这种线性分页表结构,需要一直存在于物理内存,来防范循环页错误。会寻找一个不存在于分页表中的重要部分,在当前分页表中没有。

嵌套分页表

嵌套分页表可以被用于增加硬件虚拟化的性能。为分页表虚拟化提供硬件支持,会大大降低模拟的需要。x86下虚拟化当前的选择是Intel的扩展分页表特性,和AMD的快速虚拟索引特性。

posted @ 2021-01-21 18:06  等不到的口琴  阅读(775)  评论(0编辑  收藏  举报