核心思想
fork 创建子进程时,不拷贝物理内存,父子进程虚拟页映射到同一份物理页;仅当任意进程尝试写入页面时,才复制对应的物理页。
页面分类与 fork 处理
.text、.rodata:本身 PTE 标记只读,不参与 COW。父子共享物理页;尝试写直接触发段错误 SIGSEGV。.data、.bss、堆、用户栈:属于可写页面。fork 时,内核批量修改父子双方对应的 PTE:R/W 置 0(临时只读陷阱),物理页引用计数 + 1,开启 COW。
COW 触发流程
父 / 子进程写页面,MMU 检查 PTE,R/W=0,触发写保护异常。
内核读取该物理页的引用计数:
引用计数 > 1:还有多个进程共享此页。分配新物理页,把旧页内容拷贝到新页;修改当前进程 PTE,指向新页,R/W 改为可写;旧页面保留给另一个进程。
引用计数 = 1:只剩当前进程使用该物理页,无需拷贝页面,直接把当前 PTE 的 R/W 改成可写。
程序恢复执行。
关键细节
权限标记全部存放在 PTE,物理内存条本身没有读写权限。
.text/.rodata 的只读:永久业务只读,写直接段错误。
COW 的只读:临时陷阱,目的捕获写操作,页面本身支持读写。
TLB 会缓存 PTE 权限位;修改 PTE 之后,必须失效对应 TLB 条目,避免旧缓存干扰。
COW 复制粒度:以单个页(4KB)为单位,不是一次性复制整个进程内存。
总结
fork 只复制页表,共享物理内存;可写页面 PTE 临时设为只读捕获写操作。写页面触发异常,内核依据物理页引用计数判断:多进程共享则复制页面;单独持有则直接放开写权限。只读代码段不参与写时复制。