第61期 | 两次 fork,一场断舍离

操作系统 · · 九羲科技

一、两次 fork,一场断舍离

1963 年,MIT 的 Project MAC 机房里,Fernando Corbató 的团队给后台进程起了个名字:daemon。灵感来自麦克斯韦 1867 年的思想实验——那只在后台不知疲倦分拣分子的"麦克斯韦妖"¹。拼写取的是希腊文 δαίμων,不是基督教的 demon;"Disk And Execution MONitor"这种逆缩写,是后人硬凑的¹。

1992 年,Stevens 在《Advanced Programming in the UNIX Environment》第 13 章,把"如何生一个守护进程"写成了固定仪式²:umask(0);fork,父进程退出;setsid;chdir("/");关掉继承来的文件描述符;把 stdin、stdout、stderr 重定向到 /dev/null。

好奇心该停在最怪的那一步:为什么 fork 要做两次?第一次 fork,是为了让 setsid 能成功——POSIX 规定,进程组长调用 setsid 会直接 EPERM,而 fork 出来的孩子继承父进程的进程组,一定不是组长²。setsid 一口气做三件事:自立为新会话的首领、自立为新进程组的组长、甩掉控制终端。

可 setsid 之后,自己成了会话首领——而会话首领日后 open 终端设备时,可能重新"娶"一个控制终端(System V 的老行为)。于是第二次 fork:孙子不再是会话首领,永不再娶²。两次 fork 分工明确:第一次为了能 setsid,第二次为了不再是首领。

第二次 fork 的父进程退出后,孙子成了孤儿,被 PID 1 收养——第 57 期讲僵尸进程要等父亲 wait 收尸,这一期讲孤儿进程要等 init 收养,一死一活,恰好对称。

妙处在这里:整套仪式是一场"断舍离"。断父子(两次退出),断会话(setsid),断终端(永不再娶),断目录(chdir("/") 是怕占着文件系统不让卸载),断描述符(关干净)。守护进程的诞生,就是把一切能被找到的坐标,一个一个删掉。

二、AI 的守护进程,该长什么样

如果 AI 的操作系统先出现,常驻不需要仪式。Unix 的 daemon,是把一个"为人交互而生"的进程,硬生生改造成"无人值守"的样子——两次 fork 是整形手术。在原生 AI OS 里,没有终端,没有会话,"前台/后台"之分本身就不存在:agent 默认就是常驻的,需要人看一眼才是例外。顺序反过来了。

照见一层平时看不见的:double-fork 真正管理的不是进程,是关系——父子、会话、终端。AI OS 里对应的那根隐形的线,是 agent 的关系网:谁 spawn 了我,我的上下文归谁,我死后没做完的事由谁接?Unix 用 PID 1 收养孤儿;AI OS 需要"上下文监护人"。第 57 期的僵尸问题在这里有了回响:僵尸进程占的是进程表槽位,僵尸 agent 占的是显存和上下文窗口——代价从"一个 task_struct"变成了"几十 GB 显存",收尸规则得重写。

范式之眼也在这儿:daemon 假设进程便宜、常驻无代价,这是线性计算时代的直觉。AI 时代,常驻 agent 的"便宜"是假的——每个常驻 agent 都是一块显存的长期占用。调度不该再按进程数算,而该按"算力阵列的占用"来发常驻牌照。

顺带一提发展性研究之眼:Stevens 的仪式没有发明任何新原理,只是把 fork、setsid 几个现成原语排成固定队形——"先实际,后原理",典型的中间层胜利。

三、一条命令,看一次断舍离

手边任何一台 Linux 机器,先试一条命令:setsid sleep 300 &,再 ps -o pid,ppid,pgid,sid,tty,cmd -p $!。你会看到 PPid 是 1,TTY 那栏是个"?"。关掉终端,进程还在——这就是"第一次 fork + setsid"的手感,一条命令,亲手摸到。

想看全套仪式,用 Python 写二十行:os.fork(),子进程里 os.setsid(),再 os.fork(),打印三代的 pid、ppid、sid、pgid。亲眼看:第二次 fork 的孩子 ppid=1,sid 等于中间那代的 pid,而自己不再是会话首领。再用 ls -l /proc/<pid>/fd 看描述符,cat /proc/<pid>/status | grep PPid 验证收养关系。

还有一个可观察的现象:普通后台进程收到 SIGHUP 会死(终端关了),daemon 不会——setsid 那一刻,它和终端的脐带就已经剪断了。

落到今天的实践:边缘计算盒子上常驻的推理服务、视频 AI 平台的后台任务,本质都是现代 daemon——systemd 的 service 单元替你做了 Stevens 那套仪式(新式写法见 daemon(7)³)。理解两次 fork,就理解了为什么今天不用手写这套仪式;更重要的是下一个问题:常驻智能体的"显存遗产"由谁收养?给常驻智能体设计守护机制时,这是必须回答的第一题。

觉 然 小 注

「应无所住,而生其心。」守护进程把这句话编译成了两次 fork:不住终端,不住会话,不住父进程。fork 第一次是为了出生,第二次是为了不被找到——最彻底的常驻,是把"我从哪里来"也一起删掉。

引 用 与 出 处

  1. [1] Daemon (computing). Wikipedia,"Terminology"节引 Fernando J. Corbató 关于 1963 年 MIT Project MAC 命名典故的回忆("We fancifully began to use the word daemon to describe background processes that worked tirelessly to perform system chores"). https://en.wikipedia.org/wiki/Daemon_(Unix)
  2. [2] W. Richard Stevens. Advanced Programming in the UNIX Environment. Addison-Wesley, 1992.(第 13 章"Daemon Processes";2013 年第 3 版由 Stevens 与 Stephen A. Rago 修订)
  3. [3] daemon(7). Linux man-pages / systemd,"Writing New-Style Daemons"节.

· · ·

← 返回资讯中心