内存 分页、交换空间

目录

1. 分页

1.1 地址转换

1.2 页表存在哪里

1.3 列表中究竟有什么

1.4 分页的优缺点

2. 快速地址转换(TLB)

2.1 TLB 的基本算法

2.2 谁来处理 TLB 未命中

2.2.1 硬件处理

2.2.2 软件(操作系统)处理

2.3 TLB 的内容

2.4 上下文切换时对 TLB 的处理

2.4.1 清空 TLB

2.4.2 地址空间标识符

2.5 TLB 替换策略

3. 较小的页表

3.1 简单的解决方案:更大的页

3.2 混合方法:分页和分段

3.2.1 杂合方式的原理

3.2.2 杂合方式存在的问题

3.3 多级页表

3.3.1 多级页表的原理

3.3.2 多级页表的成本

3.3.3 两级页表示例

3.4 反向页表

3.5 将页表交换到磁盘

4.超越物理内存

4.1 交换空间

4.2 存在位

4.3 页错误

4.4 内存满了怎么办

4.4.1 缓存管理

4.4.2 最优替换策略

4.4.3 简单策略:FIFO、随机

4.4.4 利用历史数据:LRU

4.4.5 近似 LRU

4.4.6 考虑脏页

4.5 交换何时真正发生


Operating Systems: Three Easy Pieces 笔记
第18章 分页:介绍
第19章 分页:快速地址转换(TLB)
第20章 分页:较小的表
第 21 章 超越物理内存:机制
第 22 章 超越物理内存:策略

1. 分页

操作系统解决空间管理的两种方法

(1)将空间分割成不同长度的分片,就像虚拟内存管理中的分段。但是,这个解决方法存在固有的问题 --> 空间本身会碎片化(fragmented), 随着时间推移,分配内存会变得比较困难。

(2)将空间分割成固定长度的分片,在虚拟内存中,我们称这种思想为分页。将一个进程的地址空间分割成固定大小的单元,每个单元称为一页。相应地,我们把物理内存看成是定长槽块的阵列,叫作页帧(page frame)。每个这样的页帧包含一个虚拟内存页。

1.1 地址转换

图 18.1 展示了一个只有 64 字节的小地址空间,有 4 个 16 字节的页(虚拟页 0、1、2、3)。真实的地址空间肯定大得 多,通常 32 位有 4GB 的地址空间,甚至有 64 位。

图 18.2 展示了对应的物理内存,由一组固定大小的槽块组成。在这个例子中,有 8 个页帧(由 128 字节物理内存构成,也是极小的)。从图中可以看出,虚拟地址空间的页放在物理内存的不同位置。图中还显示,操作系统自己用了一些物理内存。

为了记录地址空间的每个虚拟页放在物理内存中的位置,操作系统通常为每个进程保存一个数据结构,称为页表(page table)。示例中,页表具有以下 4 个条目:(VP 0→物理帧 3)、(VP 1→PF 7)、 (VP 2→PF 5)和(VP 3→PF 2)。

页表是一个每进程的数据结构。如果在上面的示例中运行另一个进程,操作系统将不得不为它管理不同的页表,因为它的虚拟页显 然映射到不同的物理页面(除了共享之外)。

为了转换该过程生成的虚拟地址,首先将其分成两个组件:虚拟页面号(virtual page number,VPN)页内的偏移量(offset)。对于这个例子,因为进程的虚拟地址空间是 64 字节,我们的虚拟地址总共需要 6 位(2^6 = 64);页的大小为16 字节(2^4 ),因此我们有一个 2 位(64字节/16字节 = 2^2)的虚拟页号(VPN);位于 64 字节的地址空间,因此我们需要能够选择 4 个页。虚拟地址表示如下:

假设上面的加载是虚拟地址 21,其二进制形式是 010101。虚拟地址“21”在虚拟页“01”(或 1)的第 5 个(“0101”)字节处。我们的最终物理地址是 1110101(十进制 117),正是我们希望加载指令(见图 18.2)获取数据的地方。

1.2 页表存在哪里

