Linux 性能实战:为什么使用写入时复制(COW)机制
Linux 内存管理中的写入时复制(COW)机制允许进程间共享只读内存页,仅在写入时触发物理页复制。动态库加载时,代码段和常量段被多个进程共享,而可写数据段初始共享,修改后私有化。这种延迟分配策略大幅降低了多进程环境下的内存消耗,使 fork 操作更高效,提升了系统整体内存利用率。其核心设计原则为“先共享,按需复制”,通过页面错误自动驱动资源分配。

Linux 内存管理中的写入时复制(COW)机制允许进程间共享只读内存页,仅在写入时触发物理页复制。动态库加载时,代码段和常量段被多个进程共享,而可写数据段初始共享,修改后私有化。这种延迟分配策略大幅降低了多进程环境下的内存消耗,使 fork 操作更高效,提升了系统整体内存利用率。其核心设计原则为“先共享,按需复制”,通过页面错误自动驱动资源分配。

在分析 Linux 内存时,很多工程师都会看到类似现象:
这是 Linux 内存管理中一个非常核心的机制:写入时复制(Copy-On-Write, COW)。
但很多人有几个疑问:
本文从工程视角,一步一步讲清楚。
一个进程的虚拟内存典型结构如下:
虚拟内存空间
+------------------+
| code segment | 代码段(.text)
+------------------+
| rodata segment | 只读数据段
+------------------+
| data segment | 已初始化全局变量
+------------------+
| bss segment | 未初始化全局变量
+------------------+
| heap | malloc 分配
+------------------+
| mmap region | 动态库映射
+------------------+
| stack |
+------------------+
动态库(.so)通过 mmap 加载到 mmap region。
假设动态库 libexample.so 大小为 2MB,它的内部结构是:
libexample.so
.text 1.5MB ← 代码段(只读)
.rodata 0.3MB ← 常量段(只读)
.data 0.1MB ← 可写全局变量
.bss 0.1MB ← 可写全局变量
关键区别:
| 段 | 是否共享 | 原因 |
|---|---|---|
| .text | ✅ 共享 | 只读,不会修改 |
| .rodata | ✅ 共享 | 只读 |
| .data | ❌ 私有(COW) | 可能修改 |
| .bss | ❌ 私有(COW) | 可能修改 |
也就是说:
这就是内存节省的来源。
Linux 并不会一开始就复制 data/bss,而是:
先共享,写的时候再复制
流程如下:
关键点:
这就是 Copy-On-Write。
这是一个非常关键的设计问题。
假设:
动态库:
代码段 1.5MB
数据段 0.5MB
启动 100 个进程。
每进程占用:
代码段 1.5MB
数据段 0.5MB
总内存:100 × 2MB = 200MB
假设只有 10% 页被修改:
共享:
代码段:1.5MB
数据段初始共享部分(尚未被写入的页):0.45MB
私有部分(被实际写入后触发 COW 的页):100 × 0.05MB = 5MB
总内存:1.95MB + 5MB ≈ 7MB
对比:
| 方案 | 内存 |
|---|---|
| 立即复制 | 200MB |
| COW | 7MB |
节省:
97%
这是巨大的差异。
COW 体现了 Linux 核心设计哲学:
不要为'可能发生'的事情付出成本,只为'已经发生'的事情付出成本
即:
不是:
可能修改 → 立即复制
而是:
真正修改 → 才复制
这是典型的 Lazy 策略。
广泛存在于:
fork 时:
pid = fork();
不会复制整个进程内存。
而是:
parent process
child process
共享所有物理页
parent write
child write
复制页
这使 fork 非常快。否则 fork 会非常慢。
假设:
libc.so 3MB
libstdc++ 2MB
libQt 20MB
总:
25MB
启动 50 个进程:
如果没有共享:
25MB × 50 = 1250MB
实际:
≈ 25MB + 少量私有页
这就是为什么 Linux 可以运行大量进程。
物理内存
虚拟空间
进程 A text
进程 B text
物理页 0x1234
多个虚拟页 → 同一个物理页。
核心思想可以总结为一句话:
先共享,按需复制
背后体现三个关键设计原则:
只读数据共享:
减少内存占用。
不是:
可能修改 → 复制
而是:
真正修改 → 才复制
避免无意义开销。
复制由 page fault 触发:
write ↓
page fault ↓
kernel copy page ↓
resume execution
完全自动。
可以用一句非常工程化的话总结:
Linux 默认假设:绝大多数内存不会被修改,所以先共享,只有真的写时才复制。
这使得:
可以用这个命令观察:
pmap -x <pid>
或:
smem -r
查看:
你会发现:
动态库大部分是 shared。
动态库加载
只读段
可写段
共享物理页
共享物理页
共享物理页
进程写入
page fault
复制页
私有页
COW 本质不是优化技巧,而是 Linux 内存管理的基础设计原则:
共享是默认,复制是例外。
这也是 Linux 能高效运行大量进程的根本原因。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online