虚拟内存
本系列笔记整理自 https://www.bilibili.com/video/BV1YE411D7nH
个人认为讲解比较清晰,容易理解。
一、虚拟内存的基本概念
1.1 传统存储管理方式的特征、缺点
在之前我们已经整理了连续分配和非连续分配的存储管理方式。但是,它们有一个共同的缺点就是很多暂时用不到的数据也会长期占用内存,导致内存利用率不高。
总结来说,这些传统的存储管理方式都有以下两个特征:
- 一次性:作业必须一次性全部装入内存后才能开始运行。这就导致那些无法一次装入内存的大作业无法运行。只有少量小作业可以运行,最终导致多道程序并发度下降。
- 驻留性:一旦作业被装入内存,就会一直驻留在内存中,直至作业运行结束。事实上,在一个时间段内,只需要访问作业的一小部分数据即可正常运行,这就导致了内存中会驻留大量数据,浪费了宝贵的内存资源。
为解决以上问题,人们便发明了虚拟存储技术。
1.2 局部性原理
在介绍什么是虚拟存储技术之前,我们复习一下这种比较先进的存储管理方式的实现原理——局部性原理。它包括时间局部性和空间局部性。
- 时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次被访问;如果某个数据被访问过,不久之后该数据很可能再次被访问。
- 空间局部性:一旦程序访问了某个存储单元,那么在不久之后,其附近的存储单元也很可能被访问。
1.3 虚拟内存的定义和特征
基于局部性原理,在程序装入时,可以将程序中很快要用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。
在程序执行过程中,当访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。这样在操作系统的管理下,用户看来,就似乎有一个比实际内存要大得多的内存,这就是虚拟内存。

总结来说,虚拟内存有以下三个主要特征:
- 多次性:无需再作业运行时一次性全部装入内存,而是允许被分成多次调入内存。
- 对唤性:在作业运行时无需一直常驻内存,而是允许在作业运行过程中,将作业换入换出。
- 虚拟性:从逻辑上扩充了内存的容量,使用户看到的内存容量远大于实际的容量。
1.4 如何实现虚拟内存技术
虚拟内存技术,允许一个作业分多次调入内存。如果采用连续分配方式,会很不方便实现。因此,虚拟内存的实现要建立在离散分配的内存管理方式基础上。
但和之前所讲的传统的非连续分配存储管理相比,虚拟内存的独特之处在于:
在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。这一步叫做请求调页(或请求调段)。
若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。这一步叫做页面置换。
这两种操作将在下面详细展开。
二、请求分页管理方式
2.1 页表机制
在基本分页管理的基础上,请求分页管理中,为了实现“请求调页”,操作系统需要知道每个页面是否已经调入内存;如果还没调入,那么也需要知道该页面在外存中的存放的位置。
当内存空间不足时,要实现“页面置换”,操作系统需要通过某些指标来决定到底换出哪些页面。有的页面没有被修改过,就不用浪费时间写回外存。有的页面修改过,就需要将外存中的旧数据覆盖。因此,操作系统也需要记录各个页面是否被修改的信息。
页号 | 内存块号 | 访问字段(最近被访问次数) | 修改位(页面是否修改) | 状态位(是否调入内存) | 外存地址 |
---|---|---|---|---|---|
0 | 无 | 0 | 0 | 0 | x |
1 | b | 10 | 0 | 1 | y |
2 | c | 6 | 1 | 1 | z |
2.2 缺页中断机构
在请求分页系统中,每当要访问的页面不在内存时,便产生一个缺页中断,然后由操作系统的缺页中断处理程序处理中断。此时,缺页的进程阻塞,放入阻塞队列,调页完成后将其唤醒,放回就绪队列。
如果内存中有空闲块,则为进程分配一个空闲块,将所缺页面装入该块,并修改页表中相应的页表项。如果内存中没有空闲块,则由页面置换算法选择一个页面淘汰,若该页面在内存期间被修改过,则要将其写回外存。未修改过的页面不用写回外存。
缺页中断是因为当前执行的指令想要访问的目标页面未调入内存而产生的,因此是内中断。
一条指令在执行期间,可能产生多次缺页中断。(如:copy A to B,将 A 中内容复制到 B 中,而 A 和 B 属于不同的页面,则有可能产生两次中断)
2.3 地址变换机构
下面我们来比较地址变换过程中,“请求分页”增加的步骤。与基本分页相比,根据它们的主要区别,我们需要新增步骤:
- 请求调页(查到页表项时进行判断)。
- 页面置换(需要调入页面,在没有空闲块时进行)。
- 需要修改请求页表中新增的表项。

在上面的过程中,我们补充几个细节:
- 一般只有“写指令”才需要修改“修改位”。并且,一般来说只需修改快表中的数据,只有要将快表项删除时才需要写回内存中的慢表。这样可以减少访存次数。
- 和普通中断处理一样,缺页中断处理依然需要保留 CPU 现场。
- 当空闲块不足时,我们需要用某种“页面置换算法”决定换出哪个页面。
- 换入/换出页面都需要启动慢速的 I/O 操作。因此,如果换入/换出操作过于频繁,会有很大的开销。
- 页面调入内存后,需要修改慢表,同时也要将表项复制到快表中,这样之后就可以直接从快表命中。
三、页面置换算法
3.1 最佳置换算法
最佳置换算法(OPT)的定义是:每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面,这样可以保证最低的缺页率。

