操作系统内存最全解析!!!(13)

操作系统内存最全解析!!!

文章插图
 
当缺页错误出现时,算法首先检查表针指向的页面,如果它的 R 位是 0 就淘汰该页面,并把新的页面插入到这个位置,然后把表针向前移动一位;如果 R 位是 1 就清除 R 位并把表针前移一个位置 。重复这个过程直到找到了一个 R 位为 0 的页面位置 。了解这个算法的工作方式,就明白为什么它被称为 时钟(clokc)算法了 。
最近最少使用页面置换算法最近最少使用页面置换算法的一个解释会是下面这样:在前面几条指令中频繁使用的页面和可能在后面的几条指令中被使用 。反过来说,已经很久没有使用的页面有可能在未来一段时间内仍不会被使用 。这个思想揭示了一个可以实现的算法:在缺页中断时,置换未使用时间最长的页面 。这个策略称为 LRU(Least Recently Used) ,最近最少使用页面置换算法 。
虽然 LRU 在理论上是可以实现的,但是从长远看来代价比较高 。为了完全实现 LRU,会在内存中维护一个所有页面的链表,最频繁使用的页位于表头,最近最少使用的页位于表尾 。困难的是在每次内存引用时更新整个链表 。在链表中找到一个页面,删除它,然后把它移动到表头是一个非常耗时的操作,即使使用硬件来实现也是一样的费时 。
然而,还有其他方法可以通过硬件实现 LRU 。让我们首先考虑最简单的方式 。这个方法要求硬件有一个 64 位的计数器,它在每条指令执行完成后自动加 1,每个页表必须有一个足够容纳这个计数器值的域 。在每次访问内存后,将当前的值保存到被访问页面的页表项中 。一旦发生缺页异常,操作系统就检查所有页表项中计数器的值,找到值最小的一个页面,这个页面就是最少使用的页面 。
用软件模拟 LRU尽管上面的 LRU 算法在原则上是可以实现的,但是很少有机器能够拥有那些特殊的硬件 。上面是硬件的实现方式,那么现在考虑要用软件来实现 LRU。一种可以实现的方案是 NFU(Not Frequently Used,最不常用)算法 。它需要一个软件计数器来和每个页面关联,初始化的时候是 0。在每个时钟中断时,操作系统会浏览内存中的所有页,会将每个页面的 R 位(0 或 1)加到它的计数器上 。这个计数器大体上跟踪了各个页面访问的频繁程度 。当缺页异常出现时,则置换计数器值最小的页面 。
NFU 最主要的问题是它不会忘记任何东西,想一下是不是这样?例如,在一个多次(扫描)的编译器中,在第一遍扫描中频繁使用的页面会在后续的扫描中也有较高的计数 。事实上,如果第一次扫描的执行时间恰好是各次扫描中最长的,那么后续遍历的页面的统计次数总会比第一次页面的统计次数小 。结果是操作系统将置换有用的页面而不是不再使用的页面 。
幸运的是只需要对 NFU 做一个简单的修改就可以让它模拟 LRU,这个修改有两个步骤
  • 首先,在 R 位被添加进来之前先把计数器右移一位;
  • 第二步,R 位被添加到最左边的位而不是最右边的位 。
修改以后的算法称为 老化(aging) 算法,下图解释了老化算法是如何工作的 。
操作系统内存最全解析!!!

文章插图
 
我们假设在第一个时钟周期内页面 0 - 5 的 R 位依次是 1,0,1,0,1,1,(也就是页面 0 是 1,页面 1 是 0,页面 2 是 1 这样类推) 。也就是说,在 0 个时钟周期到 1 个时钟周期之间,0,2,4,5 都被引用了,从而把它们的 R 位设置为 1,剩下的设置为 0。在相关的六个计数器被右移之后 R 位被添加到 左侧 ,就像上图中的 a 。剩下的四列显示了接下来的四个时钟周期内的六个计数器变化 。
CPU正在以某个频率前进,该频率的周期称为时钟滴答或时钟周期 。一个 100Mhz 的处理器每秒将接收100,000,000个时钟滴答 。
当缺页异常出现时,将置换(就是移除)计数器值最小的页面 。如果一个页面在前面 4 个时钟周期内都没有被访问过,那么它的计数器应该会有四个连续的 0 ,因此它的值肯定要比前面 3 个时钟周期内都没有被访问过的页面的计数器小 。
这个算法与 LRU 算法有两个重要的区别:看一下上图中的 e,第三列和第五列
操作系统内存最全解析!!!

文章插图
 
它们在两个时钟周期内都没有被访问过,在此之前的时钟周期内都引用了两个页面 。根据 LRU 算法,如果需要置换的话,那么应该在这两个页面中选择一个 。那么问题来了,我萌应该选择哪个?现在的问题是我们不知道时钟周期 1 到时钟周期 2 内它们中哪个页面是后被访问到的 。因为在每个时钟周期内只记录了一位,所以无法区分在一个时钟周期内哪个页面最早被引用,哪个页面是最后被引用的 。因此,我们能做的就是置换页面3,因为页面 3 在周期 0 - 1 内都没有被访问过,而页面 5 却被引用过 。


推荐阅读