第55期 | 写时才复印
一、写时才复印,fork 的懒字诀
1969 年,Thompson 在 PDP-7 上的 fork,是把整个进程"全文复印"进交换区——27 行汇编,复印全部家当¹。十年后,Bill Joy 在 3BSD 里走了另一个极端:vfork²。不复印,孩子直接住进父亲的地址空间,父亲原地睡着,直到孩子 exec 或退出。孩子手一抖,写坏父亲的栈,父亲就残了。快,但险。
1980 年代,Mach 的虚拟内存论文把写时复制做成了内存对象的标配³:共享时只建"影子",动笔才真复印。SunOS 4.0(1988)把这套语义带进主流 Unix 的 fork²。
1991 年,Linux 0.01 出生就带着它。kernel/fork.c 的 copy_process 注释还写着"copies the data segment in its entirety",可 mm/memory.c 的 copy_page_tables 干的是另一件事:一行 this_page &= ~2;——父子页表项同时清掉可写位,指向同一块物理内存,引用计数加一⁴。谁先动笔,缺页异常 do_wp_page 才复印那一页。fork 的代价,从 O(内存大小) 跌到 O(页表大小)。
发展性研究那一问,又有新答案。没人发明新原理,只是把"复印"从"出生时一次印完"改成"动笔时再印"。懒,但精确。
照见一层平时看不见的。写时复制把"拥有"推迟到了"使用"的那一刻:共享的那些页,没有一页属于谁,归属是写下去的瞬间才发生的。内存管理第一次有了时间的味道——不是空间上的划分,是时机上的划分。
美学时刻留给旧书房。读书人借书,不急着全抄;只在要批注的那一页,另起一张纸。写时才复印,是最古老的懒,也是最精确的勤。
二、分叉上下文,不必复印内存
如果 AI 的操作系统先出现,"诞生"这个动作里根本不会有复印——agent 派生子 agent,分叉的是上下文:目标、记忆摘要、工具权限。上下文天然是"只增日志加共享前缀",分叉即共享,写下新经验才追加。这正是 Git 分支干的事。
2023 年,vLLM 的 PagedAttention 把同一招用进了 KV cache⁵:KV 按 block(16 个 token 一块)分页,多个采样序列共享 prompt 的物理 block;谁要往被共享的 block 里写,先复印。论文里直说,用的是"the copy on write technique from OS virtual memory",还补了一句:这种共享长得"very similar to the fork tree when you call fork in OS processes"。三十二年,fork 的树在 GPU 显存里又长了一遍。实测:并行采样省 6%–30% 显存,beam search 省 38%–66%。
范式之眼补一句。COW 的单位今天还是 4KB 线性页——为顺序指令流设计的页。可 GPU 里跑的是整块整块的张量。PagedAttention 已经把单位换成了"16 个 token 的 KV 块"。旧抽象,正在新硬件上被一页一页地重写。
三、今晚,亲手数一数共享的页
手边任何一台 Linux 机器,今晚就能亲眼看见"写时才复制"。
父进程 malloc 80MB,逐页写一遍'A',然后 fork。子进程先睡 10 秒——这 10 秒里,另开终端读 /proc/子进程pid/smaps。本机实测:子进程 VmRSS 82928kB,其中 Shared 82884kB,Private_Dirty 只有 44kB。80MB 内存几乎全是共享的,fork 根本没复印。
然后让子进程写一半内存(40MB),再看:Shared 掉到 41924kB,Private_Dirty 涨到 41004kB。写下去的那一半,每一页都被真正复印成了私有页;没写的那一半,还在共享。页数对得上,checksum 也对得上。
自己动手就一条命令:awk '/^Shared_|^Private_/{s[$1]+=$2} END{for(k in s) print k, s[k]"kB"}' /proc/<pid>/smaps
落点。如果你在给企业做本地 AI 推理或智能体:vLLM 里 KV block 的共享与写时复制是默认开的,懂它,才知道 batch 开多大不爆显存;内存吃紧的边缘设备上,agent 派生子任务别每路完整拷贝上下文,学 COW——共享只读前缀,写新经验才追加;任何"派生子任务"的设计,默认实现就该是写时复制:便宜,才敢多派生。
觉 然 小 注
「凡所有相,皆是虚妄。若见诸相非相,则见如来。」⁶(思想化用)
fork 之后,父子各见一套页表,以为各有一份内存——相。写下去的那一刻,才见哪页真分了家,哪页还在共享。不动笔,不分别。
引 用 与 出 处
- [1] Ritchie, D. M. The Evolution of the Unix Time-sharing System. Bell Laboratories 存档. https://9p.io/cm/cs/who/dmr/hist.html
- [2] Wikipedia. Fork (system call). https://en.wikipedia.org/wiki/Vfork
- [3] Rashid, R. et al. Machine-Independent Virtual Memory Management for Paged Uniprocessor and Multiprocessor Architectures. ASPLOS, 1987. https://www.academia.edu/9488157/Machine_Independent_Virtual_Memory_Management_for_Paged_Uniprocessor_and_Multiprocessor_Architectures
- [4] Linux 0.01 源码. kernel.org Historic 存档. https://www.kernel.org/pub/linux/kernel/Historic/linux-0.01.tar.gz (所核对文件:kernel/fork.c 的 copy_process、mm/memory.c 的 copy_page_tables 与 do_wp_page)
- [5] Kwon, W. et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. SOSP, 2023. https://arxiv.org/abs/2309.06180
- [6] 鸠摩罗什 译. 《金刚般若波罗蜜经》第五品"如理实见分". 后秦弘始年间(约402年)译本. ("凡所有相,皆是虚妄",思想化用)
· · ·