页表可以变得非常大,例如,想象一个典型的 32 位地址空间,带有 4KB(2^12)的页。这个虚拟地址分成 20 位的 VPN 和 12 位的偏移量(1KB 的页面大小需要 10 位,只需增加两位即可达到 4KB)。 一个 20 位的 VPN 意味着,操作系统必须为每个进程管理 2^20个地址转换(大约一百万)。 假设每个页表格条目(PTE)需要 4 个字节,来保存物理地址转换和任何其他有用的东西, 每个页表就需要巨大的 4MB 内存!这非常大。现在想象一下有 100 个进程在运行:这意味 着操作系统会需要 400MB 内存,只是为了所有这些地址转换!

由于页表如此之大,我们没有在 MMU 中利用任何特殊的片上硬件,而是将每个进程的页表存储在内存中。

1.3 列表中究竟有什么

页表就是一种数据结构,用于将虚拟地址(或者实际上, 是虚拟页号)映射到物理地址(物理帧号)。因此,任何数据结构都可以采用。最简单的形式称为线性页表(linear page table),就是一个数组。操作系统通过虚拟页号(VPN)检索该数组,并在该索引处查找页表项(PTE),以便找到期望的物理帧号(PFN)。

每个 PTE 包含许多不同的位:

有效位(valid bit) 通常用于指示特定地址转换是否有效。例如,当一个程序开始运行时,它的代码和堆在其 地址空间的一端,栈在另一端。所有未使用的中间空间都将被标记为无效(invalid),如果进程尝试访问这种内存,就会陷入操作系统,可能会导致该进程终止。因此,有效位对于支持稀疏地址空间至关重要。通过简单地将地址空间中所有未使用的页面标记为无效,我们不再需要为这些页面分配物理帧,从而节省大量内存。

保护位(protection bit),表明页是否可以读取、写入或执行。同样,以这些位不允许的方式访问页,会陷入操作系统。

存在位(present bit)表示该页是在物理存储器还是在磁盘上(即它已被换出,swapped out)。当我们研究如何将部分地址空间交换(swap)到磁盘,从而支持大于物理内存的地址空间时,我们将进一步理解这一 机制。交换允许操作系统将很少使用的页面移到磁盘,从而释放物理内存。

脏位(dirty bit) 表明页面被带入内存后是否被修改过。

参考位(reference bit / 访问位 accessed bit)有时用于追踪页是否被访问,也用于确定哪些页很受欢迎,因此应该保留在内存中。

图 18.5 显示了来自 x86 架构的示例页表项[I09]。它包含一个存在位(P),确定是否允许写入该页面的读/写位(R/W), 确定用户模式进程是否可以访问该页面的用户/超级用户位 (U/S),有几位(PWT、PCD、PAT 和 G)确定硬件缓存如何为这些页面工作,一个访问位 (A)和一个脏位(D),最后是页帧号(PFN)本身。

1.4 分页的优缺点

与分段相比,分页有许多优点:

首先,它不会导致外部碎片,因为分页将内存划分为固定大小的单元。

其次,它非常灵活,支持稀疏虚拟地址空间

然而,实现分页支持而不小心考虑,会导致 / 缺点:

的机器(有许多额外的内存访问来访问页表)--> 映射信息一般存储在物理内存中,所以在转换虚拟地址时,每次指令获取、显式加载或保存,都要额外读一次内存以得到转换信息

内存浪费(内存被页表塞满而不是有用的应用程序数据)

2. 快速地址转换(TLB)

如何加速地址转换 --> 硬件

地址转换旁路缓冲存储器(translation-lookaside buffer,TLB)/ 地址转换缓存(address-translation cache),即频繁发生的虚拟到物理地址转换的硬件缓存(cache) -->

对每次内存访问,硬件先检查 TLB,看看其中是否有期望的转换映射,如果有,就完成转换(很快),不用访问页表 (其中有全部的转换映射)。

2.1 TLB 的基本算法

硬件算法的大体流程如下:

首先从虚拟地址中提取页号(VPN), 然后检查 TLB 是否有该 VPN 的转换映射。

如果有,我们有了 TLB 命中(TLB hit), 这意味着 TLB 有该页的转换映射。成功!接下来我们就可以从相关的 TLB 项中取出页帧号 (PFN),与原来虚拟地址中的偏移量组合形成期望的物理地址(PA),并访问内(第 5~7 行),假定保护检查没有失败。

