Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

缺页异常

复习

  • 地址转换:沿着一个虚拟地址得到最终的物理地址
  • TLB:给频繁的地址转换加一层缓存
  • 多级页表:页表本身太大时如何按需展开

TL;DR

  • 页表项的有效位为 0,说明这一页当前不在内存
  • 访问它会触发缺页异常,CPU 转入内核
  • 内核把所需页面从磁盘搬进内存,更新页表
  • 处理完,重新执行刚才那条指令,程序毫无察觉

正文

  前面默认了一件事:页表里的每一页,都已经在物理内存里了。可内存哪装得下所有页?当访问的页其实不在内存时,怎么办?

  这就是缺页异常(page fault)要处理的事。

有效位与缺页

  回忆页表项里的有效位:它标记“这一页当前在不在内存”。

  • 有效位为 1:这一页在内存里,正常转换
  • 有效位为 0:这一页不在内存(可能是首次访问,或已被挪去了磁盘)

  当 MMU 发现要访问的页有效位为 0,它没法完成转换,于是触发一个缺页异常,把控制权交给内核。

内核来救场

  内核接到这个异常,先判断:这是一次“正常的缺失”,还是程序真的访问了非法地址?

  • 如果是合法地址,只是页不在内存:内核去磁盘上找到这一页,读进一个空闲页帧,更新页表(把有效位改成 1),然后返回
  • 如果是非法访问(地址根本没分配给这个进程):那就不是“缺页”,而是真正的错误,内核通常会结束这个进程(就是你见过的“段错误”)
访问一页
  │ 有效位 = 1 ──> 照常转换
  └ 有效位 = 0 ──> 缺页异常 ──> 内核:
                                合法?──> 从磁盘调页,更新页表,重试
                                非法?──> 结束进程

一个精巧的地方

  注意最后那句“重新执行刚才那条指令”。缺页处理完之后,内核并不是跳到下一条指令,而是回到触发异常的那一条,让它再执行一次。

  这一次,页已经在内存里、页表也更新了,指令就能顺利完成。对程序来说,它只是“稍微卡了一下”,完全不知道自己曾被中断、还惊动了内核和磁盘。

  这正是“让程序感觉内存无限大”的关键一步:用不到的页可以不在内存,需要时再临时搬进来。 下一章,我们把这件事讲得更完整。

思考题

  缺页异常和“访问了非法地址”都会让 CPU 转入内核,内核怎么区分这两种情况?为什么前者要“重新执行指令”,后者却要结束进程?

小结

知识点

  • 有效位标记页面是否在内存
  • 有效位为 0 时访问,触发缺页异常
  • 内核区分“合法缺页”与“非法访问”
  • 合法缺页:调页入内存、更新页表、重试指令

参考资料

  1. Wikipedia(zh):缺页错误:page fault
  2. Wikipedia(zh):虚拟内存:按需分页

思考题答案(仅供参考)

  内核通过“这个地址是否属于该进程合法的地址空间”来区分:如果合法,只是页面暂时不在内存,就是正常的缺页;如果根本不属于它,就是非法访问。前者要“重新执行指令”,因为这次执行失败了,但页补进来之后同一条指令就能成功,程序应当继续往下走;后者则无法补救——这个访问本身就不该发生,继续执行只会越来越错,所以通常直接结束进程。一个是“暂时缺东西”,一个是“根本不该碰”,处理方式自然不同。

协议

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

封面图

设计师 | 南国微雪