Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

页面置换

复习

  • 多级页表:页表本身太大时如何按需展开
  • 缺页异常:访问的页面不在内存时 CPU 和内核怎样配合
  • 按需加载与交换空间:只把眼下需要的页面放进内存

TL;DR

  • 内存装满了,就得挑一页换出,给新页腾地方
  • FIFO 简单,但可能出现“多加内存反而更差”的异常
  • LRU 符合局部性、效果好,却很难精确实现
  • 现实中常用时钟算法等近似方案,用一点精度换效率

正文

  内存满了,现在要读入一个新页,就得先请走一页。请谁走?这就是页面置换(page replacement)要回答的问题。

  选得好,程序流畅;选得不好,刚请走的页马上又要用,又得换回来,来回折腾。

先来先出

  最省事的策略是 FIFO(先进先出):谁最早进来,就请谁走。

  它实现简单——维护一个队列就行。但它不看“这页最近有没有被用”,于是可能犯傻:把一页天天要用的页,仅仅因为它“资格老”就请出去,很快又得换回来。

  更怪的是,FIFO 会出现 Belady 异常:给它更多内存,缺页次数反而可能变多。一个置换算法竟然会“越给越差”,听起来就不合理。

最近最少使用

  合乎直觉的策略是 LRU(Least Recently Used,最近最少使用):请走最久没被访问的那一页。

  它正好贴合局部性——最近用过的,多半马上还要用;最久没碰的,多半暂时不需要。理论上,LRU 表现很好,接近最优。

  可它有个大难题:要精确知道“谁最久没用”,就得记录每一页的访问时间。页一多,这套记录的代价高得离谱,硬件也很难高效支持。理想很美好,现实难落地。

近似:时钟算法

  于是现实里退一步,用近似 LRU 的方案。最常见的是时钟算法(clock)。

  它给每一页一个“访问位”,并把这些页想成一个环:

  • 需要换页时,指针沿着环扫
  • 扫到的页,如果访问位是 1,说明最近用过,就把它清成 0,指针继续走
  • 扫到的页,如果访问位是 0,说明这一轮下来都没被用过,就换它出去
        ┌───┐
   ┌──> │页A│ 访问位1 → 清0,继续
   │    └───┘
   │    ┌───┐
   │    │页B│ 访问位0 → 换出它
   │    └───┘
   └── 指针

  它只用一位标志,就能大致实现“最近用过的先留着”,代价很小,效果不错——所以被许多真实系统采用。

  到这里,“换哪页”有了办法。可如果整个系统都在疯狂换页,连局部性都保不住,又会怎样?下一章我们看这种叫“抖动”的病。

思考题

  FIFO 会出现“内存越多、缺页反而越多”的怪事(Belady 异常)。这说明“命中率”和“内存大小”之间一定是正相关吗?

小结

知识点

  • 页面置换:内存满时决定换出哪一页
  • FIFO:简单,但可能出现 Belady 异常
  • LRU:符合局部性,但难以精确实现
  • 时钟算法:近似 LRU,实用且开销小

参考资料

  1. Wikipedia(zh):页面置换算法:置换算法
  2. Wikipedia(zh):Belady 异常:Belady’s anomaly

思考题答案(仅供参考)

  不一定。Belady 异常就是反例:对某些算法(如 FIFO),内存增大后缺页反而增多。这说明“命中率”不只取决于内存大小,还取决于访问序列置换策略的配合——算法是否利用了访问规律很关键。换句话说,内存多当然有帮助,但不是无条件的;一个不聪明的算法,可能把多出来的内存用得比原来还糟。所以置換算法才值得反复研究。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