如果 CPU 没有在 TLB 中找到转换映射(TLB 未命中),硬件访问页表来寻找转换映射,并用该转换映射更新 TLB, 假设该虚拟地址有效,而且我们有相关的访问权限。最后,当 TLB 更新成功后,系统会重新尝试该指令,这时 TLB 中有了这个转换映射,内存引用得到很快处理。

2.2 谁来处理 TLB 未命中

2.2.1 硬件处理

以前的硬件有复杂的指令集,有时称为复杂指令集计算机(Complex-Instruction Set Computer,CISC),一个例子是 x86 架构,硬件全权处理 TLB 未命中。为了做到这一点,硬件必须知道页表在内存中的确切位置(通过页表基址寄存器, page-table base register),以及页表的确切格式。发生未命中时, 硬件会“遍历”页表,找到正确的页表项,取出想要的转换映射,用它更新 TLB,并重试该指令。

2.2.2 软件(操作系统)处理

更现代的体系结构,都是精简指令集计算机(Reduced-Instruction Set Computer,RISC),有所谓的软件管理 TLB(softwaremanaged TLB)。发生 TLB 未命中时,硬件系统会抛出一个异常,这会暂停当前的指令流,将特权级提升至内核模式,跳转至陷阱处理程序(trap handler)。这个陷阱处理程序是操作系统的一段代码,用于处理 TLB 未命中。 这段代码会查找页表中的转换映射,然后用特别的“特权”指令更新 TLB,并从陷阱返回。然后,硬件重试该指令, TLB 命中。

注意两个重要的细节。
第一,这里的从陷阱返回指令稍稍不同于之前提到的服务于系统调用的从陷阱返回。
在后一种情况下,从陷阱返回应该继续执行陷入操作系统之后那条指令,就像从函数调用返回后,会继续执行此次调用之后的语句。
在前一种情况下,从 TLB 未命中的陷阱返回后,硬件必须从导致陷阱的指令继续执行。这次重试再次执行该指令,但这次会命中 TLB。
因此,根据陷阱或异常的原因,系统在陷入内核时必须保存不同的程序计数器,以便将来能够正确地继续执行。

第二,在运行 TLB 未命中处理代码时,操作系统需要避免TLB 未命中的无限递归。有很多解决方案,例如,可以把 TLB 未命中陷阱处理程序直接放到物理内存中 (它们没有映射过,不用经过地址转换)。或者在 TLB 中保留一些项,记录永久有效的地址转换,并将其中一些永久地址转换槽块留给处理代码本身,这些被监听的地址转换总是会命中 TLB。

2.3 TLB 的内容

VPN | PFN | 其他位

“其他位”,例如,TLB 通常有一个有效(valid)位,用来标识该项是不是 有效地转换映射。通常还有一些保护(protection)位,用来标识该页是否有访问权限。例如,代码页被标识为可读和可执行,而堆的页被标识为可读和可写。还有其他一些位,包括地址空间标识符(address-space identifier)、脏位(dirty bit)等。

2.4 上下文切换时对 TLB 的处理

TLB 中包含的虚拟到物理的地址映射只对当前进程有效,所以在发生进程切换时,硬件或操作系统(或二者)必须注意确保即将运行的进程不要误读了之前进程的地址映射。

这个问题有一些可能的解决方案。

2.4.1 清空 TLB

一种方法是在上下文切换时,简单地清空(flush)TLB, 这样在新进程运行前 TLB 就变成了空的。进程不会再读到错误的地址映射。但是,有一定开销:每次进程运行,当它访问数据和代码页时,都会触发 TLB 未命中。

如果是软件管理 TLB 的系统,可以在发生上下文切换时,通过一条显式(特权)指令来完成。

如果是硬件管理 TLB,则可以在页表基址寄存器内容发生变化时清空 TLB。

不论哪种情况,清空操作都是把全部有效位(valid)置为 0,本质上清空了 TLB。

2.4.2 地址空间标识符

如果操作系统频繁地切换进程,这种开销会很高。 为了减少这种开销,一些系统增加了硬件支持,实现跨上下文切换的 TLB 共享。比如,有的系统在 TLB 中添加了一个地址空间标识符(Address Space Identifier,ASID)。可以把 ASID 看作是进程标识符(Process Identifier,PID),但通常比 PID 位数少(PID 一般 32 位, ASID 一般是 8 位)。有了地址空间标识符,TLB 可以同时缓存不同进程的地址空间映。

