从第一性原理理解写时复制COW
写时复制(Copy-on-Write,COW)是 Linux 进程创建和内存管理的核心机制,理解它得先搞清楚进程创建时面临的根本矛盾是什么。(这也是 Zygote 的底层核心原理,同时也是 Flutter 单 SO 优化方案背后的依仗。) 第一性:进程创建面临什么根本矛盾 问题 描述 创建进程需要复
写时复制(Copy-on-Write,COW)是 Linux 进程创建和内存管理的核心机制,理解它得先搞清楚进程创建时面临的根本矛盾是什么。(这也是 Zygote 的底层核心原理,同时也是 Flutter 单 SO 优化方案背后的依仗。)

第一性:进程创建面临什么根本矛盾
| 问题 | 描述 |
|---|---|
| 创建进程需要复制内存 | fork() 创建子进程时,理论上需要把父进程的整个地址空间完整复制一份 |
| 内存复制太慢 | 一个进程可能有几百 MB 甚至几 GB 内存,完整复制需要几十毫秒到几秒 |
| 复制的内容可能根本用不上 | 很多内存只是被读取,不会被修改,白白复制就是浪费 |
| 内存浪费 | 父子进程各自持有一份相同的数据,物理内存直接翻倍 |
如果没有 COW,会是什么样子?
父进程(1GB 内存)
│
▼ fork()
子进程(1GB 内存)── 完全独立的 1GB 物理内存
结果:2GB 物理内存被占用,但内容一模一样
本质:延迟复制 + 共享只读
COW 的思想其实极其简单:
┌─────────────────────────────────────────┐
│ 父进程虚拟地址空间 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 页 A │ │ 页 B │ │ 页 C │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
└───────┼────────────┼────────────┼──────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────┐
│ 页表(复制) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 映射关系 │ │ 映射关系 │ │ 映射关系 │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
└───────┼────────────┼────────────┼──────┘
│ │ │
└────────────┴────────────┘
│
▼
┌─────────────────────────┐
│ 物理内存(共享) │
│ ┌─────┐┌─────┐┌─────┐ │
│ │ 页A ││ 页B ││ 页C │ │ ◄── 只读,标记为共享
│ └─────┘└─────┘└─────┘ │
└─────────────────────────┘
fork 瞬间发生了什么?
- 虚拟地址空间:复制了(页表复制)
- 物理内存:没有复制,父子指向同一块物理页
- 页表权限:标记为只读
写时复制触发过程
步骤 1:fork 完成
父进程 ──► 共享物理页 ◄── 子进程
(只读标记)
步骤 2:子进程尝试修改页 B
│
▼ 触发页错误(Page Fault)
因为页 B 是只读的,但子进程要写入
│
▼ 操作系统介入
1. 分配新的物理页 B'
2. 把原页 B 的内容复制到 B'
3. 子进程的页表指向 B'(可写)
4. 父进程的页表仍指向 B(可写)
步骤 3:结果
父进程 ──► 页A ──► 物理页A(共享)
──► 页B ──► 物理页B(独有)
子进程 ──► 页A ──► 物理页A(共享)
──► 页B ──► 物理页B'(独有,刚复制)
解决的问题
| 问题 | COW 如何解决 |
|---|---|
| fork 太慢 | 不复制物理内存,fork 瞬间完成(微秒级) |
| 内存浪费 | 只读数据共享,物理内存只存一份 |
| 不必要的复制 | 没被修改的页永远不会复制 |
| 父子进程隔离 | 修改时自动复制,保证各自独立 |
核心场景
1. fork() 创建进程(最典型的场景)
// Linux 中 fork 的 COW 实现
pid_t pid = fork();
if (pid == 0) {
// 子进程
// 此时和父进程共享所有物理页
*ptr = 100; // 修改这一页时才触发复制
}
效果:
- 一个 1GB 的进程 fork 后,如果不修改任何数据,物理内存只增加几 KB(页表开销)
- 修改 10% 的数据,物理内存只增加 100MB
2. 文件映射(mmap)
// 多个进程 mmap 同一个文件
void* addr1 = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
void* addr2 = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
效果:
- 两个进程共享同一份文件内容的物理页
- 都不修改:内存中只有一份
- 某个进程修改(MAP_PRIVATE):触发 COW,复制一份给修改者
3. Android Zygote(我们刚聊的)
Zygote 预加载 Framework(只读)
│
▼ fork 出微信
共享 Framework 物理页
│
▼ 微信加载自己的代码
修改自己的页,不影响 Zygote 和其他 App
效果:
- 100 个 App 共享同一份 Framework 内存
- 物理内存节省数百 MB
4. 字符串/数据结构 copy(编程语言层面)
// C++ std::string 的 COW 实现(C++11 前常见)
std::string s1 = "hello world"; // 分配内存
std::string s2 = s1; // 不复制,共享引用计数
s2[0] = 'H'; // 此时才触发 COW,复制一份给 s2
注意: C++11 后 string 不再用 COW(因为多线程问题),但其他数据结构仍可用。
为什么 COW 能工作:基于一个关键假设
如果这个假设不成立——比如 fork 后子进程立即修改所有页,COW 反而更慢:
- 正常复制:一次性 memcpy,缓存友好
- COW:逐页触发页错误,逐页复制,缺页中断开销叠加
所以 COW 不是万能优化,它的收益取决于“共享后修改的比例”。
一句话总结
具体总结
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
Python for S60零基础入门指南与快速上手教程
PythonforS60是标准Python在诺基亚SymbianS60系统上的移植版本,旨在为移动开发者提供易学的脚本环境,以快速创建应用。它通过专用API访问摄像头、GPS等硬件,实现交互功能,显著降低了当时移动开发的门槛。尽管受限于性能、平台依赖而未能长久,但其推动移动开发便捷化的理念,在技术史上仍具启示意义。
Ubuntu LAMP支持哪些PHP版本
Ubuntu不同LTS版本默认捆绑对应PHP版本,如22 04自带PHP8 1。若需其他版本,可通过OndřejSurý的PPA安装PHP5 6至8 2等版本,注意该PPA需手动添加并关注安全性。多版本共存时,可用update-alternatives工具切换默认PHP版本,并设置优先级顺序。建议定期更新PPA源以确保兼容性。
Ubuntu Node.js性能监控工具推荐
Ubuntu环境下Node js性能监控工具包括:PM2用于进程管理与实时监控;Prometheus与Grafana组合实现灵活的数据采集与可视化;NewRelic提供企业级全栈APM;Datadog支持云原生多环境监控;Easy-Monitor侧重故障定位与内存泄漏分析;Clinic js用于深度性能诊断;Heapdump与0x分别进行内存分析和火焰图生成
yum查看已安装软件列表方法
通过yumlistinstalled命令可列出所有已安装软件包及版本信息,配合grep过滤可查询特定软件;使用yuminfo可查看版本、仓库、描述等详细内容,是RHEL CentOS系统包管理常用操作。
Node.js日志归档最佳实践
Node js日志归档可用winston库实现日志分割、压缩、按天轮转及保留天数控制;也可用fs模块手工实现,但需自行处理日期比较与文件重命名,且易出错。生产环境推荐使用winston等成熟方案,稳定可靠。
- 热门数据榜
相关攻略
2026-07-18 22:34
2026-07-18 22:33
2026-07-18 22:33
2026-07-18 22:33
2026-07-18 22:33
2026-07-18 22:10
2026-07-18 22:10
2026-07-18 22:09
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程

