13. 页面更换算法

13.1 页面更换无可避免

在交换系统下,一个程序作为一个整体加载到内存。因此,在运行时,无须再从磁盘上加载任何东西。而在分页系统下,一个程序的所有页面并不一定都在内存中,因此,在执行的过程中就有可能发生页面不在内存的情况。

如果访问的页面不在内存中,则系统将产生缺页中断。缺页中断服务程序将负责把位于磁盘上的数据加载到物理内存来。如果物理内存还有空闲页面,那就直接使用空闲的页面。但如果物理内存已满,则需要挑选某个已经使用的页面进行替换。

那么挑选哪个页面合适呢?

13.2 页面更换的目标

挑选需要被更换的页面自然是由讲究的

如果挑选的页面是之后很快又要被访问的页面,那么系统将很快再次产生缺页中断。因为磁盘访问速度远远慢于内存访问速度,缺页中断的代价是非常大的。

就是说,我们需要精心设计一种算法来进行页面的更换。那么要设计这种算法,首先得知道我们的目的是什么。

页面更换时挑选替换页面的目标是什么呢?

降低随后发生缺页中断的次数或者概率。

因此,我们选择的页面应当是在随后相当长时间内不会被访问的页面。最好是再也不会被访问的页面同时,如果可能,我们应该选择一个没有修改过的页面。这样,替换时就无须将被替换页面的内容写回磁盘,从而进一步加快缺页中断的响应速度。

自然,一连串的页面替换算法喷涌而出:

从哲学的层次上来看,所有的算法都归结为两个大类的一类:

  • 公平算法,对所有的页面一视同仁
  • 非公平算法,具有优先度,每个页面的独特性被当做否替换的考虑因素,因此,需要保存每个页面的一些个体信息,以便算法决策。

公平算法主要包括下面4种:
●随机算法
●先来先出(FIFO)算法
●第二次机会算法
●时钟算法
非公平算法则包括如下5种:
●最优算法
●NRU算法
●LRU算法
●工作集算法
还有一种混合算法,它既想保持公平,又含有区别对待的考虑:工作集时钟算法。

(1)随机更换算法

最简单的算法当然就是随机算法,或者说没有算法。在需要替换页面的时候,产生一个随机页面号,而替换与该页面号对应的物理页面。

显然,这种算法计算需替换页面号时速度很快。但是效果如何呢?

我们可能都有种感觉,有时候花费太多精力设计算法,有时效果还不如随机算法

那么结果如何呢?

遗憾的是,结果不太令人满意。事实上,随机选页所选出的被替换的页面不太可能是随后相当长时间内不会被访问的页面。也就是说,这种算法难以保证最小化随后的缺页中断次数。事实上,这种算法的效果相当差。

(2)先进先出算法

该算法的核心是更换最早进入内存的页面。

FIFO的实现机制是使用链表将所有在内存的页面按照进入时间的早晚链接起来,然后每次置换链表头上的页面就行了。新加进来的页面则挂在链表的末端。如图13-2所示。在这里插入图片描述
FIFO的优点是简单且容易实现。但这个绝对的公平方式容易降低效率。例如,如果最先加载进来的页面是经常被访问的页面,那么这样做很可能造成常被访问的页面替换到磁盘上,导致很快就需要再次发生缺页中断,从而降低效率。

(3)第二次机会算法

由于FIFO只考虑进入内存的时间,不关心一个页面被访问的频率,从而有可能造成替换掉一个被经常访问的页面而造成效率低下。那么我们对FIFO改进的方向就是考虑一个页面是否是经常被访问的因素。

改进的手段就是在使用FIFO更换一个页面时,需要看一下该页面是否在最近被访问过。如果没有被访问过,则替换该页面。如果该页面在最近被访问过(通过检查其访问位的取值),则不替换该页面,而是将该页面挂到链表末端,并将该页面进入内存的时间设置为当前时间,并将其访问位清零。这样,对于最近被访

这样,对于最近被访问过的页面来说,相当于给了它第二次机会。就相当于人犯了错误我们并不一棍子打死,而是给其改过自新的机会。如果图13-2中的A页面最近被访问过,即其访问位R的值为1,则使用第二次机会算法后,链表的格局如图13-3所示。

第二次机会:给你辩驳的机会,你说自己很受欢迎,不能被代替,OK。先留着你

在这里插入图片描述
这样,我们考虑替换的页面将变为B。如果B页面的访问位为0,则替换B页面。否则,将B页面移到末端,再考虑C页面。这样,依此类推,直到找到一个其访问位为0的页面为止。

(4)时钟算法

