【清华大学】操作系统 陈渝——Part6 局部页面置换算法
- 页面置换算法功能与目标
- 功能
- 目标
- 页面锁定(frame locking)
- 实验模拟
- 6.1 最优页面置换算法(OPT)
- 6.2 先进先出算法(First-In First-Out , FIFO)
- 6.3 最近最久未使用算法(LRU)
- 6.4 时钟页面置换算法
- 6.5 二次机会法
- 写回操作
- 6.6 最不常用算法(LFU)
- 6.7 Belady 现象、LRU、FIFO、Clock的比较
- Belady现象
- LRU、FIFO、Clock的比较
页面置换算法功能与目标
功能
当缺页中断发生后,需要调入新的页面时而物理内存已满,没有可分配的空间,需要选择内存中一个物理页面被置换出去
目标
尽可能减少页面的换进换出次数 (即减少缺页中断的次数),因为硬盘的读写比内存的读写要慢1-2个数量级以上,通过这种方式可以确保整个系统运行比较快速,而不是受制于大量的硬盘的读写,虚存效率极大降低。
具体来说,把未来不再使用的或短期内较少使用的页面换出,通常只能在局部性原理指导下依据过去的统计数据来进行预测。
页面锁定(frame locking)
有些物理页是需要常驻内存的,比如说,存放操作系统重要的代码段和数据,这些是要经常访问的。页面锁定 用于描述必须常驻内存的操作系统的关键部分或时间关键(time-cirtical)的应用进程。使得常驻内存的代码段或者数据常驻内存,不在页面置换算法处理的范围内,确保操作系统随时都能正常工作。
实现的方法是:在页表中添加锁定标志位(lock bit)
实验模拟
页面置换算法的分析和比较需要设置一定的实验环境。
设置一个模拟的环境,记录读或写的内存地址,形成一个序列:
记录一个进程对页访问的一个轨迹
-
举例:(虚拟) 地址跟踪 (页号,位移)…
(3,0) , (1,9) , (4,1) , (2,1) , (5,3) , (2,0) , (1,9) , (2,4) , (3,1) , (4,8)
(3,0)表示为访问的地址为:第3个(虚拟)逻辑页面的第0个偏移
-
生成页面轨迹,考虑页面置换算法时只关注页就可以,因为只有当页面不存在时,才会产生缺页中断
3,1,4,2,5,2,1,2,3,4
模拟一个页面置换的行为,并且记录产生页面缺失的此时
- 更好的缺失才会有更好的性能
6.1 最优页面置换算法(OPT)
-
基本思路: 当一个缺页中断发生时,对于保存在内存当中的每一个逻辑页面,计算在它的下一次访问之前,还需要等待多长时间,从中选择等待时间最长的那个,作为被置换的页面。
(ps : 如果能预知未来,当一个中断产生之后,要替换的页是将来很长一段时间内都不会访问的页,就认为这种页暂时不需要,将它替换出去。) -
这只是一种理想情况,在实际系统中是无法实现的,因为操作系统无从知道每一个页面要等待多长时间以后才会再次被访问。
-
可用作其他算法的性能评价的依据(在一个模拟器上运行某个程序,并记录每一次的页面访问情况,这样会得到访问序列,根据这个序列来预知未来,在第二遍运行时即可使用最优算法)
-
实例:
在0时刻时,内存中已经有了访问序列(a,b,c,d)页帧表中可以看出给跑的程序分配了4个物理页帧,但是整个程序需要访问5个页,物理页 不够,会产生页替换。
随着时间的运行,在1-4 时间段内,都不会产生缺页中断,直到t=5 ,需要访问e,但是e的虚拟页不在内存中,需要选一个页替换出去。
基于最优置换算法,选取离下一次访问时间最长的那个页是要被选择替换出去的,计算得出d访问时间距离为10,值最大,将d替换出去,把d留出的物理页空间给e。
结论:直到 t=10时,最优页面置换算法一共产生2次中断。
6.2 先进先出算法(First-In First-Out , FIFO)
-
基本思路:选择在内存中驻留时间最长的页面并将它淘汰。
具体来说,系统中维护着一个链表,记录了所有位于内存当中的逻辑页面。z从链表的排列顺序来看,链首页面的驻留时间最长,链尾页面的驻留时间最短。
当发生一个缺页中断时,把链首位置的页面淘汰出局,并把新的页面添加到链表的末尾。 -
性能较差,调出的页面有可能是经常要访问的页面,并且有Belady现象 (给的物理页越多,缺页就越多)。
-
FIFO算法很少单独使用。
-
实例:
在0时刻时,内存中已经有了访问序列(a,b,c,d)页帧表中可以看出给跑的程序分配了4个物理页帧,但是整个程序需要访问5个页,物理页 不够,会产生页替换。
随着时间的运行,在1-4 时间段内,都不会产生缺页中断,直到t=5 ,需要访问e,但是e的虚拟页不在内存中,需要选一个页替换出去。
基于FIFO换算法,链表顺序为 a - b -c -d ,计算当前内存中驻留时间最久的页即a,将a替换出去,把a留出的物理页空间给e。
接下来,当t=7时,访问a,a已经在t=5 时被替换出去了,产生一次中断,基于FIFO,将b换成a。
接下来,当t=8时,访问b,b刚刚在在t=7 时被替换出去了,又产生一次中断,基于FIFO,将c换成b…
结论:FIFO算法一共产生了5次中断,多于最优页面置换算法。
6.3 最近最久未使用算法(LRU)
-
最近最久未使用算法 (Least Recently Used)
-
基本思路:当一个缺页中断发生时,选择最久未使用的那个页面,并淘汰之。
-
它是对最优页面置换算法的一个近似,其依据是程序的局部性原理,即在最近一小段时间(最近几条指令中)内,如果某些页面被频繁的访问,那么在将来的一小段时间内,它们还有可能会再一次被频繁地访问。反过来说,如果在过去某些页面长时间未被访问,那么在将来它们还可能会长时间地得不到访问。这是一种根据过去推断未来地方式。
-
实例:
在0时刻时,内存中已经有了访问序列(a,b,c,d)页帧表中可以看出给跑的程序分配了4个物理页帧,但是整个程序需要访问5个页,物理页 不够,会产生页替换。
随着时间的运行,在1-4 时间段内,都不会产生缺页中断,直到t=5 ,需要访问e,但是e的虚拟页不在内存中,需要选一个页替换出去。
基于LRU换算法,c 是最近一段时间内最久未使用的,将c替换出去,把c留出的物理页空间给e。
接下来,直到t=9时,访问c,产生缺页中断,在过去时间段内,b,c,d,e中,d是最近一段时间内最久未使用的,将d 替换出去,把d留出的物理页空间给c。
结论:LRU算法一共产生了3次中断,优于FIFO算法。
LRU算法实现
LRU算法需要记录各个页面使用时间地先后顺序。
开销比较大,两种可能的实现方法是:
1.链表实现:系统维护一个页面链表,最近刚刚使用过的页面作为首结点,最久未使用的页面作为尾结点。
每一次访问内存时,找到相应的页面,把它从链表中摘下来,再移动到链表之首。
每次缺页中断发生时,淘汰链表末尾的页面。
因此,链表中,链首为最近访问页面,链尾为最久未使用的页面,替换时直接替换链尾页面;插入时,插入到链首位置。
2.利用堆栈:设置一个活动页面栈,当访问某页时,将此页号压入栈顶,然后,考察栈内是否有与此页面相同的页号,若有则抽出。当需要淘汰一个页面时,总是选择栈底的页面,它就是最久未使用的。
每一次内存访问,访问到某一页时都要去做一次查找,看这个页是否在链表或者堆栈中,这样做的开销其实很大。LRU虽然结果高效,使得缺页减少,但放在操作系统中,要去考虑它的代价,代价如果太大,不是一个好的设计方法。操作系统的设计第一追求高效,第二追求简洁。操作系统如果占用时间太多,留给应用程序的时间就会减少。我们希望操作系统在最短的时间内获得最佳的效果,需要追求一种balance,LRU不能算是有效的方法。
6.4 时钟页面置换算法
掌握LRU算法的利弊以后,寻找一种既有LRU的效果(缺页比较少),同时实现简洁(类似于FIFO,只维护一个链表就可以)的算法
- Clock页面置换算法:LRU的近似,对FIFO的一种改进
- 基本思路:
- 需要用到页表项当中的访问位,当一个页面被装入到内存时,把该位初始化为0。然后如果这个页面被访问(读/写),则把该位置置为1;
- 把各个页面组织成环形链表(类似钟表面),把指针指向最老的页面(最先进来);
- 当发生一个缺页中断时,考虑指针所指向的最老页面,若它的访问为0,立即淘汰;若访问位为"1",则把该位置为0,然后指针往下移动一格。如此下去,直到找到被淘汰的页面,然后把指针移到它的下一格。
- 实例:
维持一个环形页面链表保存在内存中
- 用一个时钟(或者使用/引用)位来标记一个页面是否被经常访问
- 当一个页面被引用时,这个位被设置(为1)
时钟头扫遍页面寻找一个带有 used bit = 0
- 替换在一个周转内没有被引用过的页面,即找到访问位为0的页面替换出去。
运行的程序有5个物理内存页帧,访问程序有8个虚拟页。
Page: 7,4,0,3,1这5个虚拟页在内存中。
Resident bit :存在位
Used bit :访问位
在0时刻时,内存中已经有了访问序列(a,b,c,d)
页帧表中可以看出给跑的程序分配了4个物理页帧,但是整个程序需要访问5个页,物理页 不够,会产生页替换。
随着时间的运行,在1-4 时间段内,都不会产生缺页中断,直到t=5 ,需要访问e,但是e的虚拟页不在内存中,需要选一个页替换出去。
基于时钟页面置换算法, 当前在内存中的四个虚拟页都走了一圈,访问位都置为了0,因此替换a,同时指针指向b。
结论:时钟页面置换算法算法一共产生了4次中断,实际系统中会贴近LRU效果。
**实例 2 **
6.5 二次机会法
写回操作
所谓访问包括了 “读” 和 “写” 操作,“写” 操作即该页会在内存中被修改,使得该页的内容需要 “写” 回到外存中,为了保持物理内存和物理外存一致。
页表项的 “修改位” 是用来区分“读” 和 “写” 的。页面置换就是页面在内存和外存的换入换出过程。
如果是换进的页面从硬盘 “读” 进来,页面换进来也就是 “读” 进来以后,如果这个程序对这个页面的访问都是 “读” 操作,意味着硬盘中的内容和在内存中的内容是一致的,如果这个页被替换出去,不需要把这个页重新 “写” 回到硬盘中去就可以了,直接释放就可以了。
但是如果程序在访问这个页过程中,对这个页进行修改,即进行“写” 操作,意味着这一页的数据在内存中和外存硬盘中的数据不一样,如果要把这个页替换出去的话,要把这一页的内容 “写” 回到硬盘中去使得内存中与硬盘中的数据是一致的,确保再次访问的时候 “写” 回到硬盘中数据是最新的数据。
所以说,通过 dirty bit 来判断出这个页在访问过程中全是 “读” 操作,没有 “写” 操作的话,就不需要执行 “写” 回操作了,减少写回操作可以减少硬盘的操作时间,减少系统延迟,提高系统的性能。另外,在研究固态盘存储技术中,因为Nand闪存芯片物理特性,闪存颗粒有寿命限制,并且闪存只能异地更新,大量的写回到闪存操作不仅会增加延迟,还会增加闪存的擦除操作次数,减少使用寿命。
因此:
- 这里有一个巨大的代价来替换"脏"页。
dirty bit : 脏位即修改位/写位 - 修改Clock算法,使它允许脏页总是在一次时钟头扫描中保留下来
- 同时使用脏位和使用位(访问位)来指导置换
发生缺页中断要替换页面时,替换页的特征选择原则是:当该页的修改位 和 访问位 2个标志位都是0时,会被替换出去。
如果访问位是0,而修改位是 1,把 修改位置为0,访问位不变,指针指向下一页;
如果访问位是1,而修改位是0,把 访问位置为0,修改位不变,指针指向下一页;
如果访问位和修改位是 都是 1,把 访问位置为0,修改位不变,还是 1 ,指针指向下一页。
所以,二次机会算法针对那些脏页,可以让那些经常访问的脏页有更多的机会留在内存中,而不会被置换到硬盘中去,那些只读的页优先被换出去,从而减少了写回次数,也就是减少了硬盘的访问次数。
实例
在0时刻时,内存中已经有了访问序列(a,b,c,d)
页帧表中可以看出给跑的程序分配了4个物理页帧,但是整个程序需要访问5个页,物理页 不够,会产生页替换。
随着时间的运行,在1-4 时间段内,都不会产生缺页中断,直到t=5 ,需要访问e,但是e的虚拟页不在内存中,需要选一个页替换出去。
基于二次机会法,页面替换过程,和产生缺页情况如下:
结论: 产生3次中断,接近LRU 算法,并且优先把只读页换了出去,对于脏页,减少了被换出的次数,对硬盘的写回次数也就减少了,比始终算法的效果更好。
6.6 最不常用算法(LFU)
- 最不常用算法:Least Frequently Used, LFU
- 基本思路:当一个缺页中断发生时,选择访问次数最少的那个页面,并淘汰之。
- 实现方法:对每一个页面设置一个访问计数器,每当一个页面被访问时,该页面的访问计数器加 1 。在发生缺页中断时,淘汰计数值最小的那个页面。
在操作系统实现时,要考虑操作系统本身的效益,还要考虑硬件的成本(资源)开销,越少越好。这也是为什么CLOCK算法比LRU算法好,因为CLOCK算法只用了1个bit。在LFU实现时,首先需要一个计数器,硬件开销大;再者对计数器进行查找,如果访问的页很大,链表很长,查找的时间也就越长。
- LRU和LFU的区别:
LRU | LFU |
---|---|
考察的是时间: 多久未访问 ,时间越短越值得留在内存中 | 考察的是 次数/频度,访问次数越多越值得留在内存中 |
问题:一个页面在进程开始时,由于初始化,做了很多读写操作,但以后就不用了,访问次数少了,由于LFU算法统计的是一个整体的计数,在早期访问次数多,新的时间访问次数少,这种情况下,该页面还留在内存中,显然LFU算法在这样的情况下不好。
解决方法:定期把次数寄存器右移一位。
- 实例:
6.7 Belady 现象、LRU、FIFO、Clock的比较
Belady现象
Belady是科学家名字
- Belady 现象:在采用FIFIO算法时,有时会出现分配的物理页面数增加,缺页率反而提高的异常现象
- Belady 现象的原因:FIFO算法的置换特征与进程访问内存的动态特征是矛盾的,与置换算法的目标是不一致的(即替换较少使用的页面),因为,被置换出去的页面并不一定是进程不会被访问的。
实例如下:
为什么LRU会符合我们的预期,当分配的物理页越多时,缺页次数减少,而FIFO会出现Belady 现象呢?
LRU符合栈算法特点,给的物理页越多,缺页会越少;而FIFO不符合栈算法特点。
LRU、FIFO、Clock的比较
LRU算法和FIFO本质上都是先进先出的思想。
LRU是针对 页面最近访问时间 来进行排序,所以需要在每一次页面访问的时候动态地调整各个页面之间的先后顺序(有一个页面的最近访问时间变了);LRU算法在考虑页面最近被访问时,会从链表/栈中取出来放到链首/栈顶,而FIFO没有,如果程序具有局部性原理,LRU能够减少缺页次数。
LRU算法性能较好,但系统开销大。
FIFO是针对 页面进入内存的时间 来进行排序,这个时间是固定不变的,所以哥哥页面之间的先后顺序是固定的。如果一个页面进入内存后没有被访问,那么它的最近访问之间就是它进入内存的时间。换句话说,如果内存当中的所有页面都未曾访问过,那么LRU算法就退化为FIFO 算法。
FIFO算法系统开销较小,但可能会发生Belady 现象。
缺页次数不仅和算法设计有关,还和程序本身特点有关。如果程序本身不具有局部特性原理,那么LRU算法可能会退化为FIFO算法。
而Clock算法是对LRU算法的一种近似,只需要1个或者2个bit来表示访问时间,仅仅根据这1,2个bit是一种近似。它利用硬件的一些bit来模拟访问时间,先后顺序,它模拟的是LRU算法,而且开销也很小。
Clock算法是一种折衷的办法,在每一次页面访问时,它不必去动态地调整该页面在链表当中的顺序,而仅仅是做一个标记,然后等到发生缺页中断的时候,再把它移动到链表末尾。
对于内存当中那些未被访问的页面,Clock算法的表现和LRU算法一样好;而对于那些曾经被访问过的页面,它不能像LRU算法那样,记住它们的准确位置。
算法的升级,对访问序列有要求,如果程序的访问具有局部性特征,这些算法才能看到效果。如果没有局部性特征,这些算法没什么区别。