K03 —— 进程创建时的内存复制:copy_page_range 和 COW 细节
金句:fork 的优雅,在于它把”复制”变成了”共享,直到不得不”。
fork 中的内存复制流程
fork 系统调用的核心是 copy_process,其中内存相关的部分是 copy_mm,它调用 copy_page_range 来复制父进程的地址空间。
fork 内存复制的调用链:
sys_fork() → do_fork() → copy_process()
→ copy_mm() → dup_mm()
→ dup_mmap() → copy_page_range()
→ copy_pud_range() → copy_pmd_range() → copy_pte_range()copy_page_range 遍历父进程的每个 VMA,为子进程创建对应的页表条目:
copy_page_range() 的核心逻辑:
int copy_page_range(struct mm_struct *dst, struct mm_struct *src,
struct vm_area_struct *vma)
{
unsigned long addr = vma->vm_start;
unsigned long end = vma->vm_end;
for (; addr < end; addr += PAGE_SIZE) {
// 1. 为子进程分配新的 PTE
// 2. 复制 PTE 内容
// 3. 设置 COW 标志(只读)
// 4. 共享物理页面(引用计数 +1)
}
}COW 在 fork 中的具体实现
fork 时 COW 的关键步骤:
1. 复制页表条目(PTE),但共享物理页面
- 父 PTE:物理页面 + RW 位
- 子 PTE:同样的物理页面 + RO 位(只读)
- 物理页面的引用计数 +1
2. 设置页面为只读
- 父子都有页表指向同一个物理页
- 页面被标记为只读
- 当任一方写入时,触发 page fault
3. Page Fault 处理 COW
- Page fault handler 发现:
a. 页面存在
b. 只读
c. 引用计数 > 1(共享)
- 分配新页面
- 复制旧页面内容
- 建立新页面的映射
- 把写入方的 PTE 更新为新页面 + RW
关键数据结构:
struct page {
atomic_t _refcount; // 引用计数
// ...
unsigned long flags; // PG_dirty / PG_writeback 等
};
PG_referenced 标志:
- 页面最近被访问
- COW 时检查这个标志
- 如果是 clean + referenced,优先不回收文件映射的 COW 特殊处理
文件映射的 fork COW 与匿名映射不同:
匿名映射(MAP_ANON):
- 页面内容是内存(全是零或匿名数据)
- COW 时直接分配新页,复制内容
- 不需要写回文件系统
文件映射(MAP_FILE):
- 页面内容来自文件
- COW 时需要先读取文件内容到新页
- 写时复制后变成"私有"(不再同步到文件)
writeback 处理:
- COW 后的脏页不会写回原文件
- 只对当前进程可见
- 进程退出时释放(不回写文件)
MAP_SHARED 的处理:
- fork 时不复制页表(共享同一个 VMA)
- 父子共享同一个 page cache
- 写入会直接修改 page cache(立即对其他进程可见)
- 不是 COW,是真正的共享vfork 和 clone 的特殊处理
vfork(virtual fork)的特殊处理:
vfork() 和 fork() 的区别:
fork():
- 复制整个地址空间(COW)
- 父子可以独立运行
- 写时复制,内存可能翻倍
vfork():
- 完全共享父进程地址空间(不复制)
- 子进程先运行,调用 exec 或 _exit 后父进程才继续
- 效率更高(但有风险)
- 现代 Linux 实际用 clone(CLONE_VM) 实现
clone(CLONE_VM) 的效果:
- 父子共享同一个 mm_struct
- 没有 COW,没有页表复制
- 共享所有 VMA 和页表
- 立即 exec 时效率最高(不复制任何内存)
vfork 的风险:
- 如果子进程不调用 exec/_exit,父进程无法运行
- 子进程修改内存会影响父进程(完全共享)
- 现代代码很少用 vfork,fork + COW 更安全总结
- fork 内存复制:copy_page_range 遍历 VMA,复制页表但共享物理页,标记只读(COW)
- COW 实现:页表条目只读 + 物理页引用计数 +1 → 写入时 page fault → 分配新页 → 复制内容 → 更新 PTE
- 匿名 vs 文件映射:匿名 COW 直接复制,文件 COW 读文件内容到新页(私有化)
- MAP_SHARED:fork 时不复制,父子共享同一个 page cache,写入立即对其他进程可见
- vfork:完全共享地址空间(不复制),子进程先运行直到 exec/_exit,现代用 clone(CLONE_VM)
下篇预告(K04):当内存耗尽时,内核是怎么选择杀掉哪个进程的?OOM Killer 的评分机制和 cgroup 内存限制。
关注公众号「AI不着急」,回复”资料”获取内存学习路线图。
评论