第二次机会算法既简单、公平,又容易实现。只不过,

  • 每次给予一个页面第二次机会时,将其移到链表末端需要耗费时间。
  • 另外,页面的访问位只在页面替换进行扫描时才可能清零,所以其时间局域性体现得不好,访问位为1的页面可能是很久以前访问的,时间上的分辨粒度太粗,从而影响页面替换的效果。

为了改善这些缺点,人们想出了时钟算法。在时钟算法里,我们把页面排成一个时钟的形状。该时钟有一个针臂。每次需要更换页面时,我们从针臂所指的页面开始检查。如果当前页面的访问位为0,即从上次检查到这次,该页面没有被访问过,将该页面替换。如果当前页面被访问过,那就将其访问位清零,并顺时针移动指针到下一个页面。我们重复这些步骤直到找到一个访问位为0的页面。图13-4描述的就是时钟算法。

在图13-4里,指针指向的是页面F。这样第一个被考虑替换的是页面F。如果页面F的访问位为0,F将被替换。**如果页面F的访问位为1,则F的访问位清零,指针移到页面G。**

可能有读者会问,这个算法和第二次机会算法不是一样的吗?都是访问位为0就更换,访问位为1就再给一次机会。但二者真的一样吗?当然不是。在这里插入图片描述

时钟仅仅优化了第二次机会的数据结构,消除掉不必要的链表变换。但还有问题没有解决,比如考虑不同页面调用频率的不同,有的页面可能很久之前被调用过。

最优更换算法

前面介绍的4种算法均属于“公平算法”。即所有的页面都或多或少地给予公平待遇,没有页面获得特殊待遇。但是这种公平实现方式,会使效率受到一定影响。这是因为个体对整个系统的贡献没有被区别对待,造成贡献大的和贡献小的待遇一样,自然就会影响整个系统的效率。

毫无疑问,一个程序里面的不同页面对程序的影响是不同的。有的页面被频繁访问,而有的页面则访问次数较少。如果我们在选择替换页面时将其同等考虑,自然很难选出一个在今后最长时间都不会被访问的页面。因此,我们需要对页面的个体特性进行考虑。

对页面的个体特性进行考虑就是要对不同的页面区别对待。依据其在程序中的作用不同而在选择替换页面时给予不同的待遇。

这样就形成了下面要介绍的4种非公平算法:

在非公平的算法里面,最理想的页面替换算法是选择一个再也不会被访问的页面进行替换。

如果不存在这样的页面,那至少选择一个在随后最长时间内不会被访问的页面进行替换。这样,我们就可以保证在随后发生缺页中断的次数最小或者概率最低,这种替换算法就是最优的替换算法


那我们怎么知道一个页面随后多长时间不会被访问呢?当然不知道。既然如此,最优更换算法就是一种无法实际实现的算法。那么我们为什么要介绍最优算法呢?

那是为了定义一个标杆,以此来评判其他算法的优劣。例如,张三和李四各设计了一个页面替换算法,如何判断它们谁优谁劣呢?与标杆比较即可。谁的算法与最优算法更接近,谁的算法就好;反之就差。

1. NRU算法

前面讲过,最优更换算法无法在实际中实现。随机算法效果很差;FIFO算法和第二次机会算法从根本上是一种公平的算法,即页面都有相同的概率被替换。显然,并不是所有页面对程序的贡献都是一样的,因此,这种公平的替换算法不太可能反映出各个页面在程序中的不同地位,其效果也不可能是最佳的。

那我们就在第二次机会算法上进行改进。第二次机会算法选择的是最先进入内存且访问位为0的页面进行替换。改进的办法就是选择一个最近没有被访问的页面来替换,但在所有的最近没有使用的页面里,不是按照最先进入来划分,而是按照各个页面的修改位和访问位的组合来进行划分。这种算法称为最近未使用算法(Not Recently Used, NRU)。

顾名思义NRU就是选择一个在最近一段时间没有被访问过的页面进行替换,做出这种选择是基于程序访问的时空局域性

因为根据时空局域性原理,一个最近没有被访问的页面,在随后的时间里也不太可能被访问,而NRU的实现方式就是利用页面的访问和修改位。

我们知道每个页面都有一个访问位和一个修改位。凡是对页面进行读写操作时,访问位设置为1。当进程对页面进行写操作时,修改位设置为1。根据这两个位的状态来对页面进行分类的话,可以分成4种页面类型:1、2、3、4,如表13-1所示。在这里插入图片描述
也许有读者会问,第2种状态怎么可能呢?一个页面被修改却没有被访问。难道被修改不是被访问吗?答案是第2种状态是完全可能的。当然,修改需要访问。那什么情况下会出现页面没有被访问但是又被修改的状态呢?

