我知道分叉进程会映射父进程的内存并在写入时复制它。它是只复制需要写入的内容还是复制整个映射内存?
答案1
简短回答: 两者都不。内核复制整页。页面的大小取决于您使用的体系结构(当今的体系结构通常支持多种页面大小)。如果您在 x86 64 位架构上运行,则最有可能的大小是 4KiB。
更长的答案:内核和硬件虚拟内存系统通过称为页的片来处理内存。例如,x86 64 位架构上的默认页面大小为 4KiB。
期间fork()
大部分父内存空间是与子进程共享的。该内存空间由页组成。每个共享页在 期间被内核标记为只读fork()
。
后fork()
完成任何一个父级或子级尝试修改内存,硬件会生成页面错误,因为该页面被标记为只读。页面错误是陷入内核的CPU异常(即当前程序执行停止并且CPU开始执行内核中预定义的异常处理程序)。
然后内核确定这是写复制错误并分配一个新页面。该页被标记为读/写,前一页的内容被复制到其上。然后内核将其映射到内存空间。然后在捕获指令处恢复进程执行:再次执行触发故障的指令。这次该页被标记为读/写,因此指令完成而不会出现错误。
每次子级或父级写入共享页面时,都会重复此过程。页面发生 CoW 错误后,通常在“其他”进程中仍将其标记为只读(这取决于内核实现)。如果该进程尝试对其进行写入,则会像以前一样生成错误。但内核会注意到该页面不再共享,并简单地将其标记为读/写。
答案2
1988 年 SunOS-4.0 引入了现代mmap()
实现。所有现代操作系统都重新实现了 SunOS-4.0 中的这一概念。从那时起,fork()
调用通常是写时复制变体。
如果您写入由父进程以共享方式映射的访问内存,则该内存在子进程中仍然是共享的。如果您写入访问私有内存,当您尝试修改它时,它将被私有副本替换。