fork + COW(写时复制)的直觉通常是这样的:父进程拥有一个物理页 P0,mapcount = 1。
fork() 发生:父子进程共享 P0,页面被设为只读,mapcount = 2。
关键点: 谁先写,谁就触发 COW。比如父进程先写,它发现页面被共享,于是 copy 出一个新页 P1。
结局: 此时 P0 的 mapcount 降回 1。等子进程再写时,发现 P0 已经是它独占了,直接执行 Reuse(复用)。
一句话总结:一个 COW,一个 Reuse。 听起来逻辑严密,教科书也是这么写的。但在 Linux 6.17 的真实环境下,实验结果却“打”了我的脸。
二、 诡异的实验:谁也没占到便宜
环境环境:Qemu + Linux 6.17 ,参考‘深入理解 Linux 内核:匿名页缺页中断处理机制(do_anonymous_page)’。
char *p = mmap(NULL, PAGE,PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS,-1, 0);p[0] = 1;pid_t pid = fork();p[0] = 2;

PID 1712: mmap done, p=0x7fc1faa1a000...[ 525.906058] [do_wp_page 3930]: 1712 do_wp_page flag 1a55, addr 7fc1faa1a000[ 525.909179] [do_wp_page 4020]: 1712 call wp_page_copy with mapcount 2, addr 7fc1faa1a000[ 525.913167] [do_wp_page 3930]: 1713 do_wp_page flag 1a55, addr 7fc1faa1a000[ 525.916198] [do_wp_page 4020]: 1713 call wp_page_copy with mapcount 2, addr 7fc1faa1a000[ 525.920047] [do_wp_page 3930]: 1712 do_wp_page flag 1a55, addr 7fc1fa836000
PID 1712(父进程)和 1713(子进程)全部触发了 copy。原本以为能被复用的原始页 P0,在 mapcount 归零后被系统回收了。
为什么? 既然是单核,难道没有先后顺序吗?
三、内核深度解密:消失的“瞬间”
我们要去 mm/memory.c 的 do_wp_page 函数里找答案。内核判断是“拷贝”还是“复用”的逻辑核心如下:
即使是单核,父进程在触发写异常进入内核态后,如果在完成 P0→P1 的拷贝并更新 PTE(页表项)之前,由于某种原因(如时间片耗尽或抢占)切换到了子进程,子进程也触发写异常。
此时,关键时刻到了:
父进程的 COW 还没走完,PTE 还没改。
子进程去看 P0 的 mapcount,依然是 2。
内核心想:“既然还有别人在用,那我也 copy 一个吧。”
结果就是:大家都觉得自己不是最后一个使用者,大家都选择了“背锅”拷贝。
随后做了一组对比实验:在子进程写操作前加一个 sleep(1),强制拉开两者的距离。
char *p = mmap(NULL, PAGE,PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS,-1, 0);p[0] = 1;pid_t pid = fork();if (pid == 0) {/* child */sleep(1);p[0] = 2;exit(0);} else {/* parent */p[0] = 3;wait(NULL);}
PID 1906: mmap done, p=0x7f1ed83ac000......[ 5826.114947] [do_wp_page 3930]: 1906 do_wp_page flag 1a55, addr 7f1ed83ac000[ 5826.115752] [do_wp_page 4020]: 1906 call wp_page_copy with mapcount 2, addr 7f1ed83ac000[ 5827.120510] [do_wp_page 3930]: 1907 do_wp_page flag 1a55, addr 7f1ed83ac000[ 5827.123148] [do_wp_page 4004]: 1907 call wp_page_reuse with mapcount 1, addr 7f1ed83ac000
结果:父进程 COW,子进程 Reuse。 实验验证了我的猜想:Reuse 成功的前提是,第一个进程的 COW 流程必须完全走完,把旧页的 mapcount 彻底降下来。