只有一种可能性,就是系统对访问位会定期清零,对修改位却不能清零。访问位定期清零是保证这个访问位不失去意义的手段。如果不定期清零,则一段时间后所有的页面都是被访问的。这样,这个访问位就没有任何意义了。

与此同时,修改位却不能清零,因为操作系统在页面换出时要用它判断页面是否需要写回磁盘,清零后就会出错。这样,一个被访问和修改的页面在访问位定期清零后就会变成没有被访问但被修改的状态。

可以看出,我们在考虑一个页面的状态时加入了时间概念,在最近的某段时间内访问值对我们来说很重要,把时间设置为无限大是没有意义的。

有了这个分类后,NRU算法就是按照这4类页面的顺序寻找可以替换的页面。

优先级: 1 > 2 > 3 > 4 优先考虑更换第 1 种页面。
在这里插入图片描述

问题: 这种分类比较笼统,在同一类页面里,我们没有办法分辨出哪一类被访问时间更最近一些。即在某些情况下,我们替换的可能并不是最近没有使用的页面。换言之,在最差情况下,时间的分辨粒度粗到一个访问位清零的周期。

2. LRU算法

对NRU算法的改进就是LRU算法。LRU算法是最近使用最少算法(Least Recently Used)。我们不仅考虑最近是否用过,还要考虑最近使用的频率。

此处我们使用的哲学理念仍是拿过去的数据来预测将来:如果一个页面被访问的频率低,那么以后很可能也用不到。

由LRU算法的定义可以看出,LRU算法的实现必须以某种方式记录每个页面被访问的次数,而这是个相当大的工作量。最简单的办法就是在页表的记录项里增加一个计数域,一个页面被访问一次,这个计数器的值就增加1。这样,当需要更换页面时,只需找到计数域取值最小的页面替换即可,该页面即是最近最少使用的页面。

问题

  • (1)它不一定保证是最近最少使用的页面被选中,最少被使用可以肯定,但“最近”则不一定。因为计数器记录的时间段不一定是最近。
    这要看我们的最近定义的是多长时间。
  • (2)另外,由于物理计数域的长度总是有限的,值到最大后将发生溢出,而回到0。这样值最小的不一定是最少使用的,反而可能是最多使用的。

不过,我们何必一定要精确地实现LRU算法呢?研究计算机的人又不像研究数学的人那么精确,而且上述的实现还真是差不多。由于计数域长度有限,我们可以采取定期清零的操作来将时间段限制在我们所喜欢的“最近”。在这个人为确定的最近里面,计数器最小值的页面当然就是最近最少使用的页面了。事实证明,这种近似方法和真实最优情形很接近。

LRU算法的另一种简单实现方式是用一个链表将所有页面链接起来,最近被使用的页面在链表头,最近未被使用的放在链表尾。在每次页面访问时对这个链表进行更新,使其保持最近被使用的页面在链表头。

缺点: : 每次内存访问都对链表进行更新效率十分低下,因为这种更新需要考虑不同页面之间的关系。。使用计数器虽然在每
次内存访问时只需修改一个页面的数据即可,但却需要使用的计数器个数与虚拟页面数一样多,空间成本巨大。另外,在遴选页面来替换时,需要比较所有页面的计数器的取值,时间成本也很大。

LRU算法的优化

  1. 使用矩阵实现LRU算法

比起链表或页表来实现,使用矩阵实现LRU算法就要好不少。顾名思义,矩阵法,就是使用一个矩阵来记录页面的使用频率和时间。

该矩阵为n×n维,n是相关程序当前驻内存的页面数。在一开始矩阵的初值为0。

每次一个页面被访问时,例如第k个虚拟页面被访问时,我们进行如下操作:

  • 1)将第k行的值全部设置为1。
  • 2)将第k列的值全部设置为0。

在每次需要更换一个页面时,选择矩阵里对应行值最小的页面更换即可。此处的行值是指把该行所有的0和1连起来看做一个二进制数时的取值。

例如,假定某个程序有4个虚拟页面,分别是0、1、2、3。该程序虚拟页面的访问顺
序是:
0,1,2,3,2,1,0,3,2,3。那我们来看一下该实现是如何工作的。在这里插入图片描述
在这里插入图片描述

这种实现为什么正确呢? 看似好像没有逻辑关系

我们来看,在一个页面被访问时,我们将该页面所对应的行设置为1,在第1步操作后,我们已经保证该页面对应的行值为最大之一。(那它暂时就不会被选择为更换页面)。

