第63期 | 爹没了,孩子归 init 收养

操作系统 · · 九羲科技

一、爹没了,孩子归 init

Unix 有一条不成文的家规——进程不能没有爹。父亲先走了,孩子不会流浪:内核在父亲咽气的那一刻,就把孩子过继给 init¹。这条规矩藏在 kernel/exit.c 的 forget_original_parent 里,四十多年没动过。init 是系统里最后一个"负责任的成年人":它循环 wait(),替所有被收养的孩子收尸²。维纳会说,这是一个"感知—比较—执行"的控制回路,init 是回路的终点。

内核最冷的地方,藏着一句最温情的规矩。

2012 年,Linux 3.4 给这条家规打了个补丁:PR_SET_CHILD_SUBREAPER³。任何进程都能声明"我当这一支的 init"——孤儿不再一律投奔 PID 1,而是投奔最近的 subreaper。systemd 靠它看场子³。这补丁为谁打的?为容器时代。容器里的 PID 1 常常是业务进程自己,它不收尸,死掉的孤儿变僵尸,越积越多,进程表被占满,新的 fork 直接失败。于是有了 tini——一个只干收尸和传信号的小 init;2017 年 Docker 1.13 加了 --init,一键给容器塞个 tini 当 PID 1⁴。

好奇心停在最妙处:原理(孤儿得有人管)1970 年代就定了,补课补了四十年。2012 的 subreaper、Docker 的 --init,全是工程层在还债——原理不发明,只把老原理装进新形状。这就是发展性研究的长相。

二、孤儿不归 init,归意图

如果 AI 的操作系统先出现,"孤儿"这个概念可能根本不会诞生。Unix 的进程是一棵树,爹死了孩子必须过继,因为树不能有断掉的枝。原生 AI OS 里,智能体之间不是父子,是意图的传递:A 派 B 去干活,B 的派发者消失了,重要的不是"B 的爹是谁",而是"这件事还要不要继续、由谁继续"。

推演一步:AI OS 不维护进程树,维护一张意图图谱。每个智能体出生时带着三样东西:要办的事、办成的标准、办不成的退路。派发者消失,图谱不断——系统按意图把"孤儿"转交给最合适的监护者,而不是按血缘交给 init。监护的依据是匹配度:谁手里有相关记忆、谁算力空闲、谁离数据源近。

照见一层平时看不见的:Unix 收养制默认了一个没人问过的前提——"孩子必须有个爹"。单机时代天衣无缝,容器时代漏了(PID 1 不收尸),智能体时代会彻底不够用:agent 的遗产不是退出码,是半截上下文、没写完的记忆、进行中的谈判。init 只会 wait() 收尸,收不了这些。

落地还差几步:缺一个"意图监护协议"——转交的标准谁来定、监护者的算力账记在谁头上、原派发者的权限要不要继承。这三笔账没算清,意图图谱就是一张漂亮的纸。

范式之眼:进程树是线性时代的家谱;意图图谱是关系优先的。从"管理对象"(进程)到"管理关系"(意图的传递),这条线第 61 章会收束。

三、一条命令,看孤儿改姓

手边任何一台 Linux 机器,跑这三行:

(sleep 60 &)

sleep 1

ps -o pid,ppid,comm -C sleep

子 shell 派生 sleep 后立刻退出,sleep 成了孤儿——你会看到它的 PPID 变成了 1,改姓 init。这是 1974 年定下的规矩¹,今天依然有效。

再看容器的反例:docker run 不加 --init,跑一个会 fork 短命子进程的应用,ps 一看满屏 defunct;加上 --init,tini 当 PID 1,僵尸被收得干干净净⁴。一字之差,是"有没有成年人看场子"的区别。

想再往深摸一层:十行 Python,用 ctypes 调 prctl(PR_SET_CHILD_SUBREAPER, 1),声明自己当 subreaper,再双 fork 一个"孙子"让中间那代先走——ps 一看,孙子的 PPID 是你,不再是 1。亲手验证"收养权是可以下放的"。

落到实践:如果你在边缘设备或容器里跑会 fork 子进程的服务,entrypoint 记得配 tini,或启动时加 --init。资源紧的设备上,僵尸进程占着进程表不撒手,耗尽的是整台机器 fork 新进程的能力——这不是性能问题,是有没有成年人看场子的问题。

觉 然 小 注

「应无所住,而生其心。」⁵(思想化用)

爹死了,孩子不住在"丧父"里——内核当场改写它的 PPID,不哀悼,不停留。系统最温情处,是不让任何一个进程流浪;最高明处,是流浪发生时立刻重新安顿,不住于过去。

引 用 与 出 处

  1. [1] D. M. Ritchie & K. Thompson. The UNIX Time-Sharing System. Communications of the ACM, 1974. https://doi.org/10.1145/361011.361061
  2. [2] W. Richard Stevens & Stephen A. Rago. Advanced Programming in the UNIX Environment. Addison-Wesley, 第 3 版 2013.(第 8 章进程控制、第 13 章守护进程:孤儿进程由 init 收养并 wait 收尸)
  3. [3] PR_SET_CHILD_SUBREAPER(2const). Linux man-pages(Debian 存档):自 Linux 3.4 起;subreaper 为其后代进程履行 init(1) 的职责;systemd(1) 采用该机制. https://manpages.debian.org/unstable/manpages-dev/PR_SET_CHILD_SUBREAPER.2const.en.html
  4. [4] Docker --init 与 tini:自 Docker 1.13 起可用 --init 标志注入 tini 作为 PID 1,负责收割僵尸进程与转发信号. https://computingforgeeks.com/use-tini-init-system-in-docker-containers/
  5. [5] 《金刚般若波罗蜜经》第十品,鸠摩罗什译.(思想化用)

· · ·

← 返回资讯中心