上表中,第四次访问页面时,需要从 0,1,7 中淘汰一页。按最佳置换的规则,往后寻找,最后一个出现的页号就是要淘汰的页面。
同时,上表告诉我们一件事:缺页不一定需要置换!
但事实上,操作系统并不能知道之后会到达的进程,因此这种算法实际上是无法实现的。
3.2 先进先出置换算法
先进先出置换算法(FIFO):每次选择淘汰的页面是最早进入内存的页面。实现方法很简单,就是把调入内存的页面根据调入的先后顺序排成一个队列,需要换出页面时选择头页面即可。队列的最大长度取决于系统为进程分配了多少个内存块。

上表中,第四次访问页面时,需要取出队列的头,也就是页面 3,然后将页面 0 换入,从而实现页面置换。
FIFO 算法有一个独特的异常现象就是当为进程分配的物理块数增大时,缺页次数不减反增的异常现象。这叫做 Belady 异常。只有 FIFO 算法会产生 Belady 异常。同时,这种算法虽然实现简单,但算法性能差。
3.3 最近最久未使用置换算法
最近最久未使用置换算法(LRU):每次淘汰的页面是最近最久未使用的页面。实现方式是赋予每个页面对应的页表项,用访问字段记录该页面自上次被访问以来的时间 t。当需要淘汰一个页面时,选择现有页面中 t 值最大的,即最近最久未使用的页面。
该算法需要单独的硬件支持,虽然性能好,但是实现困难,开销大。
3.4 时钟置换算法
时钟置换算法是一种性能和开销比较均衡的算法,简单的实现方法为:为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成一个循环队列。
当某页被访问时,其访问位置为 1。当需要淘汰一个页面时,只需要检查页的访问位。如果是 0,则将其换出;如果是 1,则将它置为 0,继续检查下一个页面。
若一轮扫描中所有页面都是 1,则将这些页面的访问位依次置为 0 后,再进行第二次扫描(第二次扫描一定会有 0 的页面,因此简单的时钟置换算法最多会经过两轮扫描)。
四、页面分配策略
4.1 页面分配、置换策略
这里我们首先介绍一个概念——驻留集。它指的是请求分页存储管理中给进程分配的物理块的集合。
在采用了虚拟存储技术的系统中,驻留集的大小一般小于进程的总大小。若驻留集太小,会导致缺页频繁,系统要花大量时间来处理缺页,实际用于进程推进的时间很少。驻留集太大,又会导致多道程序并发性下降,资源利用率较低。所以需要选择一个合适的驻留集大小。
考虑到操作系统给每个进程分配的物理块数目不同,我们将分配分为两种情况:固定分配,也就是操作系统分配的物理块,在进程运行期间数量不再改变,即驻留集大小不变;可变分配,也就是分配的物理块数量会变化,即驻留集大小可变。
与之对应的,置换也有两种置换。它们分别是局部置换,发生缺页时只能选进程自己的物理块进行置换;全局置换,可以将操作系统保留的空闲物理块分配给缺页进程,也可以将别的进程持有的物理块置换到外存,再分配给缺页进程。
上面的分配策略和置换策略可以相互搭配,形成不同的方案。不过,我们需要注意:一定不会出现固定分配和全局置换的搭配,因为全局置换意味着一个进程拥有的物理块数量必然会改变,因此不可能是固定分配。
4.2 何时调入页面
这里我们主要介绍两种策略:
- 预调页策略:根据局部性原理,一次调入若干个相邻的页面可能比一次调入一个页面更高效。但如果提前调入的页面中大多数都没有被访问过,则又是低效的。因此,可以预测不久之后可能被访问到的页面,将它们预先调入内存,但目前预测成功率仅有 50% 左右,因此这种策略主要用于进程的首次调入。由程序员指出应该先调入哪些部分。
- 请求调页策略:进程在运行期间发现缺页时才将所缺页面调入内存。由这种策略调入的页面一定会被访问到,但由于每次只能调入一页,而每次调页都需要磁盘 I/O 操作,因此开销极大。
除此之外,我们还有一个意识:外存其实由两部分组成,分别是对换区和文件区。

当系统拥有足够的对换区空间时,页面的调入调出都是在内存和对换区之间进行。这样可以保证页面的调入、调出速度很快。在进程运行前,需将进程相关的数据从文件区复制到对换区。
若系统缺少足够的对换区空间,凡是不会被修改的数据都直接从文件区调入,由于这些页面不会被修改,因此换出时不必写回磁盘,下次需要时再从文件区调入即可。对于可能被修改的部分,换出时需写回对换区,下次需要时再从对换区调入。
4.3 抖动现象
刚刚换出的页面马上又要换入内存,刚刚换入的页面马上又要换出内存,这种频繁的页面调度行为称为抖动,或颠簸。产生抖动的主要原因是进程频繁访问的页面数目高于可用的物理块数
4.4 工作集
之前我们说过驻留集指的是请求分页存储管理中给进程分配的内存块的集合。而这里我们所说的工作集指的是在某段时间间隔里,进程实际访问页面的集合。
窗口尺寸可以理解在在某段时间间隔里访问页面的总数。工作集大小可能小于窗口尺寸,实际应用中,操作系统可以统计进程的工作集大小,根据工作及大小给进程分配若干个内存块。如:窗口尺寸为 5,经过一段时间的监测发现某进程的工作及最大为 3,那么说明该进程有很好的局限性,可以给这个进程分配 3 个以上的内存块即可满足进程的运行需要。