虚拟内存(二)
页面置换算法
地址变换时,当需要的页面不在内存时,发生缺页中断,操作系统选择
- 哪些页必须删除
- 给进入的页腾出空间
已修改的页必须首先保存下来
- 未被修改的只需覆盖就行了
最好不要选择经常使用的页
- 可能马上又要用到这些页
最优页面置换算法
最优页面置换算法最容易描述,但不可能实现
该算法是这样工作的:发生缺页时,某些页面在内存中。其中有一页将很快被引用,其他页则要以后才会被访问。每个页都可以用该页面首次被访问前所要执行的指令数进行标记
最优页面置换算法只是简单地规定,标记最大的页应该被淘汰掉
最优页面置换算法只是简单地规定,标记最大的页应该被淘汰掉。如果一个页在800万条指令内不会被使用,另外一个页在600万条指令内不会被使用,则淘汰掉前一个,从而把因为需要取回该页而发生的缺页退后,越远越好
这个算法唯一的一个问题是它是无法实现的。发生缺页时,操作系统无法知道各个页面下一次是在什么时候被引用
虽然这个方法在实际系统中毫无用处,但对评价页面置换算法很有用
最近未使用(NRU)算法
为每个页面设置两个状态位:引用位R表示页面被访问;修改位M表示页面被写入
- 当页面被访问、被修改时设置相应位
- R位被定期地清零
页面分为以下几类
| 类别 | 意义 | R位 | M位 |
|---|---|---|---|
| 第0类 | 没有被访问,没有被修改 | 0 | 0 |
| 第1类 | 没有被访问,已经被修改 | 0 | 1 |
| 第2类 | 已经被访问,没有被修改 | 1 | 0 |
| 第3类 | 已经被访问,已经被修改 | 1 | 1 |
NRU(Not Recently Used,最近未使用)算法随机地从编号最小的非空类中挑选一页删除
这个算法隐含的意思是,删除一个在最近一个时钟周期中没有被引用的已修改页要比清除一个被频繁访问的“干净”页好
NRU吸引人的地方是其易于理解和能有效地实现,并且尽管其性能不是最好的,但是还是足够的
先进先出(FIFO)
思想:选择在内存中驻留时间最长的页淘汰。操作系统维护一个当前在内存中的所有页面的链表,最老的页面在表头,最近到达的页在表尾。发生缺页时,删除表头的页面,并把新页添加到表尾
缺点:未考虑程序的动态特性。在顺序访问时较理想,但对于循环程序段,往往把会频繁访问的页面周期性选择为淘汰对象
一半不单纯使用FIFO算法,要结合其他技术
第二次机会(Second Chance)
FIFO算法可能会把经常使用的页面置换出去,为了避免该问题,对该算法做类简单的修改:检查最老页面的R位,如果R为0,则该页面既老又没用,因此理科置换,如果是1,就清除该位,并将该页放到链表的尾端,更新其载入时间使他就像刚到达内存一样。然后继续搜索其他页。
实质:寻找一个自上次检查以后未被访问的页淘汰
与NRU区别:R位不是定期清零,检查链表时才清除

(a)按FIFO排序
(b)时间20时发生缺页,A的R位被设置(数字为载入时间)
时钟(Clock)页面置换算法
尽管第二次机会算法是一个合理的算法,但它经常要在链表中移动页面,既降低了效率,有没有必要
一个更好的办法是把所有的页保存在一个类似钟表面的环形链表中,有一个指针指向最老的页面
缺页发生时
- 检查指针所指向的页面
- R=0,选择该页淘汰,算法结束
- 若R=1,则清除R位:R←0
- 指针加1,继续检查

最久未使用(LRU)算法
对最优算法的良好近似基于这样的观察:即在最近几条指令中使用频繁的页面很可能在接下来的几条指令中频繁使用。
反过来说,已经很久没有使用的页面很有可能在未来较长一段时间内也不会被用到
该思想提示类一个可以实现的算法:发生缺页时,淘汰未使用时间最长的页。该策略成为LRU(Least Recently Used,最久未使用)分页。
一种实现
维护一张链表,最近使用的页位于表头,最久未使用的页位于表尾;每次内存访问都要更新链表(把最新访问的页移到表头)
一种硬件实现方法
有一个64位的计数器C,它在每条指令执行完后自动加1。而且,每个页表项还必须有足够容纳该计数器的域。在内存访问内存后,C的当前值被保存到刚刚被引用页面的页表项中
发生缺页时,操作系统检查页表中所有的计数器以找出最小的一个。该页就是最久未使用的页
第二种硬件LRU算法
有n个页框的机器中,LRU硬件可以维持一个n✖n的矩阵,其初始值全部为0。每当引用到页k时,硬件首先把行k所有位都设置成1,再把列k所有为都设置成0。任何时刻,二进制值最小的行就是最久未使用的,以此类推

