news 2026/10/30 8:24:47

深挖 Linux 内存:为什么 fork 后的父子进程写同一个页,却没人能复用它?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深挖 Linux 内存:为什么 fork 后的父子进程写同一个页,却没人能复用它?

一、那个深深刻在脑海里的直觉模型

在学习 Linux 内核时,我们对 fork + COW(写时复制)的直觉通常是这样的:

  1. 父进程拥有一个物理页 P0,mapcount = 1。
  2. fork()发生:父子进程共享 P0,页面被设为只读,mapcount = 2。
  3. 关键点:谁先写,谁就触发 COW。比如父进程先写,它发现页面被共享,于是 copy 出一个新页 P1。
  4. 结局:此时 P0 的 mapcount 降回 1。等子进程再写时,发现 P0 已经是它独占了,直接执行Reuse(复用)。

一句话总结:一个 COW,一个 Reuse。听起来逻辑严密,教科书也是这么写的。但在 Linux 6.17 的真实环境下,实验结果却“打”了我的脸。

二、诡异的实验:谁也没占到便宜

实验环境:Qemu SMP + Linux 6.17。

应用测试代码:

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;

在内核源码 mm/memory.c 中加入打印调试,实际 log 输出如下:

p done, p=0x7fc1faa1a000 [do_wp_page 3930]: 1712 do_wp_page flag 1a55, addr 7fc1faa1a000 [do_wp_page 4020]: 1712 call wp_page_copy with mapcount 2, addr 7fc1faa1a000 [do_wp_page 3930]: 1713 do_wp_page flag 1a55, addr 7fc1faa1a000 [do_wp_page 4020]: 1713 call wp_page_copy with mapcount 2, addr 7fc1faa1a000

PID 1712(父进程)和 1713(子进程)全部触发了 copy。原本以为能被复用的原始页 P0,在 mapcount 归零后被系统回收了。

为什么?父子进程通过 barrier 同步写,难道不该是一个 copy、一个 reuse 吗?

三、内核深度解密:消失的“瞬间”

我们要去 mm/memory.c 的 do_wp_page 函数里找答案。内核判断是“拷贝”还是“复用”的逻辑核心如下:

1. 竞争的本质

先说清实验环境:QEMU 是 SMP,父子进程通过 barrier 同步写——两个写 fault 是在不同 CPU 上真正并行发生的,不存在谁先谁后的串行顺序。父进程触发写异常进入内核态后,在完成 P0 → P1 的拷贝并更新 PTE(页表项)之前,子进程已经在另一个 CPU 上触发了写异常。此时关键时刻到了:父进程的 COW 还没走完,PTE 还没改。

(顺带一提:即使在单核上,同样的窗口也存在——父进程在分配新页、拷贝页面时睡眠让出 CPU,子进程被调度运行后看到的 mapcount 同样是 2。SMP 的真并行只是让这个窗口更容易撞上,机制的判断点是同一个。)

子进程去看 P0 的 mapcount,依然是2。内核心想:“既然还有别人在用,那我也 copy 一个吧。”结果就是:大家都觉得自己不是最后一个使用者,大家都选择了“背锅”拷贝。

2. 时序决定命运

随后做了一组对比实验:在子进程写操作前加一个 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 [do_wp_page 3930]: 1906 do_wp_page flag 1a55, addr 7f1ed83ac000 [do_wp_page 4020]: 1906 call wp_page_copy with mapcount 2, addr 7f1ed83ac000 [do_wp_page 3930]: 1907 do_wp_page flag 1a55, addr 7f1ed83ac000 [do_wp_page 4004]: 1907 call wp_page_reuse with mapcount 1, addr 7f1ed83ac000

结果:父进程 COW,子进程 Reuse。实验验证了我的猜想:Reuse 成功的前提是,第一个进程的 COW 流程必须完全走完,把旧页的 mapcount 彻底降下来。

补一句严谨性说明:复用判断严格来说不只看 mapcount,还涉及 refcount——如果页面被 GUP 等机制 pin 住,即使 mapcount 降到 1,也可能被迫走 copy。本实验中没有这类额外引用,所以正文按 mapcount 视角简化。另外,“双 copy”是概率性现象,多次运行并非每次都发生,取决于两个进程 fault 交错的时机——这正是下文结论的由来。

