在进程运行过程中,若其所要访问的页面不在内存,而需把它们调入内存,但内部无空闲空间时,为了保证该进程能正常运行,系统必须从内存中调出一页程序或数据送到磁盘的对换区中。但应将哪个页面调出,须根据一定的算法来确定。通常,把选择换出页面的算法称为页面置换算法( Page - Replacement Algorithms )。置换算法的好坏将直接影响到系统的性能。
不适当的算法可能会导致进程发生“抖动”( Thrashing ),即刚被换出的页很快又要被访问,需要将它重新调入,此时又需要再选一页调出;而此刚被调出的页很快又被访问,又需将它调入,如此频繁地更换页面,以致一个进程在运行中把大部分时间都花费在页面置换工作上,我们称该进程发生了“抖动”。
一个好的页面置换算法应具有较低的页面更换频率。从理论上讲,应将那些以后不再会访问的页面换出,或把那些在较长时间内不会再访问的页面调出。目前已有多种置换算法,它们都试图更接近于理论上的目标。下面介绍几种常用的置换算法。
最佳置换算法和先进先出置换算法
目前有许多页面置换算法,相比而言,下面将介绍的是两种比较极端的算法。最佳置换算法是一种理想化的算法,它具有最好的性能,但实际上是无法实现的。通常使用最佳置换算法作为标准,来评价其它算法的优劣。先进先出置换算法是最直观的算法,由于与通常页面的使用规律不符,可能是性能最差的算法,故实际应用极少。
1.最佳( Optimal )置换算法
最佳置换算法是由 Belady 于1966年提出的一种理论上的算法。其所选择的被淘汰页面将是以后永不使用的,或许是在最长(未来)时间内不再被访问的页面。采用最佳置换算法通常可保证获得最低的缺页率。但由于人们目前还无法预知,一个进程在内存的若干个页面中,哪一个页面是未来最长时间内不再被访问的,因而该算法是无法实现的,但可以利用该算法去评价其它算法。现举例说明如下。
假定系统为某进程分配了三个物理块,并考虑有以下的页面号引用串:
7
,
0
,
1
,
2
,
0
,
3
,
0
,
4
,
2
,
3
,
0
,
3
,
2
,
1
,
2
,
0
,
1
,
7
,
0
,
1
7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1
7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1
进程运行时,先将7,0,1三个页面装入内存。以后,当进程要访问页面2时,将会产生缺页中断。此时 OS 根据最佳置换算法将选择页面7予以淘汰。这是因为页面0将作为第5个被访问的页面,页面1是第14个被访问的页面,而页面7则要在第18次页面访问时才需调入。下次访问页面0时,因它已在内存而不必产生缺页中断。当进程访问页面3时,又将引起页面1被淘汰;因为,它在现有的1,2,0三个页面中,将是以后最晚才被访问的。图5-3示出了采用最佳置换算法时的置换图。由图可看出,采用最佳置换算法发生了6次页面置换。
2.先进先出(FIFO)页面置换算法
FIFO 算法是最早出现的置换算法。该算法总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面予以淘汰。该算法实现简单,只需把一个进程已调入内存的页面按先后次序链接成一个队列,并设置一个指针,称为替换指针,使它总是指同最老的页面。但该算法与进程实际运行的规律不相适应,因为在进程中,有些页面经常被访问,比如,含有全局变量、常用函数、例程等的页面, FIFO 算法并不能保证这些页面不被淘汰。
这里,我们仍用上面的例子,但采用 FIFO 算法进行页面置换(图5-4)。当进程第一次访问页面2时,将把第7页换出,因为它是最先被调入内存的;在第一次访问页面3时,又将把第0页换出,因为它在现有的2、0、1三个页面中是最老的页。由图5-4可以看出,利用FIFO算法时,进行了12次页面置换,比最佳置换算法正好多一倍。
最近最久未使用和最少使用置换算法
1.LRU( Least Recently Used )置换算法的描述
FIFO 置换算法的性能之所以较差,是因为它所依据的条件是各个页面调入内存的时间,而页面调入的先后并不能反映页面的使用情况。最近最久未使用( LRU )的页面置换算法是根据页面调入内存后的使用情况做出决策的。由于无法预测各页面将来的使用情况,只能利用“最近的过去”作为“最近的将来”的近似,因此, LRU 置换算法是选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t 。当需淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最久未使用的页面予以淘汰。
利用 LRU 算法对上例进行页面置换的结果如图5-5所示。当进程第一次对页面2进行访问时,由于页面7是最近最久未被访问的,故将它置换出去。当进程第一次对页面3进行访问时,第1页成为最近最久未使用的页,将它换出。由图可以看出,前5个时间的图像与最佳置换算法时的相同,但这并非是必然的结果。因为最佳置换算法是从“向后看”的观点出发的,即它是依据以后各页的使用情况进行判断;而 LRU 算法则是“问前看”的,即根据各页以前的使用情况来判断,而页面过去和未来的走向之间并无必然的联系。
2. LRU 置换算法的硬件支持
LRU 置换算法虽然是一种比较好的算法,但要求系统有较多的支持硬件。为了了解一个进程在内存中的各个页面各有多少时间未被进程访问,以及如何快速地知道哪一页是最近最久未使用的页面,须有寄存器和栈两类硬件之一的支持。
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~…R~2~R~1~R~0~
R=R n−1 R n−2 R n−3 …R 2 R 1 R 0
当进程访问某物理块时,要将相应寄存器的 Rn-1位置成1。此时,定时信号将每隔一定时间(例如100 ms )将寄存器右移一位。如果我们把 n 位寄存器的数看作是一个整数,那么,具有最小数值的寄存器所对应的页面,就是最近最久未使用的页面。图5-6示出了某进程在内存中具有8个页面,为每个内存页面配置一个8位寄存器时的 LRU 访问情况。这里,把8个内存页面的序号分别定为1~8。由图可以看出,第3个内存页面的 R 值最小,当发生缺页时,应首先将它置换出去。
2)栈
可利用一个特殊的栈保存当前使用的各个页面的页面号。每当进程访问某页面时,便将该页面的页面号从栈中移出,将它压入栈顶。因此,栈顶始终是最新被访问页面的编号,而栈底则是最近最久未使用页面的页面号。假定现有一进程,它分有五个物理块,所访问的页面的页面号序列为:
4
,
7
,
0
,
7
,
1
,
0
,
1
,
2
,
1
,
2
,
6
4,7,0,7,1,0,1,2,1,2,6
4,7,0,7,1,0,1,2,1,2,6
在前三次访问时,系统将依次将4、7、0放入栈中,4是栈底,0是栈项;第四次是访问第7页,使7成为栈顶。在第八次访问页面2时,该进程的五个物理块都已装满,在第九和十次访问时,未发生缺页。在第11次访问页面6时发生了缺页,此时页面4是最近最久未被访问的页,应将它置换出去。随着进程的访问,栈中页面号的变化情况如图5-7所示。
3.最少使用( Least Frequently Used , LFU )置换算法
在采用 LFU 算法时,应为在内存中的每个页面设置一个移位寄存器,用来记录该页面被访问的频率。该置换算法选择在最近时期使用最少的页面作为淘汰页。由于存储器具有较高的访问速度,例如100 ns ,在1 ms 时间内可能对某页面连续访问成干上万次,因此,直接利用计数器来记录某页被访问的次数是不现实的,只能采用较大的时间间隔来记录对存储器某页的访问。在最少使用置换算法中采用了移位寄存器方式。每次访问某页时,便将该移位寄存器的最高位置1,再每隔一定时间(例如100 ms )右移一次。这样,在最近一段时间使用最少的页面将是 ∑ R i \sum R~i~ ∑R i 最小的页。 LFU 置换算法的页面访问图,与 LRU 置换算法的访问图完全相同;或者说,利用这样一套硬件既可实现 LRU 算法,又可实现 LFU 算法。应该指出,这种算法并不能真正反映出页面的使用情况,因为在每一时间间隔内,只是用寄存器的一位来记录页的使用情况,因此,在该时间间隔内,对某页访问一次和访问1000次是完全等效的。
Clock 置换算法
虽然 LRU 是一种较好的算法,但由于它要求有较多的硬件支持,使得其实现所需的成本较高,故在实际应用中,大多采用 LRU 的近似算法。 Clock 算法就是用得较多的一种 LRU 近似算法。
1.简单的 Clock 置换算法
当利用简单 Clock 算法时,只需为每页设置一位访问位,再将内存中的所有页面都通过链接指针链接成一个循环队列。当某页被访问时,其访问位被置1。置换算法在选择一页淘汰时,只需检查页的访问位。如果是0,就选择该页换出;若为1,则重新将它置0,暂不换出,给予该页第二次驻留内存的机会,再按照 FIFO 算法检査下一个页面。当检查到队列中的最后一个页面时,若其访问位仍为1,则再返回到队首去检査第一个页面。图5-8示出了该算法的流程和示例。由于该算法是循环地检查各页面的使用情况,故称为 Clock 算法。但因该算法只有一位访问位,只能用它表示该页是否已经使用过,而置换时是将未使用过的页面换出去,故又把该算法称为最近未用算法或 NRU(Not Recently Used )算法。
2.改进型 Clock 置换算法
在将一个页面换出时,如果该页己被修改过,便须将该页重新写回到磁盘上;但如果该页未被修改过,则不必将它拷回磁盘。换而言之,对于修改过的页面,在换出时所付出开销比未修改过的页面大,或着说,置换代价大。在改进型 Clock 算法中,除须考虑页面的使用情况外,还须再增加一个因素一一置换代价。这样,选择页面换出时,既要是未使用过的页面,又要是未被修改过的页面。把同时满足这两个条件的页面作为首选淘汰的页面。由访问位 A 和修改位 M 可以组合成下面四种类型的页面:
- 1类( A =0, M =0):表示该页最近既未被访问,又未被修改,是最佳淘汰页。
- 2类( A =0, M =1):表示该页最近未被访问,但己被修改,并不是很好的淘汰页。
- 3类( A =1, M =0):表示最近已被访问,但未被修改,该页有可能再被访问。
- 4类( A = l , M =1):表示最近已被访问且被修改,该页可能再被访问。
在内存中的每个页,都必定是这四类页面之一。在进行页面置换时,可采用与简单 Clock 算法相类似的算法,其差别在于该算法须同时检査访问位与修改位,以确定该页是四类页面中的哪一种。其执行过程可分成以下三步: .
- (1)从指针所指示的当前位置开始,扫描循环队列,寻找 A =0且 M =0的第一类贝面,将所遇到的第一个页面作为所选中的淘汰页。在第一次扫描期间不改变访问位 A 。
- (2)如果第一步失败,即査找一轮后未遇到第一类页面,则开始第二轮扫描,寻找 A =0且 M =1的第二类页面,将所遇到的第一个这类页面作为淘汰页。在第二轮扫描期间,将所有扫描过的页面的访问位都置0。
- (3)如果第二步也失败,亦即未找到第二类页面,则将指针返回到开始的位置,并将所有的访问位复0。然后重复第一步,即寻找 A =0且 M =0的第一类页面,如果仍失败,必要时再重复第二步,寻找 A =0且 M =1的第二类页面,此时就一定能找到被淘汰的页。
该算法与简单 Clock 算法比较,可减少磁盘的 IO 操作次数。但为了找到一个可置换的页,可能须经过几轮扫描。换言之,实现该算法本身的开销将有所增加。
页面缓冲算法( Page Bufering Algorithm , PBA )
在请求分页系统中,由于进程在运行时经常会发生页面换进换出的情况,所以一个十分明显的事实就是,页面换进换出所付出的开销将对系统性能产生重大的影响。在此,我们首先对影响页面换进换出效率的若干因素进行分析。
1.影响页面换进换出效率的若干因素
影响页面换进换出效率的因素有许多,其中包括有:
- 对页面进行置换的算法
- 将已修改页面写回磁盘的频率
- 将磁盘内容读入内存的频率。
(1)页面置换算法
影响页面换进换出效率最重要的因素,无疑是页面置换算法。因为一个好的页面置换算法,可使进程在运行过程中具有较低的缺页率,从而可以减少页面换进换出的开销。正因如此,才会有许多学者去研究页面置换算法,相应地也就出现了大量的页面置换算法,其中主要的算法前面已对它做了介绍。
(2)写回磁盘的颊率
对于已经被修改过的页面,在将其换出时,应当写回磁盘。如果是采取每当有一个页面要被换出时就将它写回磁盘的策略:这意味着每换出便需要启动一次磁盘。但如果在系统中已建立了一个已修改换出页面的链表,则对每一个要被换出的页面(已修改),系统可暂不把它们写回磁盘,而是将它们挂在已修改换出页面的链表上,仅当被换出页面数目达到一定值时,例如64个页面,再将它们一起写回磁盘上,这样就显著地减少了磁盘 I/O 的操作次数。或者说,减少已修改页面换出的开销。
(3)读入内存的频率
在设置了已修改换出页面链表后,在该链表上就暂时有一批装有数据的页面,如果有进程在这批数据还未写回磁盘时需要再次访问这些页面时,就不从外存上调入,而直接从已修改换出页面链表中获取,这样也可以减少将页面从磁盘读入内存的频率,减少页面换进的开销。或者说,只需花费很小的开销便可使这些页面又回到该进程的驻留集中。
2.页面缓冲算法 PBA
PBA 算法的主要特点是:
- ①显著地降低了页面换进、换出的频率,使磁盘 I / O 的操作次数大为减少,因而减少了页面换进、换出的开销;
- ②正是由于换入换出的开销大幅度减小,才能使其采用一种较简单的置换策略,如先进先出( FFO )算法,它不需要特殊硬件的支持,实现起来非常简单。
页面缓冲算法已在不少系统中采用,下面我们介绍 VAX/VMS 操作系统中所使用的页面缓冲算法。在该系统中,内存分配策略上采用了可变分配和局部置换方式,系统为每个进程分配一定数目的物理块,系统自己保留一部分空闲物理块。为了能显著地降低页面换进、换出的频率,在内存中设置了如下两个链表:
1)空闲页面链表
实际上该链表是一个空闲物理块链表,是系统掌握的空闲物理块,用于分配给频繁发生缺页的进程,以降低该进程的缺页率。当这样的进程需要读入一个页面时,便可利用空闲物理块链表中的第一个物理块来装入该页。当有一个未被修改的页要换出时,实际上并不将它换出到外存,而是把它们所在的物理块挂在空闲链表的末尾。应当注意,这些挂在空闲链表上的未被修改的页面中是有数据的,如果以后某进程需要这些页面中的数据时,便可从空闲链表上将它们取下,免除了从磁盘读入数据的操作,减少了页面换进的开销。
2)修改页面链表
它是由已修改的页面所形成的链表。设置该链表的目的是为了减少已修改页面换出的次数。当进程需要将一个已修改的页面换出时,系统并不立即把它换出到外存上,而是将它所在的物理块挂在修改页面链表的末尾。这样做的目的是:降低将已修该页面写回磁盘的频率,降低将磁盘内容读入内存的频率。
访问内存的有效时间
与基本分页存储管理方式不同,在请求分页管理方式中,内存有效访问时间不仅要考虑访问页表和访问实际物理地址数据的时间,还必须要考虑到缺页中断的处理时间。这样在具有快表机制的请求分页管理方式中,存在下面三种方式的内存访问操作,其有效访问时间的计算公式也有所不同:
(1)被访问页在内存中,且其对应的页表项在快表中
显然,此时不存在缺页中断情况,内存的有效访问时间( EAT )分为查找快表的时间(λ)和访问实际物理地址所需的时间( t ):
E
A
T
=
λ
+
t
EAT=\lambda+t
EAT=λ+t
(2)被访问页在内存中,且其对应的页表项不在快表中
显然,此时也不存在缺页中断情况,但需要两次访问内存,一次读取页表,一次读取数据,另外还需要更新快表。所以,这种情况内存的有效访问时间可分为查找快表的时间、查找页表的时间、修改快表的时间和访问实际物理地址的时间:
E
A
T
=
λ
+
t
+
λ
+
t
=
2
×
(
λ
+
t
)
EAT =\lambda+ t +\lambda+ t =2×(\lambda+ t)
EAT=λ+t+λ+t=2×(λ+t)
(3)被访问页不在内存中
因为被访问页不在内存中,需要进行缺页中断处理,所以这种情况的内存的有效访问时间可分为查找快表的时间、查找页表的时间、处理缺页中断的时间、更新快表的时间和访问实际物理地址的时间:
假设缺页中断处理时间为 ε ,则
E
A
T
=
λ
+
t
+
ε
+
λ
+
t
=
ε
+
(
λ
+
t
)
EAT =\lambda+t+\varepsilon+\lambda+ t =\varepsilon+(\lambda+ t)
EAT=λ+t+ε+λ+t=ε+(λ+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)]
式中, a 表示命中率, f 表示缺页率。
如果不考虑命中率,仅考虑缺页率,即上式中的λ=0和 a =0,设缺页中断处理时间为Φ,由此可得
E
A
T
=
t
+
f
×
(
Φ
+
t
)
+
(
1
−
f
)
×
t
EAT = t + f\times(\Phi+t)+(1- f)\times t
EAT=t+f×(Φ+t)+(1−f)×t