LRU使用矩阵—页面被引用的顺序为0,1,2,3,2,1,0,3,2,3
软件模拟LRU
前面两种LRU算法在理论上都可以实现,但只有非常少(如果有的话)的计算机有这种硬件,因此需要的是可以用软件实现的解决方案
一种可能的方案称为NFU(Not Frequently Used,不经常使用)算法。该算法需要一个初值为0的软件计数器与每页相联。每次时钟中断时,操作系统扫描内存中的所有页面,将每页的R位(其值为0或1)加到计数器上。缺页时淘汰计数器值最小的页
实质:跟踪每页被访问的频繁程度
老化算法
对NFU算法的修改:
-
先将计数器右移一位
-
把R位加到计数器的最左端
—称为老化算法,很好地模拟了LRU
假设,在第一个时钟周期后,页0到5的R位值分别为1、0、1、0、1、1(页0为1,页1位0,等等)。换句话说,在时钟周期0到时钟周期1期间,页0、2、4、5被引用,设置其R为为1,而其他的页仍然是0。对应的6个计数器在经过移位并把R位插入其左端后的值如下图所示。途中后面的4列是接下来4个时钟周期后的6个计数器的值

用软件模拟LRU的老化算法
- 图中所示是6个页面在5个时钟周期的情况,5个时钟周期分别由(a)-(e)所示
工作集(working set)
在单纯的分页系统里,启动进程时,内存中没有其页面。当CPU试图取第一条指令时就会产生一次缺页,使操作系统载入含有第一条指令的页。其他关于全局变量和堆栈的缺页通常会紧接着发生。一段时间以后,进程已经有了大部分其所需的页,开始安定运行,较少发生缺页了。这个额策略称为请求调页(demand paging),因为页面只在需要时载入,而不是预先载入。
进程当前正在使用的页的集合称为它的工作集(working set)。如果整个工作集都在内存里,进程的运行很少引起缺页,除非进入下一运行阶段。
如果内存太小而无法容纳整个工作集,进程的运行会产生大量的缺页,且运行缓慢,由于通常执行一条指令只需要几个纳秒,而从磁盘上读入一个页面一半需要几十个毫秒。
程序每执行几条指令就会发生一次缺页,称为系统颠簸(thrashing)。
不少分页系统都设法记录进程的工作机,以确保进程运行之前,其工作机就已经在内存中。该方法称为工作集模型(working set model)。其目的在于大大减少缺页率。进程在运行前载入页面,也称为预先分页(prepaging)。
注意:工作集一直在变化

工作集是由K个最近内存引用使用的页面组成的集合
函数w(w, t)是工作集在时刻t的大小
基于工作集的页面置换算法
- 当前虚拟时间(CVT):一个进程从它启动开始到现在所实际占用CPU的时间
- 工作集:在进程虚拟时间内,过去\(\tau\)秒内所访问过的页面的集合
- 思想:缺页时,淘汰不在工作集中的页面
- 实现:页表项中有一个域记录页面最近使用时间(LUT),R位每次时钟中断清零。发生缺页时,搜索页表,检查R位:
- R == 1,LUT←CVT(表示缺页时,此页面被访问)
- R == 0 && age = CVT-LUT > \(\tau\),(此页不在工作集中),选择该页淘汰
- R == 0 && age <= \(\tau\),记下age最大者
- 重复上述步骤,无第二种情况存在,则选择age最大者淘汰(LUT最小者);若第2、3都不存在,则选择一个M=0的页面淘汰。

工作集时钟(WSClock)
由于在每次缺页时都要扫描整个页表直到定位一个合适的候选,因此工作集算法还是比较麻烦
基于时钟算法,但是同样适用工作集信息的改进算法称为工作集时钟(WSClcok)。由于其易于实现而且性能优良,在实际中被广泛使用
它所需的数据结构是页框的一个环形链表,和时钟算法一样