3. 重复跑 500 次:概率长什么样

一次命中说明不了“racy”到底有多 racy。于是把三种模式各跑了 500 次:

模式双 copy单 copy + reusereuse 方
who=0 屏障并发(父子同时写)463(92.6%)37(7.4%)子 24 / 父 13
who=1 父先写(子 sleep 200ms)0(0%)500(100%)子 500
who=2 子先写(父 sleep 200ms)1(0.2%)499(99.8%)父 499

三点值得说明:

  1. 屏障并发下,双 copy 不是“偶尔撞上”,而是默认结局(92.6%)。竞争窗口比直觉大得多——只要两个 fault 交错进同一个窗口,基本就是双 copy。
  2. 拉开 200ms 后,基本 100% 是单 copy + reuse,且 reuse 方总是后写的一方。对称性验证了上一节的解释:谁先走完 COW,谁就把 mapcount 降下来,后写的一方正好赶上 reuse。
  3. who=2 里那 1 次例外如实保留。200ms 间隔下仍有 1/500 双 copy,原因未深究(可能是调度噪声)。

四、总结

  1. Linux 的 COW 是“基于瞬时状态判断的竞争式实现(racy but correct)”。fork 后是发生 1 次还是 2 次 COW,是实现细节,与调度有关。重复跑的数据也印证了这一点:屏障并发下双 copy 占 92.6%,拉开 200ms 后基本 100% 是单 copy + reuse。
  2. 内核的正确性 vs 性能:内核首先保证的是数据的独立(Correctness),至于能不能 Reuse 节省那一页内存,取决于调度器脸色。

有时候我们请教前辈/大牛问题,他们有时候会说 “hmm, it's hard to say...”。当时不清楚为什么这么难说,现在想想,有些问题有时候确实还挺难说的……

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/30 8:21:45

在 SAP S/4HANA 中用 Email Template、CL_BCS 与 CID 内嵌图片发送品牌化 HTML 邮件

企业项目里有一类需求很常见,业务事件发生以后,SAP 系统需要给客户、供应商或者内部员工发送一封邮件。技术上看,这件事似乎很简单,调用 CL_BCS,准备 Subject 和 Body,再执行 SEND 就结束了。 真正落到企业场景里,事情往往没这么简单。 业务部门通常不会满足于一封纯文…

作者头像 李华
网站建设 2026/10/30 8:21:27

AI 外呼系统情绪识别功能选型避坑指南

选型 AI 外呼系统的情绪识别功能,最容易被 demo 里"客户一怒,系统秒切安抚话术"的演示迷住。但真实场景里,一台成熟外呼系统的接通率要做到 56% 以上、答准率 99%、识破率低于 0.1%,情绪识别才有可信的数据基础——一旦…

作者头像 李华
网站建设 2026/10/30 8:21:18

小白程序员必学:用Prompt Engineering让AI瞬间变身“数字战友”

本文通过“超级天才”比喻解析Prompt Engineering的核心逻辑,即如何将模糊需求转化为AI可执行的高确定性指令。文章对比了坏提示词(意识流提问)与好提示词(结构化任务清单)的输出差异,并给出三套实用公式&a…

作者头像 李华
网站建设 2026/10/30 8:17:23

显存优化不是万能药:4 步加速版在 8G 显卡上翻车的那些瞬间

显存优化不是万能药:4 步加速版在 8G 显卡上翻车的那些瞬间 最近半年,AI 画图圈最不缺的就是“8G 显卡也能跑”的传说。 Qwen-Image 2.1 系列一出,各种加速版、4 步版、6 步版、量化版像下饺子一样往外冒。社区里随手一刷,就是“显…

作者头像 李华
网站建设 2026/10/30 8:09:25

N_m3u8DL-RE:三条命令完成 m3u8 下载与直播录制

N_m3u8DL-RE:三条命令完成 m3u8 下载与直播录制 【免费下载链接】N_m3u8DL-RE Cross-Platform, modern and powerful stream downloader for MPD/M3U8/ISM. English/简体中文/繁體中文. 项目地址: https://gitcode.com/GitHub_Trending/nm3/N_m3u8DL-RE 你手…

作者头像 李华