当一个理论过时或者出现悖论时,我们就用一个新的理论来替换它。
当一个页面不再有用时,我们也用一个新的页面进行替换。准确地说,我们不是因为一个页面过时而用一个新的页面来替换,而是因为需要使用新的页面而用一个过时的页面作为替换牺牲品。
页面需要更换
分页系统克服了交换系统的各种缺点:外部碎片、难以增长、程序不能大于物理内存等
在交换系统下,一个程序作为一个整体加载到内存。因此,在运行时,无须再从磁盘上加载任何东西。而在分页系统下,一个程序的所有页面并不一定都在内存中,因此,在执行的过程中就有可能发生页面不在内存的情况。
如果访问的页面不在内存中,则系统将产生缺页中断。缺页中断服务程序将负责把位于磁盘上的数据加载到物理内存来。如果物理内存还有空闲页面,那就直接使用空闲的页面。但如果物理内存已满,则需要挑选某个已经使用过的页面进行替换。
页面更换的目标
因为磁盘访问速度远远慢于内存访问速度,缺页中断的代价是非常大的。因此,挑选哪个页面进行更换换是有要求的。
也就是说,我们需要精心设计一种算法来进行页面的更换。
那么要设计这种算法,首先得知道我们的目的是什么。
是降低随后发生缺页中断的次数或者概率
从哲学的层次上来看,所有的算法都归结为两个大类的一类:
公平算法
非公平算法
公平算法主要包括下面4种:
随机算法
先来先出(FIFO)算法
第二次机会算法
时钟算法
非公平算法则包括如下5种:
最优算法
NRU算法
LRU算法
工作集算法
还有一种混合算法,它既想保持公平,又含有区别对待的考虑:工作集时钟算法。
随机更换算法
在需要替换页面的时候,产生一个随机页面号,而替换与该页面号对应的物理页面。
事实上,随机选页所选出的被替换的页面不太可能是随后相当长时间内不会被访问的页面。也就是说,这种算法难以保证最小化随后的缺页中断次数。事实上,这种算法的效果相当差。
先进先出算法
既然随机算法效果不怎么样,那我们就需要进行改进。先进先出就是我们的第一个改进算法。先进先出的英文缩写是FIFO(First In First Out)。顾名思义,该算法的核心是更换最早进入内存的页面。先进先出是任何人都能想到的办法,因为它是人类的天性。
FIFO的实现机制是使用链表将所有在内存的页面按照进入时间的早晚链接起来,然后每次置换链表头上的页面就行了。新加进来的页面则挂在链表的末端。如图
如果下次需要寻找页面来替换,A将成为替换页面。
但这个绝对的公平方式容易降低效率。例如,如果最先加载进来的页面是经常被访问的页面,那么这样做很可能造成常被访问的页面替换到磁盘上,导致很快就需要再次发生缺页中断,从而降低效率。
第二次机会算法
由于FIFO只考虑进入内存的时间,不关心一个页面被访问的频率,从而有可能造成替换掉一个被经常访问的页面而造成效率低下。
那么我们对FIFO改进的方向就是考虑一个页面是否是经常被访问的因素。
如果每个页面都是最近被访问过,那怎么办呢?
因为我们给一个页面第二次机会时已经将其访问位清零。因此,最多轮转一圈后,碰到最先给予机会的页面,此时其访问位已经是0,因此可将其替换。
时钟算法
第二次机会算法既简单、公平,又容易实现。只不过,每次给予一个页面第二次机会时,将其移到链表末端需要耗费时间。
另外,页面的访问位只在页面替换进行扫描时才可能清零,所以其时间局域性体现得不好,访问位为1的页面可能是很久以前访问的,时间上的分辨粒度太粗,从而影响页面替换的效果。
为了改善这些缺点,人们想出了时钟算法。
在时钟算法里,我们把页面排成一个时钟的形状。该时钟有一个针臂。每次需要更换页面时,我们从针臂所指的页面开始检查。如果当前页面的访问位为0,即从上次检查到这次,该页面没有被访问过,将该页面替换。如果当前页面被访问过,那就将其访问位清零,并顺时针移动指针到下一个页面。我们重复这些步骤直到找到一个访问位为0的页面
指针指向的是页面F。这样第一个被考虑替换的是页面F。如果页面F的访问位为0,F将被替换。如果页面F的访问位为1,则F的访问位清零,指针移到页面G。
这个算法和第二次机会算法不是一样的吗?
首先,它们的数据结构不一样。
第二次机会使用的是链表,而这里使用的是索引(整数指针)。
然后,这样其使用的内存空间不一样:第二次机会需要使用额外的内存,而时钟算法可以直接使用页表。使用页表的好处是无需额外的空间。更大的好处是页面的访问位会定期自动清零。这样将使得时钟算法的时间分辨粒度较第二次机会算法高,从而取得更好的页面替换效果。
时钟算法的精髓就是第二次机会算法,其缺点就和第二次机会算法一样
最优更换算法
前面介绍的4种算法均属于“公平算法”。
但是这种公平实现方式,会使效率受到一定影响。
这是因为个体对整个系统的贡献没有被区别对待,造成贡献大的和贡献小的待遇一样,自然就会影响整个系统的效率。
在非公平的算法里面,最理想的页面替换算法是选择一个再也不会被访问的页面进行替换。如果不存在这样的页面,那至少选择一个在随后最长时间内不会被访问的页面进行替换。这样,我们就可以保证在随后发生缺页中断的次数最小或者概率最低,这种替换算法就是最优的替换算法。
那我们怎么知道一个页面随后多长时间不会被访问呢?当然不知道。
既然如此,最优更换算法就是一种无法实际实现的算法。那么我们为什么要介绍最优算法呢?那是为了定义一个标杆,以此来评判其他算法的优劣。例如,张三和李四各设计了一个页面替换算法,如何判断它们谁优谁劣呢?与标杆比较即可。谁的算法与最优算法更接近,谁的算法就好;反之就差。
NRU算法
选择一个最近没有被访问的页面来替换,但在所有的最近没有使用的页面里,不是按照最先进入来划分,而是按照各个页面的修改位和访问位的组合来进行划分。这种算法称为最近未使用算法(Not Recently Used,NRU)。
做出这种选择是基于程序访问的时空局域性。因为根据时空局域性原理,一个最近没有被访问的页面,在随后的时间里也不太可能被访问,而NRU的实现方式就是利用页面的访问和修改位。
凡是对页面进行读写操作时,访问位设置为1。当进程对页面进行写操作时,修改位设置为1。根据这两个位的状态来对页面进行分类的话,可以分成4种页面类型:1、2、3、4
第2种状态就是系统对访问位会定期清零,对修改位却不能清零。访问位定期清零是保证这个访问位不失去意义的手段。如果不定期清零,则一段时间后所有的页面都是被访问的。
因为所谓访问和未访问都需要局限在一段时间内讨论才有意义,如果考虑无限长的时间段,页面都是被访问状态。那我们就无法判断了。
这种分类比较笼统,在同一类页面里,我们没有办法分辨出哪一类被访问时间更最近一些。即在某些情况下,我们替换的可能并不是最近没有使用的页面。换言之,在最差情况下,时间的分辨粒度粗到一个访问位清零的周期。
LRU算法
LRU算法是最近使用最少算法(LeastRecently Used)。我们不仅考虑最近是否用过,还要考虑最近使用的频率。
此处我们使用的哲学理念仍是拿过去的数据来预测将来:如果一个页面被访问的频率低,那么以后很可能也用不到。
使用矩阵实现LRU算法
矩阵法,就是使用一个矩阵来记录页面的使用频率和时间。该矩阵为n×n维,n是相关程序当前驻内存的页面数。在一开始矩阵的初值为0。每次一个页面被访问时,例如第k个虚拟页面被访问时,我们进行如下操作:
1)将第k行的值全部设置为1。
2)将第k列的值全部设置为0。
在每次需要更换一个页面时,选择矩阵里对应行值最小的页面更换即可。此处的行值是指把该行所有的0和1连起来看做一个二进制数时的取值。
使用移位寄存器实现LRU算法
虽然使用矩阵法实现LRU算法已经较链表法和页表法有了很大的改善,但成本仍然较高。虽然实际驻内存的页面数比虚拟页面数要少,但毕竟一个n×n维的矩阵还是相当地占空间。
给每个存放在内存的页面配备一个移位寄存器。该寄存器用来记录该页面被访问频率和最近的属性。所有移位寄存器的初值皆为0。然后在每个规定长度的周期内,将移位寄存器的值往右移动一位,并将对应页面的访问位的值加到该移位寄存器的最左位。
例如,假定某程序有6个页面在内存中,页号分别为0、1、2、3、4、5。其对应的6个移位寄存器初值皆设置为0,如图13-6a所示。
在第一个规定周期内,页面0、2、4、5的访问位为1,页面1和3的访问位为0。我们将移位寄存器往右移动一位,并将各个页面的访问位加到相应移位寄存器的左面,就得到图13-6b的状态。在第2个规定周期内,页面0、1、4的访问位为1,而2、3、5的访问位为0,我们重复同样的操作获得图13-6c的状态。假如第3个规定周期内页面0、1、3、5的访问位为1,2、4的访问位为0,则获得图13-6d的状态。第4个周期内页面0、4被访问过,其他页面的访问位为0,得到图13-6e的状态,第5个周期内页面1、2被访问过,其他页面的访问位为0,得到图13-6f的状态。
用移位寄存器实现的,造价高。即使是单价降低了,成本仍然很大,因为需要的寄存器数量较大。
工作集算法
LRU算法为什么会成本高呢?
是因为我们想要分辨出不同页面中哪个页面是最近最少使用的。即需要对每个页面保持某种记录,并在每次页面访问发生时或者周期性地对这些记录进行更新,从而造成空间和时间成本居高不下。
在面对这种时间和空间上的双重困难时,也许我们应该反思一下:我们真的需要为每个页面保留每次访问的记录才能找到一个合适的替换页面吗?
答案是否定的。由于不可能精确地确定哪个页面是最近最少使用的,那就干脆不用花费这个力气,只维持少量的信息使得我们选出的替换页面不太可能是马上又会使用的页面即可。这种少量的信息就是工作集信息。
工作集概念来源于程序访问的时空局域性。即在一段时间内,程序访问的页面将局限在一组页面集合上。例如,最近k次访问均发生在某m个页面上,那么m就是参数为k时的工作集。我们用w(k,t)来表示在时间t时k次访问所涉及的页面数量。
显然,随着k的增长,w(k,t)的值也随着增长;但在k增长到某个数值后,w(k,t)的值将增长极其缓慢甚至接近停滞,并维持一段时间的稳定,如图13-7所示。
如果其在内存的页面数小于工作集,则发生缺页中断的频率将增加,甚至发生内存抖动。
工作集算法的实现如下:
为页表的每个记录增加一项信息用来记录该页面最后一次被访问的时间。这个时间不必是真实时间,只要是按规律递增的一个虚拟时间即可。同时我们设置一个时间值t,如果一个页面的最后一次访问在当前时间减去t之前,则视为在工作集外,否则视为在工作集内。
这样,在每次需要替换页面时,扫描所有的页面记录,并进行如下操作:
1)如果一个页面的访问位是1,则将该页面的最后一次访问时间设为当前时间,并将访问位清零。
2)如果页面的访问位为0,则查看其访问时间是否在当前时间减去t之前。
a)如果在,则该页面将是被替换页面,算法结束。
b)如果不在,记录当前所有被扫描过页面的最后访问时间里面的最小值。扫描下一个页面并重复1、2两个步骤。
该算法如图13-8所示。
如果在所有页面扫描后没有找到一个被替换的页面,则所有页面中最后一次访问时间最早的页面将被替换。这也是第b步记录当前最小值的原因。
这里需要注意的是,该算法只扫描页表一次。如果所有页面的访问位都为1的话
那么该算法的t设置为多少呢?或者说我们怎么知道每个进程的工作集有多大呢?工作集有时空局域性,如果时刻都发生变化,那么就没有工作集了。
通过测试,我们可以求出这个临界的次数。在得到这个次数后,就可以将时间t算出来。只有算一下访问这么多次,需要多长时间即可。
工作集时钟算法
由于其数据结构是线性的,造成每次都按同样的顺序进行扫描,这样就对某些页面不太公平。就好像评选优秀学生,如果大家的表现都同样优秀,就按照学号顺序来确定人选的话,那学号靠后的同学总是吃亏。
方法就是将工作集算法与时钟算法结合起来,设计出工作集时钟算法,即使用工作集算法的原理,但是将页面的扫描顺序按照时钟的形式组织起来。这样每次需要替换页面时,从指针指向的页面开始扫描,从而达到更加公平的状态。而且,按时钟组织的页面只是在内存里的页面,在内存外的页面不放在时钟圈里,从而提高实现效率。
页面替换策略
前面的内容讨论了页面替换的各种算法,但是任何算法都可以应用到不同的数据集上。在页面替换算法的考虑中,算法应用的对象可以分为全局和本局两类,从而形成两类替换策略:全局策略和局部策略。
在全局策略下,算法应用的对象是物理内存里面的所有页面,即我们是从所有的页里面按照选定的算法选出替换页面。这个被选出的页面既可以是当前发生缺页中断的进程的页面,也可以是另外一个进程的页面。这种策略可能影响其他进程的内存使用。
在局部策略下,算法应用的对象是当前进程的所有页面,即我们是从当前进程中物理页面按照我们选定的算法选出替换页面。这种策略不会影响其他进程的内存使用。
全局策略的优点是系统的总页面缺失率低,但程序运行不稳定。因为程序无法控制自己的缺页率。
局部替换策略的优点是更加公平,程序运行更加稳定。
固定与可变驻留集
使用何种页面替换策略决定了一个进程在内存所占页面的数量是否固定。
局部策略由于只选择本进程的页面进行替换,所以一个进程所占的物理页面数将保持不变,即进程驻留内存的页面数是固定的。
全局页面替换策略由于动态地改变一个进程所占物理页面数,进程驻留内存的页面数是可变的。从另一方面说,凡是支持固定驻留集的页面替换算法只能使用局部替换策略,凡是支持可变驻留集的页面替换算法只能使用全局页面替换策略。
初始页面数确定
为每个进程分配内存块的算法主要有3种:均分法、比例法、优先权法。
《操作系统之哲学原理第2版》邹恒明
精彩摘抄
?