缺页异常
复习
- 地址转换:沿着一个虚拟地址得到最终的物理地址
- TLB:给频繁的地址转换加一层缓存
- 多级页表:页表本身太大时如何按需展开
TL;DR
- 页表项的有效位为 0,说明这一页当前不在内存
- 访问它会触发缺页异常,CPU 转入内核
- 内核把所需页面从磁盘搬进内存,更新页表
- 处理完,重新执行刚才那条指令,程序毫无察觉
正文
前面默认了一件事:页表里的每一页,都已经在物理内存里了。可内存哪装得下所有页?当访问的页其实不在内存时,怎么办?
这就是缺页异常(page fault)要处理的事。
有效位与缺页
回忆页表项里的有效位:它标记“这一页当前在不在内存”。
- 有效位为 1:这一页在内存里,正常转换
- 有效位为 0:这一页不在内存(可能是首次访问,或已被挪去了磁盘)
当 MMU 发现要访问的页有效位为 0,它没法完成转换,于是触发一个缺页异常,把控制权交给内核。
内核来救场
内核接到这个异常,先判断:这是一次“正常的缺失”,还是程序真的访问了非法地址?
- 如果是合法地址,只是页不在内存:内核去磁盘上找到这一页,读进一个空闲页帧,更新页表(把有效位改成 1),然后返回
- 如果是非法访问(地址根本没分配给这个进程):那就不是“缺页”,而是真正的错误,内核通常会结束这个进程(就是你见过的“段错误”)
访问一页
│ 有效位 = 1 ──> 照常转换
└ 有效位 = 0 ──> 缺页异常 ──> 内核:
合法?──> 从磁盘调页,更新页表,重试
非法?──> 结束进程
一个精巧的地方
注意最后那句“重新执行刚才那条指令”。缺页处理完之后,内核并不是跳到下一条指令,而是回到触发异常的那一条,让它再执行一次。
这一次,页已经在内存里、页表也更新了,指令就能顺利完成。对程序来说,它只是“稍微卡了一下”,完全不知道自己曾被中断、还惊动了内核和磁盘。
这正是“让程序感觉内存无限大”的关键一步:用不到的页可以不在内存,需要时再临时搬进来。 下一章,我们把这件事讲得更完整。
思考题
缺页异常和“访问了非法地址”都会让 CPU 转入内核,内核怎么区分这两种情况?为什么前者要“重新执行指令”,后者却要结束进程?
小结
知识点
- 有效位标记页面是否在内存
- 有效位为 0 时访问,触发缺页异常
- 内核区分“合法缺页”与“非法访问”
- 合法缺页:调页入内存、更新页表、重试指令
参考资料
- Wikipedia(zh):缺页错误:page fault
- Wikipedia(zh):虚拟内存:按需分页
思考题答案(仅供参考)
内核通过“这个地址是否属于该进程合法的地址空间”来区分:如果合法,只是页面暂时不在内存,就是正常的缺页;如果根本不属于它,就是非法访问。前者要“重新执行指令”,因为这次执行失败了,但页补进来之后同一条指令就能成功,程序应当继续往下走;后者则无法补救——这个访问本身就不该发生,继续执行只会越来越错,所以通常直接结束进程。一个是“暂时缺东西”,一个是“根本不该碰”,处理方式自然不同。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