而第2个操作将该页面对应的列置为0,则保证了该页面对应的行值为唯一的最大。

因为我们最终要比较的行的二进制数,通过这两个操作,我们可以保证越久没有被访问的页面,它的对应行元素里被置为 0 的列个数就越多,它所对应的行值就越小。

使用矩阵法相对于链表法和页表法的优点是我们可以使用矩阵的许多理论来迅速判断哪个矩阵行值最小。而且,使用矩阵来实现LRU算法的成本比使用计数域要低,因为这个矩阵的行列数并不需要是虚拟页面数,而是在内存中的实际页面数,因此要小多了。

但是:

但该矩阵仍然太大,因为总的存储位是页面数的平方。

另外,我们将最近的时间限定在最近的9次访问的话,那么应该被更换的是页面0。因为在最近9次访问里,页面0只被访问过1次,而其他页面均被访问过2次或以上。但按照矩阵法的结果是页面1被更换。

由于每次页面访问时,矩阵的置1操作都覆盖了之前的信息,所以矩阵法不能体现长时间访问次数的累积

(3)工作集算法

LRU算法虽然很好,但实现成本高,并且时间代价大。因此,一般的商用操作系统并没有采纳LRU页面更新算法

那么LRU算法为什么会成本高呢?仔细分析可以发现,其成本之所以高,是因为我们想要分辨出不同页面中哪个页面是最近最少使用的。即需要对每个页面保持某种记录,并在每次页面访问发生时或者周期性地对这些记录进行更新,从而造成空间和时间成本居高不下。

我们真的需要为每个页面保留每次访问的记录才能找到一个合适的替换页面吗?

答案是否定的。由于不可能精确地确定哪个页面是最近最少使用的,那就干脆不用花费这个力气,只维持少量的信息使得我们选出的替换页面不太可能是马上又会使用的页面即可。这种少量的信息就是工作集信息。

工作集概念来源于程序访问的时空局域性。即在一段时间内,程序访问的页面将局限在一组页面集合上。例如,最近k次访问均发生在某m个页面上,那么 m 就是参数为 k 时的工作集。我们用w(k, t)来表示在时间 t 时 k 次访问所涉及的页面数量。

显然,随着k的增长,w(k, t)的值也随着增长;但在k增长到某个数值后,w(k,t)的值将增长极其缓慢甚至接近停滞,并维持一段时间的稳定,如图13-7所示。
在这里插入图片描述
由此可以看出,如果一个程序在内存里面的页面数与其工作集大小相等或超过工作集,则该程序可在一段时间内不会发生缺页中断。如果其在内存的页面数小于工作集,则发生缺页中断的频率将增加,甚至发生内存抖动。

就是说:由于程序访问的时空局域性,在一段时间内,程序所需要访问的页面数是固定的一组页面数量, 而这组页面我们称为工作集

因此,我们的目标就是维持当前的工作集的页面在物理内存里面。每次页面更换时,寻找一个不属于当前工作集的页面替换即可。

而在这些不属于工作集的页面中间,无须顾虑哪个页面访问次数多或少,或者最后一次访问离当前时间谁近谁远,因为它们被替换出去都不会造成什么不良后果,从而大大降低页面替换算法的实现复杂性。

这样,我们在寻找页面时只需将页面分离为两大类即可:当前工作集内页面和当前工作集外页面。这样,只要找到一个非当前工作集的页面,即可将其替换。


工作集算法的实现:

为页表的每个记录增加一项信息用来记录该页面最后一次被访问的时间。这个时间不必是真实时间,只要是按规律递增的一个虚拟时间即可。同时我们设置一个时间值T,如果一个页面的最后一次访问在当前时间减去T之前,则视为在工作集外,否则视为在工作集内。

这样,在每次需要替换页面时,扫描所有的页面记录,并进行如下操作:

if (一个页面的访问位是 1):
	将该页面的最后一次访问时间设为当前时间,并将访问位清零
if (一个页面的访问位为0 ):
	查看其访问时间是否在当前时间减去 T 之间, 
	    if( 在):
	    		该页面将是替换页面,算法结束。
	    if(不在):
	    		记录当前所有被扫描过页面的最后访问时间里面的最小值。

就这样不断扫描页面

在这里插入图片描述

如果在所有页面扫描后没有找到一个被替换的页面,则所有页面中最后一次访问时间最早的页面将被替换。这也是第b步记录当前最小值的原因。

该算法选择的是否是一个工作集外的页面呢?如果一个页面的访问位为0,表示自从上次扫描以来,该页面没有被访问过。如果该页面最后一次访问的时间在T时间以前,那么就说明该页面不在工作集内。

