第五章 虚拟存储器
5.1 虚拟存储器概述
5.1.1 常规存储管理方式的特征和局部性原理
(1)常规存储管理方式的特征
上一章(第四章)所介绍的各种存储器管理方式统称为传统存储器管理方式,具有的共同特征为:
-
一次性
作业必须一次性地全部装入内存后才能开始运行。
-
驻留性
作业被装入内存后,整个作业都一直驻留在内存中,直至作业运行结束。
这两个特征使得许多在程序运行中不用或暂时不用的程序(数据)占据了大量的内存空间。
(2)局部性原理
1968 年,由 P.Denning 指出。局限性表现在以下两个方面:
-
时间局限性。
程序中的某条指令被执行,则不久后该指令可能再次执行;
程序中的某数据被访问过,则不久后该数据可能再次被访问。
**典型原因:**程序中存在着大量的循环操作。
-
空间局限性。
一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问。
典型原因: 程序顺序执行。
5.1.2 虚拟存储器的定义和特征
(1)虚拟存储器的定义
虚拟存储器,是指具有请求调入功能和置换功能,能从逻辑上对内存容量加以扩充的一种存储器系统。
(2)虚拟存储器的特征
-
多次性
相对于传统存储器管理方式的一次性而言。
指一个作业中的程序和数据无需在作业运行时一次性地全部装入内存,而是允许被分成多次调入内存运行。
-
对换性
相对于传统存储器管理方式的对换性而言。
指一个作业中的程序和数据无须在作业运行时一直常驻内存,而是允许在作业的运行过程中进行换进换出。
-
虚拟性
指能从逻辑上扩充内存容量,使用户看到的内存容量远大于实际内存容量。
虚拟性以多次性和对换性为基础。
5.1.3 虚拟存储器的实现方法
所有虚拟存储器的实现都是建立在离散分配存储管理方式的基础上的。
(1)分页请求系统
在分页系统的基础上增加了:
- 请求调页功能
- 页面置换功能
允许用户程序只装入少数页面的程序(数据)即可启动运行,之后在通过调页功能及页面置换功能陆续地把即将运行的页面调入内存,同时把暂不运行的页面换出到外存上。置换时以页面为单位。
-
需要的硬件支持:
- 请求分页的页表机制
- 缺页中断机构
- 地址变换机构
-
实现请求分页的软件:
包括有用于实现请求调页的软件和实现页面置换的软件
(2)请求分段系统
在分段系统的基础上增加了:
- 请求调段功能
- 分段置换功能
允许用户程序只装入少数段的程序(数据)即可启动运行,之后在通过调段功能和段的置换功能将暂不运行的段换出,再调入即将运行的段。置换时以段为单位。
-
需要的硬件支持:
- 请求分段的段表机制
- 缺页中断机构
- 地址变换机构
-
软件支持:
包括有用于实现请求调段的软件和实现段置换的软件
在内存分配和回收上都比较复杂
5.2 请求分页存储管理方式
5.2.1 请求分页中的硬件支持
计算机系统除了要求一定容量的内存和外存外,还需要以下 3 种支持:
(1)请求页表机制
为了满足页面换进换出的需要,在请求页表中又增加了 4 个字段:
页号 物理块号 状态位 P 访问字段 A 修改位 M 外存地址 \begin{array}{| c | c | c | c | c | c |} \hline \text{页号} & \text{物理块号} & \text{状态位 P} & \text{访问字段 A} & \text{修改位 M} & \text{外存地址}\\ \hline \end{array} 页号物理块号状态位 P访问字段 A修改位 M外存地址
-
状态位 P
用于指示该页是否已调入内存。仅有一位,也称为“位字”。
供程序访问时参考。
-
访问字段 A
用于记录本页在一段时间内被访问的次数,或者记录本页最近已有多长时间未被访问。
供置换算法在选择换出页面时参考。
-
修改位 M
用于标识该页在调入内存后是否被修改过。置换该页时,若未被修改,就不必将该页再写回外存上了。
供置换页面时参考。
-
外存地址
用于指出该页在外存上的地址,通常是物理块号。
供调入该页时参考。
(2)缺页中断机构
缺页中断是一种特殊的中断,与一般的中断相比,区别主要表现在以下两个方面:
-
在指令执行期间产生和处理中断信号
通常,CPU 都是在一条指令执行完后,才检查是否有中断请求到达。
然而缺页中断是在指令执行期间,能够立即产生和处理缺页中断信号。
-
一条指令在执行期间可能产生多次缺页中断
(3)地址变换机构

