第54期 | 一次调用,两次返回

操作系统 · · 九羲科技

一、二十七行汇编,一个新进程

1969 年的夏天,Ken Thompson 在一台被冷落的 PDP-7 上写 Unix。Ritchie 后来在《The Evolution of the Unix Time-sharing System》里回忆,fork 的第一版实现只需要两件事:把进程表扩大一点,加一个 fork 调用——把当前进程整个拷到磁盘交换区,用的全是现成的 swap I/O,再微调一下进程表。PDP-7 上的 fork,精确地 27 行汇编¹。

发展性研究那一问,在这里有标准答案。fork 没有发明任何新原理:进程表是现成的,swap 机制是现成的,Thompson 干的只是"借"。原理突破一次,发展性研究管五十年——对照 GNU Hurd 追着微内核的原理完美跑,1991 年还远没法用,答案就写在 27 行汇编里。

更妙的是 fork 和 exec 的分离。今天看来天经地义——fork 复印自己,exec 换上新灵魂——可 Ritchie 复盘说,这分离存在的主要原因,是 fork 太好实现了:系统本来就有进程表、本来就会 swap,加个 fork 顺手得很;而一个合体的 fork-exec 反而复杂得多,何况 exec 当时根本不存在,shell 是用显式 I/O 干这活的¹。顺带一提,这种分离在 Berkeley 的分时系统里已有先例,Thompson 见过¹。所谓天才设计,一半是顺手。

1974 年那篇 UNIX 论文²把 fork 的语义钉死了:除引导之外,新进程只能由 fork 诞生;一次调用,把一个进程劈成两个独立执行的进程,各有一份独立的 core 拷贝,共享已打开的文件;父进程从 fork 直接返回,子进程从约定的 label 处继续;返回值,是对方的进程号。

照见一层平时看不见的。fork 的语义里藏着 UNIX 最深的组织假设:没有"凭空创建",只有"分裂繁殖"。所有进程构成一棵树,根是 init——引导时特批,不在规则内。进程有家谱。三十年后,Linux 的 clone、namespace、容器,全是在这棵家谱上打补丁,没人敢动"分裂繁殖"这条根。

美学时刻留给生物学。细胞有丝分裂:染色体先复制、再均分,一个变两个,两个一模一样。fork 就是操作系统的有丝分裂。妙处不在复制,在复制之后——两个细胞(进程)从同一个起点开始,写不同的命运。

二、智能体的第一次分裂

如果操作系统的第一批用户不是人,而是智能体,"进程"会是什么样?

agent 派生子 agent 去并行探索,天然就是一次 fork。但 AI 的 fork 不必复印整个 core image:子 agent 继承的是上下文——目标、记忆摘要、工具权限——而不是内存页。语义保留(分裂加继承),实现换成"上下文分叉"。

"一次调用,两次返回"在 AI 世界里变成"一次派生,两份记忆起点"。父子从同一份上下文出发,经验此后分岔——这正是今天多智能体系统里"分支探索、再合并"的雏形。fork 活了五十五年,变的只是复印的东西。

但有个问题 UNIX 从没问过:fork 之后父子完全平等,权限允许的话,子进程可以 kill 掉父进程。AI 世界里,子 agent 的权限该从父完整继承,还是被收敛?派生即授权——spawn 的那一刻,就要做 capability 收敛,而不是事后补。

范式之眼补一句。进程是线性时代的"指令流容器",装的是一条顺序执行的指令流;AI 的"进程"可能是"目标流容器",调度的单位不再是指令指针,而是一段意图。容器换了,fork 的"分裂繁殖"却未必换——因为"从已知分岔出未知",是智能探索最古老的动作。

三、今晚,亲手养一只僵尸

手边任何一台 Linux 机器,今晚就能亲眼看见"一次调用,两次返回"。

python3 -c "import os; pid = os.fork(); print('子进程报到' if pid == 0 else f'父进程报到,孩子 pid={pid}')"

同一行 print,执行了两次。本机实测,先后吐出"子进程报到"和"父进程报到,孩子 pid=4999"——一个调用,两个返回,两个世界。

再养一只僵尸。父进程 fork 出孩子后,孩子出生即退场,父进程睡 25 秒不收尸;另开一个终端跑 ps aux,你会看到一行 [python3] <defunct>,STAT 列是个 Z。本机实测,一字不差。这就是僵尸进程:死了,但没人收尸,PCB 还占着进程表——fork 只管生,wait 才管埋。

落点。如果你所在的团队在给企业做本地 AI,设计智能体的"派生子任务"时,可以直接抄 fork 的三条:第一,派生必须便宜——学写时复制的思想,子任务只读共享父上下文,写时才拷贝;第二,父子要有血缘——任务 ID 链,方便追踪、限权、回收;第三,派出去的必须有人收尸——父任务要 wait,不然满屏僵尸任务,占着任务表不干活。

觉 然 小 注

「一切贤圣,皆以无为法而有差别。」³

二十七行汇编是有为法,会过时、会被重写;"分裂即创造"的那一闪念是无为,不生不灭。fork 活了五十五年,活的从来不是那 27 行代码,是那个念头。

引 用 与 出 处

  1. [1] Ritchie, D. M. The Evolution of the Unix Time-sharing System. Bell Laboratories 存档. https://9p.io/cm/cs/who/dmr/hist.html
  2. [2] Ritchie, D. M. & Thompson, K. The UNIX Time-Sharing System. Communications of the ACM, 17(7): 365–375, 1974. https://doi.org/10.1145/361011.361061
  3. [3] 鸠摩罗什 译. 《金刚般若波罗蜜经》第七品"无得无说分". 后秦弘始年间(约402年)译本. ("一切贤圣,皆以无为法而有差别",思想化用)

· · ·

← 返回资讯中心