这里需要注意的是,工作集既可以用访问次数表示,也可以用时间来表示,它们之间可以相互转换。因此,该算法选择的确的是一个工作集外的页面。当然,如果不存在一个最后一次访问时间在T之前的页面,则该算法选择的就有可能不是工作集外的页面,但选择的也是工作集里的最早页面。

这里需要注意的是,该算法只扫描页表一次。如果所有页面的访问位都为1的话,则表示分配给该程序的内存不够。最好的解决办法不是替换页面,而是给该程序增加一个物理页面。

工作集算法如何设置 T?

工作集有时空局域性,如果时刻都发生变化,那么就没有工作集了。所以它必须在某段时间是比较固定的。即随着访问次数的增多,访问的页面数也在增多,但是到了后面,你会发现,访问了100次的页面,你的页面始终集合在某个集里。

当然,如果你的程序是顺序的,也可能打破这个规则。但在正常情况下,你有50次访问,都是这10个页面,100次也是这10个页面。通过测试,我们可以求出这个临界的次数。在得到这个次数后,就可以将时间T算出来。只有算一下访问这么多次,需要多长时间即可。

工作集算法的优点是实现简单,只需在页表的每个记录增加一个虚拟时间域即可。因此,其空间成本不大。而且,这个时间域不是每次发生访问时都需要修改,而是在需要更换页面时,页面更换算法对其进行修改,因此其时间成本也不大。

工作集时钟算法

前面刚刚说过,工作集算法优势明显,但还是有缺点。缺点就是每次扫描页面进行替换时,有可能需要扫描整个页表。而我们知道,并不是所有页面都在内存里,因此扫描过程中的一大部分时间将是无用功。

工作集通过扫描页表进行页面遍历?

另外,由于其数据结构是线性的,造成每次都按同样的顺序进行扫描,这样就对某些页面不太公平。就好像评选优秀学生,如果大家的表现都同样优秀,就按照学号顺序来确定人选的话,那学号靠后的
同学总是吃亏。

我们可不可以在保留其优点的同时对其进行改进呢?答案是肯定的。方法就是将工作集算法与时钟算法结合起来,设计出工作集时钟算法,即使用工作集算法的原理,但是将页面的扫描顺序按照时钟的形式组织起来。这样每次需要替换页面时,从指针指向的页面开始扫描,从而达到更加公平的状态。

而且,按时钟组织的页面只是在内存里的页面,在内存外的页面不放在时钟圈里,从而提高实现效率。

鉴于其时间和空间上的优势,工作集时钟算法被大多数商业操作系统所采纳。

页面替换策略

讨论了页面替换的各种算法,接下来讨论算法应用的对象,任何算法都可以应用到不同的数据集上。在页面替换算法的考虑中,算法应用的对象可以分为全局和本局两类,从而形成两类替换策略:全局策略和局部策略。

在全局策略下,算法应用的对象是物理内存里面的所有页面,即我们是从所有的页里面按照选定的算法选出替换页面。这个被选出的页面既可以是当前发生缺页中断的进程的页面,也可以是另外一个进程的页面。这种策略可能影响其他进Z程的内存使用。

在局部策略下,算法应用的对象是当前进程的所有页面,即我们是从当前进程中物理页面按照我们选定的算法选出替换页面。这种策略不会影响其他进程的内存使用。

自己进程内的页面进行替换,不替换其他进程的页面。

全局策略的优点是系统的总页面缺失率低,但程序运行不稳定。因为程序无法控制自己的缺页率。一个进程在内存中的页面使用不仅取决于该进程的页面走向,也取决于其他进程的页面走向。因此,相同程序由于外界环境不同会造成执行上的很大差别。

局部替换策略的优点是更加公平,程序运行更加稳定。因为一个进程的内存使用情况不会干扰其他进程的内存使用,但缺点是不能充分利用系统的整体资源,可能造成不同进程之间在页面使用上的不平衡:即有的进程有页面富余,而有的进程却频繁缺页。

固定与可变驻留集

使用何种页面替换策略决定了一个进程在内存所占页面的数量是否固定。局部策略由于只选择本进程的页面进行替换,所以一个进程所占的物理页面数将保持不变,即进程驻留内存的页面数是固定的。全局页面替换策略由于动态地改变一个进程所占物理页面数,进程驻留内存的页面数是可变的。从另一方面说,凡是支持固定驻留集的页面替换算法只能使用局部替换策略,凡是支持可变驻留集的页面替换算法
只能使用全局页面替换策略。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值