5.2.2 请求分页中的内存分配
在为进程分配内存时,需要涉及到以下 3 个问题:
(1)最小物理块数的确定
最小物理块数是指能保证进程正常运行所需要的最小物理块数,当系统为进程分配的物理块数小于此值时,进程将无法运行。
最小物理块数与计算机的硬件结构有关,取决于指令的格式、功能和寻址方式。
(2)内存分配策略
-
固定分配局部置换
固定分配 — — 为每个进程分配一组固定数目的物理块,在进程运行期间不再改变。
局部置换 — — 如果进程在运行中发现缺页,则只能从分配给该进程的 n 个页面中选一页换出,然后再调入一页,保证分配给该进程的内存空间不变。
缺点:
-
难以确定为每个进程分配多少个物理块。
太少 — — 频繁出现缺页中断,降低了系统的吞吐量
太多 — — 内存中驻留的进程数目减少
-
-
可变分配全局置换
可变分配 — — 先为每个进程分配一定数目的物理块,在程序运行期间,可根据情况做适当的增加或减少。
全局置换 — — 如果进程在运行中发现缺页,则将 OS 所保留的空闲物理块取出一块分配给该进程,或者从所有进程的全部物理块未标记的选择一块换出,将所缺之页调入。
采用这种策略时,范式产生缺页(中断)的进程,都将获得新的物理块,仅当空闲物理块队列中的物理块用完时,OS 才能从内存中选择一页调出。这一页可能是系统红任何一个进程的页,因此被选中的进程所拥有的物理块会减少,导致其缺页率增加。
-
可变分配局部置换
当进程发现缺页时,只允许从该进程在内存的页面中选择一页换出,这样就不会影响其他进程的运行。
如果进程在运行中频繁地发生缺页中断,则系统须再为该进程分配若干附加的物理块,直至该进程的缺页率减少到适当程度为止。反之,若一个进程在运行过程中的缺页率特别低,则此时可适当减少分配给该进程的物理块数,但不应引起其缺页率的明显增加。
(3)物理块分配算法
- 平均分配算法
- 按比例分配算法
- 考虑优先权的分配算法
5.2.3 页面调入策略
(1)何时调入页面
-
预调页策略
将那些预计在不久之后便会访问的页面预先调入内存。但是这样的成功率目前仅有 50%。
但是可以应用于进程第一次调入内存的过程。
-
请求调页策略
当进程在运行中需要访问某部分程序和数据时,若发现其所在的页面不在内存,便由 OS 将其所需页面调入内存。由请求调页策略所确定调入的页是一定会被访问的。
在目前的虚拟存储器中,大多采用此策略。但这种策略每次仅调入一页,故须花费较大的系统开销,增加了磁盘 I/O 的启动频率。
(2)从何处调入页面
将请求分页系统中的外存分为两部分:
- 用于存放文件的文件区
- 用于存放对换页面的对换区
通常,由于对换区是采用连续分配方式,而文件区是采用离散分配方式,所以对换区的数据存取(磁盘 I/O)速度比文件区的高。
-
系统拥有足够的对换区空间
这时可以全部从对换区调入所需页面,以提高调页速度。为此,在进程运行前,便须将与该进程有关的文件从文件区拷贝到对换区。
-
系统缺少足够的对换区空间
这时凡是不会被修改的文件,都直接从文件区调入;而当换出这些页面时,由于它们未被修改,则不必再将它们重写到磁盘(换出),以后再调入时,仍从文件区直接调入。
对于那些可能被修改的部分,在将它们换出时便须调到对换区,以后需要时再从对换区调入。
-
UNIX 方式
由于与进程有关的文件都放在文件区,故凡是未运行过的页面,都应从文件区调入。而对于曾经运行过但又被换出的页面,由于是被放在对换区,因此在下次调入时应从对换区调入。由于 UNIX 系统允许页面共享,因此,某进程所请求的页面有可能已被其它进程调入内存,此时也就无需再从对换区调入。
(3)缺页率
缺页率计算公式为:
缺页率 = 访问时缺页的次数 总访问次数 \text{缺页率} = {\text{访问时缺页的次数} \over \text{总访问次数}} 缺页率=总访问次数访问时缺页的次数
影响缺页率的几个因素:
- 页面大小
- 进程所分配的物理块的数目
- 页面置换算法
- 程序固有特性
5.3 页面置换算法
抖动:刚被换出的页很快又要被访问,需要重新调入。而选择调出的页又很快被访问,又需要将其重新调入… 如此反复使得进程在运行时把大部分时间都花费在页面置换上。
5.3.1 最佳置换算法
由 Belady 于 1966 年提出。其所选择的被淘汰页面将是以后永不使用的,或许是在最长(未来)时间内不再被访问的页面。可保证获得最低的缺页率,但由于人们目前还无法预知被淘汰的页面因而该算法是无法实现的。