WSClock的算法操作
页面置换算法实例
设内存中有3个页框


页面置换算法小结
| 算法 | 注释 |
|---|---|
| 最优 | 无法实现,不过可以作为测量基准 |
| 最近未使用(NRU) | 非常粗糙 |
| 先进先出(FIFO ) | 可能丢弃重要的页 |
| 第二次机会 | 对FIFO有很大改进 |
| 时钟 | 很实际 |
| 最久未使用(LRU) | 完美,但很难精确地实现 |
| 非经常使用(NFU) | 相当粗糙地近似LRU |
| 老化 | 很好地近似LRU的有效算法 |
| 工作集 | 实现代价有点高 |
| WSClock | 非常有效的算法 |
最优算法置换当前页中最后引用的页。不幸的是,没有办法确定哪一页最后,因此实际当中无法使用该算法。不过,它可以作为基准用于测试其他算法。
NRU算法根据R和M位将页面分成四类。从最低序号的类中随机选择一页。该算法易于实现,但过于粗糙,还有更好的一个算法。
FIFO通过链表记录页面载入内存的次序。删除最老的页面,不过该页框可能仍在使用当中,因此FIFO不是一个好的选择。
第二次机会是FIFO的一种改进,该算法在删除页面之前检测其是否正在使用中,如果是,则保留该页。这一改进大大提升类性能。时钟算法与第二次机会的实现稍有不同。它具有相同的性能属性,不过算法的执行时间要少一些。
LRU是一种极好的算法,但是没有特殊硬件就无法实现。如果没有这种硬件设备,就无法使用该算法,NFU是对LRU的简单近似,并不很好。不过,老化技术是对LRU的很好的近似,而且可以有限的实现,是一种不错的选择。
最后两种算法都是用工作集。工作集算法具有相当好的性能,不过其实现的代价稍大。WSClock是一个变种,不仅有好的性能,而且可以有效实现。
总而言之,最好的两种算法就是老化和WSClock。它们分别基于LRU和工作集。两者都有好的分页性能,也都能有效地实现。还有几种其他的算法,不过这两个算法大概是十几种最重要的。
分页系统的设计问题
局部与全局分配策略(Local versus Global Allocation Policies)
局部算法实际上对应于为每个进程分配固定的内存片段;全局算法在可运行集成之间动态地分配页框。因此,分配给各个进程的页框数是随时间变化的
通常,全局算法工作得更好,特别是当工作集的大小随进程的生命期发生变化时尤甚
若使用局部算法,即使有大量的空闲页框存在,工作集的增长也会导致颠簸;如果工作集缩小了,局部算法将浪费内存
如果使用全局算法,系统必须不停地确定该给各个进程分配多少页框

局部和全局页面置换
(a)原始配置
(b)局部页面置换(选择本进程的页面A5置换)
(c)全局页面置换(选择内存中的页面B3置换)
如果采用全局算法,可以在每个进程启动时根据进城大小分配一定数目的页,不过在进程运行时该分配必须动态地更新。管理分配可以使用PFF(Page Fault Frequency,缺页率)算法。它告知什么时候增加或减少进程的页的分配,不过不涉及缺页时置换哪一页。他只是控制分配集的大小

缺页率是已指派的页框数的函数
负载控制(Load Control)
无论设计多么好,系统仍旧有可能崩溃
PFF算法表明:
- 某些进程需要更多的内存
- 但是没有进程会希望削减自己的没存占有量
解决方案:减少竞争内存的进程数目
- 将一个或多个进程交换到磁盘上,分割它们占有的页面
- 重新考虑多道程序的度
页面尺寸(Page Size)
确定最佳的页面尺寸需要在几个互相矛盾的因素之间平衡
一般情况下,最后一页中有一半是空的。该页中多余的空间就被浪费掉了,这种浪费称为内存碎片(internal fragmentation)
对于小页面:
- 小页面内部碎片少
- 适用于各种数据结构和代码块
- 内存中无用的程序少
缺点:
- 程序需要很多页面,页表大
- 载入页寄存器的时间长
代价取决于页表和内部碎片
式中:
- s=平均的进程大小(字节)
- p=页面大小(字节)
- e=页表项大小(字节)
最优情况满足

浙公网安备 33010602011771号