功能:置换算法是指当出现缺页异常时,需要调入新页面而内存已满时,置换算法选择被置换的物理页面。
设计目标:
- 尽可能减少页面的调入调出次数;
- 把未来不再访问或短期内不访问的页面调出。
页面锁定:
了解具体的置换算法之前,先了解一个概念,页面锁定。页面锁定是用来描述某些必须常驻内存的逻辑页面,比如操作系统的关键部分,再比如一些要求响应速度的代码和数据。页面锁定是通过页表中的锁定标志位实现的。
分类:
- 局部置换算法:置换页面的选择范围仅限于当前进程占用的物理页面内。具体又有一系列算法:最优算法、先进先出算法、最近最久未使用算法,最近最久未使用算法又衍生出两种近似算法:时钟算法、最不常用算法,
- 全局置换算法:置换页面的选择范围是所有可换出的物理页面。具体有工作集算法、缺页率算法。
最优界面置换算法:
- 基本思路:选择内存中等待时间最长(未来最长时间不访问)的页作为置换页面。
- 算法实现:缺页时,计算内存中每个逻辑页面的下一次访问时间;选择未来最长时间不访问的页面。
- 算法特征:
- 只能是理想情况,OS无法实现(不知道未来情况)。
- 可以作为最佳的标准,在第二遍运行时利用第一次的访问轨迹使用最优算法。其他算法应尽量逼近。
先进先出算法( first-in first-out FIFO)
- 思路:选择在内存中驻留时间最长的页面并淘汰之。OS维护着一个队列链表,淘汰首位,添加末位。
- 实现:维护一个记录所有位于内存中的逻辑页面;链表元素按驻留内存时间排序,链首最长,链尾最短;出现缺页时,选择链首页面进行置环,新页面加到链尾。
- 特征:性能较差,调出的页面可能是常用页面(驻留时间长,本身就说明可能常用),有belady现象(给的物理页帧越多反而缺页越频繁)。因此该算法很少单独使用。
最近最久未使用算法(least recently used LRU)
- 思路:选择最长时间没有被引用的页面进行置换。是对最优置换算法的近似,以过去推未来。根据程序的局部性原理,如果最近一段时间内某些页面被频繁访问,那么在将来还可能被频繁访问。反之,未被访问的将来也不会被访问。 程序应具有较好的局部性。
- 实现:缺页时,计算内存中每个逻辑页面上一次访问时间;选择上一次使用到当前时间最长的页面
- 特征:最优置换算法的一种近似,但仍然很复杂,很难实现。下面是LRU算法的两种可能的实现算法:
- 页面链表:系统维护一个按最近一次访问时间排序的页面链表,链表首节点是最近使用的页面,尾节点是最久未使用的页面,访问内存时找到相应页面,并把它移到链表之首,缺页时,置换链表尾节点的页面。
- 活动页面堆栈:访问页面时将此页号压入栈顶,并将栈内相同的页号抽出,缺页时置换栈底的页面。(栈是先进后出,只有栈顶开口,怎么push栈底?)
- 可以看到这两种方式存在一个问题就是平时不缺页时开销较大,每次访问页面都需要遍历链表或栈,找到相同的元素进行处理。
时钟置换算法(Clock):
时钟置换算法是最近最久未使用算法的优化。先进先出是完全不考虑最近访问情况,最近最久未使用算法是将所有页面在整个运行过程中的访问情况都进行考虑。而时钟置换算法是这两者的折中,即仅对页面的访问情况进行大致统计。具体实现
- 思路:仅对页面的访问情况进行大致统计
- 数据结构:在页表项中增加访问位,描述页面在过去一段时间内的访问情况;各页面组织成环形链表;指针指向最先调入的页面;
- 算法实现:页面装入内存时,访问位初始化为0;访问页面(读/写)时,访问位置1;缺页时,从指针当前位置顺序检查环形链表【访问位为0,则置环该页;访问位为1,则访问位置0,并指针移动到下一个页面,直到找到可置换得页面】
- 特征:是FIFO和LRU的折中
时钟置换算法实际还有一些改进。比如减少修改页的缺页处理开销。因为被修改的页如果要被置换,需要先写到外存,再将需要的页写入内存,开销至少乘2。因此为了减小修改过的页被置换,可以遇到被修改过的页指针就跳过。而在系统空闲时定期地将内存写入外存。实现通过在页面中增加修改位,并在访问时进行相应修改,缺页扫描时跳过有修改的页面。
改进的Clock算法
- 思路:减少修改页得缺页处理开销
- 算法:在页面增加修改位,并在访问时进行相应修改;缺页时,修改页面标志位,以跳过有修改得页面
最不常用置换算法(Least Frequently Used, LFU):
- 思路:选择置换访问次数最少的那个页面
- 实现:对每个页面设置访问计数器,当一个页面被访问时加1。置换数值最小的那个。
- 特征:存在的问题就是统计开销大,并且新拿进来的页面可能因为计数较少马上又被置换出去,对于后面这个问题的一个解决方法是已经计的数定期地衰减
- 最不常用置换算法页式最近最久未使用算法的优化。可以看到LFU与LRU的区别。LRU关注多久未被访问,时间越短越好,LFU关注访问次数,次数越多越好。
LFU与时钟算法都是对LRU算法的一种简化近似,开销减小,同时精度下降。LFU是比较难实现的,因此在内存管理中基本不会采用,但是在读硬盘文件的时候对时间要求不高的场景中还是可以使用的。
Belady现象
当一个进程频繁出现缺页异常时,应该分配给进程更多的物理页面,分配完后按照常理,缺页异常出现的频率应该降低。但是如果算法不好,很可能不会降低,我们把这种现象称为Belady现象。
比如FIFO算法,因为置换出去的页面不一定是进程近期不会访问的页面,如下图:
但是LRU算法是没有Belady现象的。时钟算法和改进的时钟算法也都是没有Belady现象的。分配更多的页面以为这更大的缓存,缓存越大命中率肯定升高。
综合比较局部页替换算法
LRU和FIFO的比较:LRU和FIFO本质都是先进先出,但LRU是页面的最近访问时间而不是进入内存的时间,有动态调整,符合栈算法的特性,空间越大缺页越少。如果程序局部性,则LRU会很好。如果内存中所有页面都没有被访问过会退化为FIFO。
Clock 和enhanced clock也是类似于FIFO的算法,但用了硬件的BIT来模拟了访问时间和顺序,近似了LRU,综合起来较好,但也会退化为FIFO。
都对程序的访问次序有局部性的要求,不然都会退化。
LRU、FIFO和Clock的比较:开销上,LRU开销大,FIFO开销小但BELADY,折中的是Clock算法,开销较小。对内存中还未被访问的页面,Clock效果等同LRU。Clock对曾经被访问过的则不能记住其准确位置,而LRU算法可以。
全局置换算法:
局部置换算法没有考虑进程访问的差异,有时候给一个进程多分配一个物理页面可以大幅度降低它的缺页率。全局置换算法就是要为进程分配可变数目的物理页面。它需要解决以下几个问题:
- 进程在不同阶段的内存需求是变化的
- 分配各进程的内存也需要在不同阶段有所变化。
- 全局置换算法需要确定分配给进程的物理页面数
首先我们需要知道CPU利用率与并发进程数之间的关系。随着并发进程数从0增加,CPU的利用率也不断增大;当进程数越来越多,内存访问的局部性特征越来越被破坏,内存变的越来越紧张,利用率增长速度开始放缓,当增加到某个临界点,CPU利用率开始下降。也就是CPU利用率与并发进程数存在相互促进和制约的关系。
工作集
一个进程当前使用的逻辑页面集合,可以用一个二元函数W(t,Δ),t是当前执行时刻,Δ是工作集窗口(working-set window),一个定长的页面访问的时间窗口。t+Δ构成了一个时间段,W(t,Δ)就是在当前时刻t之前的Δ时间内所有访问页面组成的集合,在随t不断更新。| W(t,Δ)|是工作集的大小即页面数目。
工作集的变化:进程开始后,随着访问新页面逐步建立较稳定的工作集,当内存访问的局部性区域的位置大致稳定时| W(t,Δ)|波动很小,在过渡阶段,则会快速扩张和收缩过渡到下一个稳定值。有波峰,有波谷。
常驻集
定义:在当前时刻,进程实际驻留在内存当中的页面集合。
工作集与常驻集的关系:工作集是固有性质,常驻集取决于系统分配给进程的物理页面数目和所采用的置换算法。
缺页率与常驻集的关系:当常驻集包含了工作集,也就是工作集都在内存中,缺页较少;工作集发生剧烈变动时,缺页较多;当常驻集大小达到某个数目后,再分配物理页帧也不会有明显下降的缺页率——可以把多出来的物理页帧分给其他程序了
工作集置换算法:
- 思路:换出不在工作集中的页面
- 窗口大小:当前时刻前τ个内存访问的页引用是工作集,τ被称为窗口大小
- 实现方法:访存链表:维护窗口内的访存页面链表;在访存时,换出不在工作集的页面;缺页时,换入页面,更新访存链表。
工作集置换算法的思路是换出不在工作集中的页面。局部置换算法是在缺页异常发生时才决定置换哪个页面,但是工作集置换算法不一定是在缺页时才做这件事,而是在访存时就进行处理。工作集置换算法中有一个窗口大小 τ,当前时刻前 τ 个内存访问的页引用是工作集。
可以看到,缺页处理很简单,但是每次访存都要进行判断,开销还是很大的。这与LRU算法是类似的。
缺页率置换算法(Page-Fault-Frequency, PFF)
缺页率=缺页次数/内存访问次数 =1/缺页的平均时间间隔
影响因素有:页面置换算法,分配给进程的物理页面数目(越多越小),页面本身的大小(页面大则会小),编程方法(局部性好就会小)
缺页率置换算法是依据缺页率来决定哪些页面放到内存哪些被置换出去。其中我们能够控制的是页面置换算法。
正常情况下,随着分配给进程的物理页面数增多,缺页率会下降,缺页率置换算法通过调节常驻集的大小,使每个进程的缺页率保持在一个合理的范围内。如果缺页率过高,则增加物理页面数,如果缺页率过低,则降低物理页面数。
- 思路:动态调整常驻集的大小。性能较好,但增加了系统开销
- 具体机制:
- 访存时设置引用位标志
- 缺页时,计算从上次缺页时间 tlast 到现在的时间 tcurrent 间隔,如果时间间隔 tcurrent - tlast 大于常量 T,则置换所有在 [tlast , tcurrent] 时间内没有被引用的页,以减少常驻集的大小;如果时间间隔 tcurrent - tlast 小于等于常量 T,则增加缺失页到常驻集。
对比缺页率置换算法与工作集置换算法,缺页率置换算法是在缺页异常时进行处理,工作集置换算法是在访存时进行处理,缺页的出现频率是远小于访存的,开销降下来了。
抖动
- 抖动:如果分配给一个进程的物理页面太少,常驻集远小于工作集,则缺页率会很大,频繁在内外存之间替换页面,使进程的运行慢,这种状态成为”抖动”。
- 产生抖动的原因:随着驻留内存的进程数目增加,分配给每个进程的物理页面数不断减少,缺页率上升。因此OS要选择一个适当的进程数目和进程需要的帧数,在并发水平和缺页率中达到平衡。
负载控制
我们希望平均缺页间隔时间(MTBF)大于等于缺页异常处理时间(PFST)。因此如下图右侧虚线以左是CPU满负荷工作也满足平均缺页间隔时间大于等于缺页异常处理时间的区域。