- 具有最好的性能
- 无法实现,通常使用其作为标准评价其他置换算法
5.3.2 先进先出置换算法(FIFO)
该算法最早出现。其总是淘汰最先进入内存的页面。

- 实现简单
- 性能可能是最差的,几乎不使用
5.3.3 最近最久未使用置换算法(LRU)
根据页面调入内存后的使用情况做出决策。利用“最近的过去”作为“最近的将来”的近似,选择最近最久未使用的页面予以淘汰。
该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t。当需淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最久未使用的页面予以淘汰。
需要硬件支持。

(1)寄存器
为每个页面配置一个移位寄存器:
R = R n − 1 R n − 2 R n − 3 … R 2 R 1 R 0 R=R_{n-1}R_{n-2}R_{n-3}\dots R_{2}R_{1}R_{0} R=Rn−1Rn−2Rn−3…R2R1R0
当进程访问物理块时,将相应寄存器的 R n − 1 R_{n-1} Rn−1 置位成 1。
定时信号将每隔一定时间(如 100 ms)将寄存器右移一位。
通过比较 R R R 的大小来判断最久未使用的页面。
(2)栈
利用一个特殊的栈保存当前使用的各个页面的页面号。每当进程访问某页面时,便将该页面的页面号从栈中移出,将它压入栈顶。因此,栈顶始终是最新被访问页面的编号,而栈底则是最近最久未使用页面的页面号。

- 性能较好
- 需要较多的硬件支持
5.3.4 最少使用置换算法(LFU)
实现同 LFU,但是将 ∑ R i \sum{R_{i}} ∑Ri 作为选出页面的标准。
这种方法不能真正反映页面的使用情况,因为不可能每次都记录下页面的访问。
5.3.5 Clock 置换算法
LRU 需要较多的硬件支持,成本较高。Clock 算法就是用的较为广泛的 LRU 近似算法。
(1)简单的 Clock 置换算法
为每一页设置一位访问位 A,将内存中所有的页面链接成一个循环队列,按照 FIFO 算法进行检查:
- 如果 A 是 0,则将其换出
- 如果 A 是 1,将其置 0,给予一次驻留内存的机会

