在 Linux 中调用 fork() 之后,两个进程(一个是另一个的子进程)将共享分配的堆内存。这些分配的页面被标记为 COW(写时复制),并且将保持共享状态,直到任一进程修改它们。此时,它们被复制,但引用它们的虚拟地址指针保持不变。 MMU(内存管理单元)如何区分两者呢?考虑以下:
- 进程 A 已启动
- 进程A分配了一个内存页,由虚拟地址0x1234指向
- 进程A fork()s,生成进程B
- 进程A和B现在共享虚拟地址0x1234,指向相同的物理内存位置
- 进程B修改其0x1234内存页
- 该内存页被复制然后修改
- 进程A和B都有虚拟地址0x1234,但这指向不同的物理内存地址
这如何区分呢?
答案1
内核在进程之间的上下文切换期间所做的事情之一是修改 MMU 表以删除描述前一个进程的地址空间的条目并添加描述下一个进程的地址空间的条目。根据处理器架构、内核以及可能的配置,这可以通过更改处理器寄存器或通过操作存储器中的页表来完成。
在 fork 操作之后,由于写时复制,两个进程的 MMU 表具有相同的物理地址,即虚拟地址 0x1234。同样,这是两个单独的表,对于这个特定的虚拟地址恰好有相同的条目。
该页的描述符具有只读属性。如果一个进程尝试写入(无论是 A 还是 B),都会因权限违规而触发处理器故障。内核的页面错误处理程序运行,分析情况并决定分配一个新的物理页面,将只读页面的内容复制到这个新页面,更改调用进程的MMU配置,以便0x1234现在指向这个新分配的物理页面具有读写属性的页面,并重新启动导致故障的指令上的调用进程。这次页面是可写的,因此指令不会陷入困境。
请注意,其他进程中的页面描述符不受此操作的影响。事实上,可能是这样,因为内核还执行一项操作:如果该页面现在仅映射到单个进程中,则它会切换回读写状态,以避免稍后复制它。
也可以看看页面错误后会发生什么?