2.5 TLB 替换策略

缓存替换(cache replacement)--> 向 TLB 中插入新项时,会替换(replace)一个旧项,这样问题就来了:应该替换那一个?

(1)替换最近最少使用(least-recently-used,LRU)的项。

(2)另一 种典型策略就是随机(random)策略,即随机选择一项换出去。

这种策略很简单,并且可以避免一种极端情况。例如,一个程序循环访问 n+1 个页,但 TLB 大小只能存放 n 个页。 这时之前看似“合理”的 LRU 策略就会表现得不可理喻,因为每次访问内存都会触发 TLB 未命中。

3. 较小的页表

现在来解决分页引入的第二个问题:页表太大,因此消耗的内存太多。

3.1 简单的解决方案:更大的页

从线性页表开始,假设有一个 32 位地址空间(2^32 字节),页表项4 字节

4KB的页(2^12 字节) --> 约一百万个虚拟页面 (2^32/2^12=1M),页表大小为 4MB

一种简单的减小页表大小的方法:使用更大的页

16KB 的页 -->18 位的 VPN +14 位的偏移量 --> 现在线性页表中有 2^18(256K个虚拟页面)个项,页表大小为 1MB(256K*4B),页表缩到四分之一

存在的问题:大内存页会导致每页内的浪费,浪费在分配单元内部,称为内部碎片(internal fragmentation)问题。因此,大多数系统在常见的情况下使用相对较小的页大小:4KB(如 x86)或 8KB(如 SPARCv9)。

3.2 混合方法:分页和分段

3.2.1 杂合方式的原理

假设我们有一个地址空间,其中 堆和栈的使用部分很小。例如,我们使用一个 16KB 的小地址空间和 1KB 的页(见图 20.1)。该地址空间的页表如表 20.1 所示。

这个例子假定单个代码页(VPN 0)映射到物理 页 10,单个堆页(VPN 4)映射到物理页 23,以及 地址空间另一端的两个栈页(VPN 14 和 15)被分别 映射到物理页 28 和 4。从图 20.1 中可以看到,大部 分页表都没有使用,充满了无效的(invalid)项,十分浪费。

因此,我们的杂合方式为每个逻辑分段提供一个页表。在这个例子中,我们可能有 3 个页表,地址空间的代码、堆和栈部分各有一个。

分段中有一个基址(base)寄存器,存放每个段在物理内存中的位置,还有一个界限(bound)或限制(limit)寄存器,存放该段的大小。在杂合方案中也有这些结构。不同的是,使用基址寄存器保存该段页表的物理地址;界限寄存器用于指示页表的结尾(即它有多少有效页)。

假设 32 位虚拟地址空间包含 4KB 页面,且地址空间分为 4 个段。本例中,我们只使用 3 个段:一个用于代码,另一个用于堆,还有 一个用于栈。 用地址空间的前两位确定地址引用的是哪个段。假设 00 是未使用的段,01 是代 码段,10 是堆段,11 是栈段。因此,虚拟地址如下所示:

在硬件中,假设有 3 个基本/界限对,代码、堆和栈各一个。当进程正在运行时,每个段的基址寄存器都包含该段的线性页表的物理地址。因此,系统中的每个进程现在都有 3 个与其关联的页表。在上下文切换时,必须更改这些寄存器,以反映新运行进程的页表的位置。 在 TLB 未命中时(假设硬件负责处理 TLB 未命中),硬件使用分段位(SN)来确定要用哪个基址和界限对。然后硬件将其中的物理地址与 VPN 结合起来, 形成页表项(PTE)的地址。

例如,如果代码段使用它的前 3 个页(0、1 和 2),则代码段页表将只有 3 个项分配给它,并且界限寄存器将被设置为 3。内存访问超出段的末尾将产生一个异常,并可能导致进程终止。以这种方式,与线性页表相比,杂合方法实现了显著的内存节省。栈和堆之间未分配的页不再占用页表中的空间(仅将其标记为无效)。

3.2.2 杂合方式存在的问题

首先,仍然要求使用分段。如果有一个大而稀疏的堆,仍然可能导致大量的页表浪费。