(2)改进型 Clock 置换算法
改进算法考虑到了写回磁盘的情况:若页面未被修改过,则不需要写回磁盘。
因此增加一个修改位 M,作为置换代价,添加到我们的考虑因素之内:
- 1 类(A = 0,M = 0):该页最近既未被访问,又未被修改,是最佳淘汰页。
- 2 类(A = 0,M = 1):该页最近未被访问,但已被修改,并不是很好的淘汰页。
- 3 类(A = 1,M = 0):最近已被访问,但未被修改,该页有可能再被访问。
- 4 类(A = 1,M = 1):最近已被访问且被修改,该页可能再被访问。
因此检查步骤为:
- 第一趟扫描,寻找 1 类页面。若找到直接作为淘汰页,检查结束;若未找到,则进入第二趟扫描。
- 第二趟扫描,寻找 2 类页面。将遇到的第一个页面作为淘汰页,并将第二趟扫描所遇到的所有页面的访问位 A 都置为 0。若未找到,则进入第三趟扫描。
- 第三趟扫描,寻找 3 类页面。将遇到的第一个页面作为淘汰页。若未找到,则进入第四趟扫描。
- 第四趟扫描,寻找 4 类页面。一定能找到。
- 可以减少磁盘的 I/O 操作次数
- 可能经过数轮扫描,算法本身的开销有所增加
5.3.6 页面缓冲算法(PBA)
由于页面的换进换出所付出的开销十分大,因此我们可以考虑从该方面对算法进行改进。
(1)影响页面换进换出效率的因素
- 页面置换算法的选择
- 写回磁盘的频率
- 读入内存的频率
(2)页面缓冲算法 PBA
采用 FIFO 算法,内存分配策略采用可变分配和局部置换方式。在内存中设置两个链表:
-
空闲页面链表
即系统的空闲物理块链表,进程读入页面时用该链表进行装入。当有一个未被修改的页面要换出时,将其挂在链表末尾,而不直接将其换出到外存。以后的进程需要这些页面的数据时,便可直接从链表上将其去下,免除了磁盘读入数据的操作。
-
修改页面链表
由已修改的页面组成。当进程需要将一个已修改的页面换出时,不立即换出到外存,而是将其挂在该链表末尾,最后一并换出。
- 显著降低了页面换进换出的频率,减少其开销
- 不需要硬件支持,实现简单
5.3.7 访问内存的有效时间
在请求分页管理方式中,内存有效访问时间不仅要考虑访问页表和访问实际物理地址数据的时间,还必须要考虑到缺页中断的处理时间。
这样,在具有快表机制的请求分页管理方式中,存在下面三种方式的内存访问操作。我们约定符号:
- λ \lambda λ:查找快表的时间
- t t t:访问实际物理地址所需要的时间
- a a a:命中率
- f f f:缺页率
- ε \varepsilon ε:缺页中断处理时间
(1)被访问页在内存中,且其对应的页表项在快表中
E A T = λ + t EAT=\lambda+t EAT=λ+t
(2)被访问页在内存中,且其对应的页表项不在快表中
E A T = 2 × ( λ + t ) EAT=2\times(\lambda+t) EAT=2×(λ+t)
(3)被访问页不在内存中
- 不考虑命中率和缺页率:
E A T = ε + 2 × ( λ + t ) EAT=\varepsilon+2\times(\lambda+t) EAT=ε+2×(λ+t)
- 考虑命中率和缺页率:
E A T = λ + a × t + ( 1 − a ) × [ t + f × ( ε + λ + t ) + ( 1 − f ) × ( λ + t ) ] EAT=\lambda+a\times t+(1-a)\times[t+f\times(\varepsilon+\lambda+t)+(1-f)\times(\lambda+t)] EAT=λ+a×t+(1−a)×[t+f×(ε+λ+t)+(1−f)×(λ+t)]
- 仅考虑缺页率,不考虑命中率
E A T = t + f × ( ε + t ) + ( 1 − f ) × t EAT=t+f\times(\varepsilon+t)+(1-f)\times t EAT=t+f×(ε+t)+(1−f)×t
5.4 “抖动”与工作集
5.4.1 多道程序度与“抖动“
(1)多道程序度与处理机的利用率
由于虚拟存储器系统能从逻辑上扩大内存,这时,只需装入一个进程的部分程序和数据便可开始运行,故人们希望在系统中能运行更多的进程,即增加多道程序度,以提高处理机的利用率。

(2)产生“抖动”的原因
发生“抖动”的根本原因是:同时在系统中运行的进程太多,由此分配给每一个进程的物理块太少,不能满足进程正常运行的基本要求,致使每个进程在运行时,频繁地出现缺页,必须请求系统将所缺之页调入内存。这会使得在系统中排队等待页面调进/调出的进程数目增加。显然,对磁盘的有效访问时间也随之急剧增加,造成每个进程的大部分时间都用于页面的换进 / 换出,而几乎不能再去做任何有效的工作,从而导致发生处理机的利用率急剧下降并趋于 0 的情况。我们称此时的进程是处于“抖动”状态。
5.4.2 工作集
(1)工作集的基本概念
进程发生缺页率的时间间隔与进程所获得的物理块数有关。该理论在 1968 年由 Denning 提出并推广。Denning 认为,基于程序运行时的局部性原理得知,程序在运行期间,对页面的访问是不均匀的,在一段时间内仅局限于较少的页面,在另一段时间内,又可能仅局限于对另一些较少的页面进行访问。这些页面被称为活跃页面。如果能够预知程序在某段时间间隔内要访问哪些页面,并将它们调入内存,将会大大降低缺页率,从而可显著地提高处理机的利用率。

