本文为学习笔记,原文链接为:time.geekbang.org/column/arti…
页中断有哪些类型?
页中断有两类重要的类型:写保护中断和缺页中断。正是这两类中断在整个系统的后台默默地工作着,就像守护神一样支撑着内存系统正常工作。
如果物理页不在内存中,或者页表未映射,或者读写请求不满足页表项内的权限定义时,MMU 单元就会产生一次中断。
根据中断来源的不同,页中断大致可以分为以下几种类型:
fork 原理:写保护中断与写时复制
操作系统为每个进程提供了一个进程管理的结构,在偏理论的书籍里一般会称它为进程控制块(Process Control Block,PCB)。具体到 Linux 系统上,PCB 就是 task_struct 这个结构体。它里面记录了进程的页表基址,打开文件列表、信号、时间片、调度参数和线性空间已经分配的内存区域等等数据。
在操作系统内核里,fork 的第一个动作是把 PCB 复制一份,但类似于物理页等进程资源不会被复制。
fork 的第二个动作是复制页表和 PCB 中的 vma 数组,并把所有当前正常状态的数据段、堆和栈空间的虚拟内存页,设置为不可写,然后把已经映射的物理页面的引用计数加 1。
父子进程的页表情况如图所示:
不管是父进程还是子进程,它们接下来都有可能发生写操作,但我们知道在 fork 的第二步操作中,已经将所有原来可写的地方都变成不可写了,所以这时必然会发生写保护中断。
系统会首先判断发生中断的虚拟地址所对应的物理地址的引用计数,如果大于 1,就说明现在存在多个进程共享这一块物理页面,那么它就需要为发生中断的进程再分配一个物理页面,把老的页面内容拷贝进这个新的物理页,最后把发生中断的虚拟地址映射到新的物理页。这就完成了一次写时复制 (Copy On Write, COW)。
当子进程发生写保护中断后,系统就会为它分配新的物理页,然后复制页面,再修改页表映射。这时老的物理页的引用计数就变为 1,同时子进程中的 PTE 的权限也从只读变为读写。
当父进程再访问到这个地址时,也会触发一次写保护中断,这时系统发现物理页的引用计数为 1,那就只要把父进程 PTE 中的权限,简单地从只读变为读写就可以了。
如下图所示:
mmap 强大的能力是怎么来的?
mmap 根据映射的类型,有四种最常用的组合:
- 私有匿名映射,用于分配堆空间;
- 共享匿名映射,用于父子进程之间通讯;
- 私有文件映射,用于加载动态链接库;
- 共享文件映射,用于多进程之间通讯。
而这四个组合的作用:
- 私有匿名映射,在缺页中断的处理过程,会通过 do_anonymous_page 函数申请一块全零的物理页,并建立虚拟地址到物理页的映射,以达成分配内存的目标;
- 私有文件映射,则借助文件的 inode 结构共享文件的物理缓存页,当发生写操作时,则会出现写时复制,从而保证每一个进程中都有自己的副本;
- 共享文件映射,在私有文件映射的基础上,只取消了写时复制,这样一个进程就可以看到其他进程对这个页的修改了;
- 共享匿名映射,借助了虚拟文件系统。内核在父子进程间,使用自己创建的虚拟文件和共享文件映射,来实现共享匿名映射。
所以,mmap 的功能之所以十分强大,主是因为操作系统综合使用写保护中断、缺页中断和文件机制来实现 mmap 的各种功能。
引用
本文内容来自极客时间《编程高手必学的内存知识》第10讲,原文链接为:time.geekbang.org/column/arti…