其次,这种杂合导致外部碎片再次出现。尽管大部分内存是以页面大小单位管理的,但页表现在可以是任意大小(是 PTE 的倍数)。因此,在内存中为它们寻找自由空间更为复杂。

3.3 多级页表

3.3.1 多级页表的原理

如何去掉页表中的所有无效区域,而不是将它们全部保留在内存中?

使用多级页表(multi-level page table)将线性页表变成了类似树的东西,eg:x86系统。

多级页表的基本思想:让线性页表的一部分消失(释放这些帧用于其他用途),并用页目录来记录页表的哪些页被分配。

首先,将页表分成页大小的单元。然后,如果整页的页表项(PTE)无效,就完全不分配该页的页表。使用名为页目录(page directory)的新结构追踪页表的页是否有效,以及(如果有效)它在内存中的位置。图 20.2 的左边是经典的线性页表。即使地址空间的大部分中间区域无效,我们仍然需要为这些区域分配页表空间(即页表的中间两页)。右侧是一个多级页表。页目录仅将页表的两页标记为有效(第一个和最后一个);因此,页表的这两页就驻留在内存中。

在一个简单的两级页表中,页目录为每页页表包含了一项。它由多个页目录项(Page Directory Entries,PDE)组成。PDE 至少拥有有效位(valid bit)和页帧号(page frame number, PFN),类似于 PTE。如果 PDE 项的有效位为1,则意味着该项指向的页表(通过 PFN)中至少有一页是有效的,即在该 PDE 所 指向的页中,至少一个 PTE。如果 PDE 项无效,则 PDE 的其余部分没有定义。 

其次,如果仔细构建,页表的每个部分都可以整齐地放入一页中,从而更容易管理内存。有了多级结构,我们增加了一个间接层 (level of indirection),使用了页目录,它指向页表的各个部分。这种间接方式,让我们能够将页表页放在物理内存的任何地方。

3.3.2 多级页表的成本

在任何复杂的多级页表访问发生之前,硬件首先检查 TLB。在命中时,物理地址直接形成,而不像之前一样访问页表。只有在 TLB 未命中时,硬件才需要执 行完整的多级查找。

以两级页表为例,在 TLB 未命中时,需要从内存加载两次,才能从页表中获取正确的地址转换信息(一次用于页目录,另一次用于 PTE 本身),而用线性页表只需加载一次。因此,多级表是一个时间-空间折中(time-space trade-off)的例子。在TLB 命中的情况下,性能和线性页表相同,但 TLB 未命中时,则会因较小的表而导致较高的成本。 另一个明显的缺点是复杂性。在 TLB 未命中时,无论是硬件还是操作系统来处理页表查找,无疑都比简单的线性页表查找更复杂。

3.3.3 两级页表示例

设想一个大小为 16KB 的小地址空间,其中包含 64 个字节的页,具体信息整理如下

地址空间16KB, 14位
页大小64字节,6位 -> 偏移量6位
VPN8位
线性页表2^8 = 256项
现在将将完整的线性页表 分解成 页大小的单元  
PTE 的大小

4字节 -> 页表大小1KB(4 字节×256项) ->

1KB 页表可以分为 16 个 64 字节的页(1024字节/64字节)

每个页可以容纳 16 个 PTE(64字节/4字节)

页目录需要几位来索引页表项所在的页

256 个项,分布在 16 (2^4)个页上 -> 

需要 4 位 VPN 来索引目录

图 20.3 展示了这种地址空间的一个例子。

在这个例子中,虚拟页 0 和 1 用于代码,虚拟页 4 和 5 用 于堆,虚拟页 254 和 255 用于栈。地址空间的其余页未被使用。 要为这个地址空间构建一个两级页表,我们从完整的线性页表开始,将它分解成页大小的单元。

在这个例子中,完整页表有 256 个项;假设每个 PTE 的大小是 4 个字节。因此,我们的完整页表大小为 1KB(256×4 字节)。页的大小为64 字节,1KB 页表可以分为 16 个 64 字节的页,每个页可以容纳 16 个 PTE。

这个例子中的页表很小:256 个项,分布在 16 个页上。页目录需要为页表的每页提供一个项,所以需要 4 位 VPN 来索引目录。我们使用 VPN 的前 4 位,如下所示:

PDIndex 页目录索引

PTIndex 页表索引

PDE 页目录项

PTE 页表项

