页表 & 写时复制 COW(Copy‑On‑Write)
继续沿用比喻:
进程 = 车间
物理内存 = 真实的物理仓库,硬件实际内存条
虚拟地址 = 车间内部图纸上的编号(虚拟地址)
页表 = 图纸和真实仓库的映射对照表
CPU不直接用物理内存地址。每个车间(进程)拿到一套独立虚拟地址空间,车间只认识虚拟地址。CPU通过页表,把虚拟地址翻译成内存条真正的物理地址。
重点:每个进程有自己一套页表。不同进程,同一个虚拟地址,映射到完全不同物理内存。
页表干什么
- CPU访问内存时,MMU(内存管理单元硬件)查页表,虚拟地址 → 物理地址
- 标记页权限:可读、可写、执行、是否在内存中(缺页中断)
- fork子进程,核心操作就是操作页表,而不是立刻拷贝全部内存。
没有COW的fork(老时代)
父进程fork,内核把父进程全部物理内存完整复制一份给子进程。
- 父进程1GB内存,fork瞬间就要拷贝1GB,很慢,占用双倍内存。
- 绝大多数场景fork之后马上调用
exec()加载新程序,复制完立刻全部丢弃,完全白干活。
于是诞生写时复制 COW Copy‑On‑Write。
fork + COW 完整过程
父进程调用fork()系统调用进入内核:
- 创建子进程task_struct,分配PID
- 复制父进程的页表,不复制物理内存!
- 父子进程的页表,全部指向同一套物理内存页
- 内核修改页表项标记:把这些共享页设置成只读
📝比喻:
父车间有一套完整图纸+仓库货物。
fork:给子车间复印一份【对照表(页表)】,货物不复制,父子共用同一堆真实货物,并且约定:谁都不许修改货物。
此时父子读内存完全没问题,读的时候共用物理内存,内存不会翻倍。
什么时候真正复制内存?
只要任意一方(父 or 子)尝试写这块内存:
- CPU执行写指令,MMU查页表,发现页面标记为只读,触发缺页异常(页面故障),陷入内核。
- 内核捕获异常:知道这是COW共享页,不是真的权限错误。
- 内核分配一块新的物理内存,把原来那一页内容拷贝过去。
- 修改触发写操作那一方的页表:指向新复制出来的物理页,标记为可写。
- 另一方依旧指向老物理页。
- 返回用户态,程序继续执行写操作。
比喻:父子共用货物,父车间想改其中一件货物。
内核管家:复制一份这件货物放到新仓库位置,父车间以后修改自己这份;子车间继续用老的那一份。从此以后两者不再共享这一页。
👉 只有发生写的时候,才真正拷贝那一页内存;只读全程共享。
两个典型场景
场景1:fork之后立刻execve()(shell跑命令)
bash(父进程)fork出子进程,此时全部内存共享。
紧接着子进程调用exec("ls"),直接加载ls程序,直接丢弃整个子进程的地址空间和页表。
因为没有发生写,几乎没有内存拷贝开销,COW在这里收益最大。
场景2:fork之后父子都修改全局变量
int a = 100;
pid_t pid = fork();
if(pid == 0) {
a = 200; //子进程写a,触发COW,复制这一页
printf("child a=%d\n",a);
}else{
sleep(1);
printf("parent a=%d\n",a);
}
输出:
child a=200
parent a=100
- fork瞬间,父子共享存a的物理页。
- 子进程执行
a=200写操作,触发COW,复制页面。子拿到自己副本。 - 父进程的a依旧是100,两者互不影响。
很多人疑惑:为什么子进程改变量父进程看不见?根源就在这里。
关键易错点
- COW是按页粒度复制,不是整个进程内存一次性复制。一页通常4KB。哪一页被写,就复制哪一页。
- 只读访问不会复制,只有写才复制。
- 页表本身在内核内存,不属于用户进程地址空间,用户代码不能直接修改页表,全部靠内核+MMU硬件。
- 线程没有COW:多线程共享同一份页表,同一个物理内存,线程修改全局变量,其他线程立刻可见。
对比记忆:
- pthread多线程:同一个页表,全部内存共享,写直接修改原物理页。
- fork子进程:复制页表,初始共享物理页,写触发COW复制。
缺页中断两种情况
- 真正缺页:页面不在物理内存,被交换到磁盘swap,内核从磁盘读回内存。
- COW缺页:页面就在物理内存,只是页表标记只读,写触发复制。
整体串联回顾整套链路
- 每个进程拥有独立虚拟地址空间,靠页表映射到物理内存条。
- fork:内核新建task_struct、PID,复制页表,父子页表指向相同物理页,全部标记只读。
- 读内存:MMU通过页表访问共享物理内存,无拷贝。
- 任意进程写内存 → MMU报页错误,进入内核。
- 内核分配新物理页,拷贝页面内容,修改触发方页表映射。
- 用户态继续运行,父子从此各自持有独立副本。
一句话总结
fork不会复制真正内存,只会复制页表,父子共享物理内存;只有写操作发生的时候,内核才复制对应的内存页,这就是写时复制COW。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/Logintern09/article/details/163896819



