第62期 | 不复印,直接借——vfork 的前世今生
一、不复印,直接借
fork 有个老毛病,第 55 期讲过:fork+exec 连用时,fork 辛辛苦苦复制的地址空间,exec 一来全扔了。1970 年代末的机器上没有写时复制,复制是真复制——一个几十 KB 的进程(当年不算小),fork 复制一遍,exec 再扔掉,纯属白干。
Berkeley 的人坐不住了。1979 年的 3BSD——第一个支持虚拟内存的 Unix¹——里,他们塞进一个新系统调用:vfork。规矩很怪:子进程不复制地址空间,直接穿父亲的衣服;父亲被挂起,屏住呼吸,一直等到孩子 exec 或退出,才准接着跑。借来的东西不能乱动:孩子不许从调用 vfork 的函数里 return(栈帧是借的,return 会回到不存在的栈帧),退出必须用 _exit,不能用 exit(exit 会冲掉 stdio 缓冲区,祸及父亲)²。
Stevens 在《Advanced Programming in the UNIX Environment》第 8 章,把 vfork 定为 fork+exec 的专用快车道³——它从来不是给通用场景准备的。System V 一开始还看不上:共享内存容易出错,直到 System V R4 才补上¹。
好奇心停在最妙处:vfork 不是新原理,是纯工程——原理(写时复制)还没影,先拿"借"顶上,"先实际,后原理"的中间层胜利。Linux 这边更直白:2.2 之前 vfork 就是 fork 的别名,2.2.0-pre9 起才成为独立系统调用,底层是 clone 带着 CLONE_VFORK|CLONE_VM 两面旗⁴。
然后写时复制成熟了,vfork 的饭碗被砸了。COW 让 fork 本身足够快,POSIX 在 2004 年把 vfork 标成"过时",接班人叫 posix_spawn——而 posix_spawn 在 glibc 里,底层通常还是走 vfork 语义的 clone¹。一圈转回来:借壳的技巧死了,借壳的思想借尸还魂。
二、生而有目的
如果 AI 的操作系统先出现,"复制再扔掉"这种事根本不会发生。Unix 的 fork+exec 是两步走:先生一个和我一样的,再换魂;vfork 是作弊:先借着用,换完魂再说。原生 AI OS 里,spawn 从第一天起就带着意图——"我要一个干活的智能体",而不是"复制我,再看它变成谁"。分叉(fork)与派活(spawn)是两种原语,不是一对补丁。
照见一层平时看不见的:vfork 真正借的不是内存,是时间——父亲的时间被孩子借走了。AI 时代最贵的也是时间:一个智能体挂起等另一个,烧的是显存和上下文窗口。Unix 用"挂起父亲"换速度;AI OS 得回答:等待时,智能体的上下文由谁保管、代价由谁来付?第 61 期的"上下文监护人",在这里接上了。
范式之眼:vfork 诞生于"复制很贵"的线性内存时代;今天"复制"便宜了(COW、快照),贵的是算力占用。旧抽象的补丁是"借地址空间",新硬件上的对应物是"借算力"——函数计算的冷启动、GPU 的按需挂载,都是 vfork 的转世。
三、一条 strace,看见借壳现场
手边任何一台 Linux 机器,先试这条:
python3 -c "import os; os.posix_spawn('/bin/echo', ['echo','hi'], os.environ)"
再加 strace 看底层:
strace -f -e trace=%process python3 -c "import os; os.posix_spawn('/bin/echo', ['echo','hi'], os.environ)"
你会看到一行带 CLONE_VM|CLONE_VFORK 的 clone3——2026 年的 glibc,派活走的还是 1979 年 vfork 的老路。借壳的思想没死,只是改名了。
另一个可观察的现象:man 2 vfork 还在,但正门已经是 posix_spawn。想亲手摸一下"借"的危险,写个十行 C 程序:vfork 之后,孩子改一个全局变量,再 _exit;父亲醒来一看,变量变了——共享不是比喻,是同一块内存。
落到今天的实践:内存吃紧的边缘设备上,推理服务派发子任务时,走 fork 还是走 posix_spawn,是一笔真金白银的账。动手算一下:time 加 strace -c,对比两种方式在设备上的开销,再决定。1979 年的老路,可能正是这类设备上最便宜的那条。
觉 然 小 注
「一切有为法,如梦幻泡影,如露亦如电,应作如是观。」⁵(思想化用)
vfork 是有为法:原理没到时顶上的工程技巧,原理一到,如露如电。可"借"这个念头没死——它换了个名字,活在每一次 posix_spawn 里。技巧会过时,念头不会。
引 用 与 出 处
- [1] Fork (system call). Wikipedia,Vfork 节:vfork 起源于 3BSD(第一个支持虚拟内存的 Unix);子进程借用父进程的内存与控制线程,父进程挂起直到子进程 exec 或退出;System V 在 R4 之前不支持;POSIX 2004 年标为过时,由 posix_spawn 接替(通常经由 vfork 实现). https://en.wikipedia.org/wiki/Vfork
- [2] VFORK(2). BSD System Calls Manual:子进程借用父进程的内存与控制线程,直到 execve(2) 或退出;不得从调用 vfork() 的函数中返回;失败退出须用 _exit(2) 而非 exit(3). https://www.unix.com/man-page/freebsd/2/VFORK/
- [3] W. Richard Stevens. Advanced Programming in the UNIX Environment. Addison-Wesley, 1992.(第 8 章"vfork 函数"一节;2013 年第 3 版由 Stevens 与 Stephen A. Rago 修订)
- [4] vfork(2). Linux man-pages,HISTORY 节:vfork 系统调用出现于 3.0BSD;Linux 在 2.2.0-pre6 之前等同于 fork,2.2.0-pre9(i386)起成为独立系统调用;glibc 2.0.112 起支持.
- [5] 《金刚般若波罗蜜经》第三十二分,鸠摩罗什译.
· · ·