从 VPN 中提取 PDIndex -> 索引到页目录 -> 如果 PDE 标记为无效则引发异常 (End)  如果 PDE 有效 -> 从页目录项指向的页表的页中获取 PTE -> 获取页

3.4 反向页表

不同于上述,系统的每个进程一个页表,有许多页表。反向页表,只保留一个页表。

反向页表(inverted page table)的页表项代表系统的每个物理页,告诉我们哪个进程正在使用此页,以及该进程的哪个虚拟页映射到此物理页。 现在,要找到正确的项,就是要搜索这个数据结构。线性扫描是昂贵的,因此通常在此基础结构上建立散列表,以加速查找。

3.5 将页表交换到磁盘

到目前为止,我们一直假设页表位于内核拥有的物理内存中。即使我们有很多技巧来减小页表的大小,但是它仍然有可能是太大而无法一 次装入内存。因此,一些系统将这样的页表放入内核虚拟内存(kernel virtual memory),从而允许系统在内存压力较大时,将这些页表中的一部分交换(swap)到磁盘。

4.超越物理内存

为了支持更大的地址空间,操作系统需要把当前没有在用的那部分地址空间找个地方存储起来。

比内存有更大的容量,所以一般来说也更慢 --> 硬盘(hard disk drive)

4.1 交换空间

我们将内存中的页交换到 交换空间(swap space),并在需要的时候又交换回去。因此,我们会假设操作系统能够以页大小为单元读取或者写入交换空间。为了达到这个目的,操作系统需要记住给定页的硬盘地址(disk address)。使用交换空间可以让系统假装内存比实际物理内存更大。

4.2 存在位

假设有一个硬件管理 TLB 的系统。 硬件首先从虚拟地址获得 VPN,检查 TLB 是否匹配,

如果命中,则获得最终的物理地址并从内存中取回。

如果 TLB 未命中,则硬件在内存中查找页表(使用页表基址寄存器),并使用 VPN 查找该页的页表项(PTE)作为索引。如果页有效且存在于物理内存中,则硬件从 PTE 中获得 PFN,将其插入 TLB,并重试该指令,这次 TLB 命中。不过,硬件在 PTE 中查找的页,有可能不在物理内存中。

硬件(或操作系统,在软件管理 TLB 时)判断是否在内存中的方法,是通过页表项中的一条新信息,即存在位(present bit)。如果存在位设置为 1,则表示该页存在于物理内存中。如果存在位设置为 零,则页不在内存中,而在硬盘上。访问不在物理内存中的页,这种行为通常被称为页错 误(page fault)。

4.3 页错误

在 TLB 未命中的情况下,如果页不存在,由操作系统负责处理页错误,将该页交换到内存中。

当硬盘 I/O 完成时,操作系统会更新页表,将此页标记为存在,更新页表项(PTE)的 PFN 字段以记录新获取页的内存位置,并重试指令。再次重新访问 TLB 还是未命中,但这次页在内存中,因此会将页表中的地址更新到 TLB 中(也可以在处理页错误时更新 TLB 以避免此步骤)。最后的重试操作会在 TLB 中找到转换映射,从已转换的内存物理地址,获取所需的数据或指令。

当 TLB 未命中发生的时候有 3 种重要情景:

第一种情况,该页有效且存在。在这种情况下,TLB 未命中处理程序可以简单地从 PTE 中获取 PFN,然后重试指令(这次 TLB 会命中)。

第二种情况,该页有效但不存在,页错误处理程序需要运行。虽然这是进程可以访问的合法页,但它并不在物理内存中。

第三种情况,访问的是一个无效页,可能由于程序中的错误。在这种情况下,PTE 中的其他位都 不重要了。硬件捕获这个非法访问,操作系统陷阱处理程序运行,可能会杀死非法进程。 

4.4 内存满了怎么办

在上面的描述中,我们假设有足够的空闲内存来从存储交换空间换入(page in)的页。但是,内存可能已满(或接近满了)。因此,操作系统可能希望先交换出(page out)一个或多个页,以便为操作系统即将交换入的新页留出空间。选择哪些页被交换出或被替换(replace)的过程,被称为页交换策略(page-replacement policy)

4.4.1 缓存管理

由于内存只包含系统中所有页的子集,因此可以将其视为系统中虚拟内存页的缓存(cache)。