(2)工作集的定义
所谓工作集,是指在某段时间间隔 Δ \Delta Δ 里,进程实际所要访问页面的集合。
Denning 指出,虽然程序只需要少量的几页在内存便可运行,但为了较少地产生缺页,应将程序的全部工作集装入内存中。然而我们无法事先预知程序在不同时刻将访问哪些页面,故仍只有像置换算法那样,用程序的过去某段时间内的行为作为程序在将来某段时间内行为的近似。
把某进程在时间 t 的工作集记为 w(t, Δ \Delta Δ),其中的变量 Δ \Delta Δ 称为工作集的“窗口尺寸”。

w(t, Δ \Delta Δ) 为二元函数,是窗口尺寸 Δ \Delta Δ 的非降函数:
w ( t , Δ ) ⊆ w ( t , Δ + 1 ) w(t, \Delta)\subseteq w(t, \Delta+1) w(t,Δ)⊆w(t,Δ+1)
5.4.3 “抖动”的预防方法
-
采取局部置换策略
-
把工作集算法融入到处理机调度中
-
利用“L = S”准则调节缺页率
L:产生缺页的平均时间
S:系统处理缺页的平均时间
当 L = S 时,处理机和磁盘的利用率最高。
-
选择暂停的进程调出到磁盘上
5.5 请求分段存储管理方式
5.5.1 请求分段中的硬件支持
(1)请求段表机制
表中除了具有请求分页机制中具有的四个字段之外,还增加了存取方式字段和增补位:
段名 段长 段基址 存取方式 访问字段 A 修改位 M 存在位 P 增补位 外存始址 \begin{array}{| c | c | c | c | c | c | c | c | c |} \hline \text{段名} & \text{段长} & \text{段基址} & \text{存取方式} & \text{访问字段 A} & \text{修改位 M} & \text{存在位 P} & \text{增补位} & \text{外存始址}\\ \hline \end{array} 段名段长段基址存取方式访问字段 A修改位 M存在位 P增补位外存始址
-
存取方式
用于对段进行保护。
若该字段为 2 位,则存取属性是:只执行、只读、允许读 / 写
-
访问字段 A
用于记录本页在一段时间内被访问的次数,或者记录本页最近已有多长时间未被访问。
供置换算法在选择换出页面时参考。
-
修改位 M
用于标识该页在调入内存后是否被修改过。置换该页时,若未被修改,就不必将该页再写回外存上了。
供置换页面时参考。
-
存在位 P
用于指示该页是否已调入内存。仅有一位,也称为“位字”。
供程序访问时参考。
-
增补位
用于表示本段在运行过程中是否做过动态增长,是请求分段式管理中的特有字段。
-
外存地址
用于指出该页在外存上的地址,通常是物理块号。
供调入该页时参考。
(2)缺段中断机构

(3)地址变换机构

5.5.2 分段的共享与保护
(1)共享段表
共享段表的表项包括以下几部分:
- 共享进程计数 count
- 存取控制字段
- 段号

(2)分段保护
常用的目前包括以下三种措施:
-
越界检查
利用地址变换机构来完成。
-
存取控制检查
访问存取控制字段进行权限检查
-
环保护机构
一种功能较完善的保护机制。
在该机制中规定:低编号的环具有高优先权。
OS 核心处于 0 号环内;某些重要的实用程序和操作系统服务占居中间环;而一般的应用程序,则被安排在外环上。
在环系统中,程序的访问和调用应遵循以下规则:
- 一个程序可以访问驻留在相同环或较低特权环(外环)中的数据;
- 一个程序可以调用驻留在相同环或较高特权环(内环)中的服务。