因此,在为这个缓存选择替换策略时,我们的目标是让缓存未命中(cache miss)最少,即从磁盘获取页的次数最少。或者,让缓存命中(cache hit)最多,即在内存中找到待访问页的次数最多。

平均内存访问时间(Average Memory Access Time,AMAT)

4.4.2 最优替换策略

如果不得不踢出一些页,为什么不踢出在最远将来才会访问的页呢?

假设一个程序按照以下顺序访问 虚拟页:0,1,2,0,1,3,0,3,1,2,1。表 22.1 展示了最优的策略,这里假设缓存可以存 3 个页。

前 3 个访问是未命中,因为缓存开始是空的。这种未命中有时也称作冷启动未命中(cold-start miss,或强制未命中,compulsory miss)

计算缓存命中率:有 6 次命中和 5 次未命中,那么缓存命中率 54.5%。也可以计算命中率中除去强制未命中(即忽略页的第一次未命中),那么缓存命中率为 81.8%。

但是未来的访问是无法知道的,所以无法为通用操作系统实现最优策略。因此,最优策略只能作为比较,知道我们的策略有多接近“完美”。

4.4.3 简单策略:FIFO、随机

FIFO 命中率只有 36.4%(不包括强 制性未命中为 57.1%)。

任何像 FIFO 或随机这样简单的策略都可能会有一个共同的问题:它可能会踢出一个重要的页,而这个页马上要被引用。

4.4.4 利用历史数据:LRU

页替换策略可以使用的一个历史信息是频率(frequency)。如果一个页被访问了很多次, 也许它不应该被替换,因为它显然更有价值。页更常用的属性是访问的近期性(recency), 越近被访问过的页,也许再次访问的可能性也就越大。

最不经常使用(Least-Frequently-Used, LFU)策略会替换最不经常使用的页。同样,最少最近使用(Least-Recently-Used,LRU) 策略替换最近最少使用的页面。

4.4.5 近似 LRU

从计算开销的角度来看,近似 LRU 更为可行。这个想法需要硬件增加一个使用位(use bit,有时称为引用位, reference bit)

系统的每个页有一个使用位,存储在某个地方(例如,它们可能在每个进程的页表中,或者只在某个数组中)。每当页被引用(即读或写)时,硬件将使用位设置为 1。 但是,硬件不会清除该位(即将其设置为 0),这由操作系统负责。

有一个简单的实现近似 LRU方法称作 时钟算法(clock algorithm)。考虑系统中的所有页都放在一个循环列表中。时钟指针(clock hand)开始时指向某个特定的页(哪个页不重要)。当必须进行页替换时,操作系统检查当前指向的页 P 的使用位是 1 还是 0。如果是 1,则意味着页面 P 最近被使用, 因此不适合被替换。然后,P 的使用位设置为 0,时钟指针递增到下一页(P + 1)。该算法 一直持续到找到一个使用位为 0 的页(在最坏的情况下,所有的页都已经被使用了,那么就将所有页的使用位都设置为 0)。

4.4.6 考虑脏页

时钟算法的一个小修改是对内存中的页是否被修改的额外考虑。这样做的原因是:如果页已被修改(modified)并因此变脏(dirty),则踢出它就必须将它写回磁盘,这很昂贵。如果它没有被修改(因此是干净的,clean),踢出就没成本。物理帧可以简单地重用于其他目的而无须额外的 I/O。

因此,一些虚拟机系统更倾向于踢出干净页,而不是脏页。 为了支持这种行为,硬件应该包括一个修改位(modified bit,又名脏位,dirty bit)。每次 写入页时都会设置此位,因此可以将其合并到页面替换算法中。例如,时钟算法可以被改变, 以扫描既未使用又干净的页先踢出。无法找到这种页时,再查找脏的未使用页面。

4.5 交换何时真正发生

为了保证有少量的空闲内存,大多数操作系统会设置高水位线(High Watermark,HW) 和低水位线(Low Watermark,LW),来帮助决定何时从内存中清除页。原理 -->

操作系统发现有少于 LW 个页可用时,后台负责释放内存的线程会开始运行,直到有 HW 个可用的物理页。该后台线程也称为交换守护进程(swap daemon)或页守护进程(page daemon)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值