肯·汤普森
Ken Thompson
1969 年的一个夏天,肯·汤普森(Ken Thompson)的妻子和儿子去加利福尼亚度假,给他留下了三个星期的独处时光。他用这三周,在贝尔实验室一台被废弃的 PDP-7 小型机上,写出了一个操作系统内核、一个 shell 和一个文件系统编辑器。
这就是 Unix 的诞生。
这个故事因为过于传奇而常被质疑。汤普森本人后来确认了这个基本框架,但也强调实际工作远比"三周"复杂——这三周完成的是核心结构,完善工作持续了数年。然而关键是:Unix 诞生于一个人的专注、一台废弃的机器,以及一段意外的独处时光。
破除误解:汤普森不只是里奇的合作者
丹尼斯·里奇(Dennis Ritchie)发明了 C 语言,因此在公众认知中往往比汤普森更知名。但如果要区分两人的核心贡献,汤普森更像是 Unix 的架构师和最初实现者——是他在 PDP-7 上从零打出了第一行 Unix 代码,是他设计了 Unix 的文件系统哲学和进程模型。
里奇是语言的发明者和理论深度的提供者,汤普森是那个坐下来把事情做出来的人。
现场:从太空旅行游戏到操作系统
汤普森 1943 年 2 月 4 日生于新奥尔良,在加州大学伯克利分校获得电子工程学士和硕士学位,1966 年加入贝尔实验室。
1960 年代末,贝尔实验室曾参与 MIT 的 Multics 操作系统项目。Multics 野心勃勃,但进展迟缓,贝尔最终于 1969 年退出。退出之后,汤普森、里奇以及一批同事发现自己突然没有了可以使用的分时操作系统。
汤普森原本在 Multics 上开发一个叫"太空旅行"(Space Travel)的游戏——一个模拟太阳系行星运动的小程序。Multics 没了,他想把这个游戏移植到那台废弃的 PDP-7 上。但 PDP-7 上没有操作系统。于是他写了一个。
这个起点令人忍俊不禁:Unix 的最初驱动力,是为了跑一个游戏。
Unix 的核心设计
汤普森的 Unix 有几个当时极不寻常的设计决策,后来成为了操作系统设计的行业标准:
一切皆文件(Everything is a file):设备、进程、网络连接,都被表示为文件,通过统一的 open/read/write/close 接口操作。这极大简化了程序员的心智模型——学会了操作文件,就学会了操作一切设备。
管道(Pipes):程序的输出可以直接接入另一个程序的输入,用 | 符号连接。这个 1973 年由道格拉斯·麦克罗伊(Douglas McIlroy)完善的特性,使得数十个简单工具的任意组合成为可能,彻底改变了命令行交互的范式。
进程与分叉(Processes and fork):Unix 用 fork() 系统调用创建新进程——子进程是父进程的精确副本,然后两者分叉执行。这个模型简单、优雅,且极易理解,成为了后来所有 Unix 系衍生系统的基础。
Shell 脚本:Shell 既是命令解释器,也是一种编程语言。汤普森设计的第一个 Shell(Thompson Shell)奠定了 Bourne Shell、bash 等后续 Shell 的基础,也奠定了 DevOps 时代"脚本化一切"的文化。
这些选择加在一起,与其说是功能清单,不如说是一种接口策略。设备出现在 /dev 里,管道是另一种文件,程序不必知道对面是磁盘、终端还是另一个进程。学会 open/read/write/close,就学会了跟整台 操作系统 说话。管道把「做好一件事」从口号变成可执行的组合:小工具保持愚蠢,智力放在连接处。
1973 年 Unix 用 C 重写之后,同一套接口被搬到新机器上,可移植性第一次压过了「为这台硬件手写汇编」。汤普森与 丹尼斯·里奇 1974 年在《ACM 通讯》上发表的《UNIX 分时系统》,把文件系统、进程与 shell 写成可以讲授的公共知识。操作系统从此不只是厂商的黑箱,也可以是一篇论文、一门课、一份可以被抄走再改的设计。
B 语言:C 的前身
在 C 语言之前,汤普森发明了 B 语言。B 语言源自 BCPL(Martin Richards 设计),汤普森简化和修改了它以适应 PDP-7 的内存限制。B 语言是无类型的(所有值都是"机器字"),这在 PDP-7 上够用,但当他们将 Unix 移植到 PDP-11 时,需要区分不同大小的数据类型。于是里奇在 B 的基础上发展出了有类型系统的 C 语言。
汤普森→B 语言→里奇→C 语言,这条谱系清晰地说明了两人工作的递进关系。
B 的出身更土。汤普森起初想在 PDP-7 上做一套 FORTRAN 编译器,写着写着发现编译器本身就塞不进那点内存,于是按 BCPL 的语义一削再削,留下一个只认得「机器字」的小语言。PDP-7 按字寻址,字长即一切,无类型在这种机器上不是缺陷,是如实描述。名字为什么叫 B,有人说是 BCPL 的缩写,有人说是他更早的 Bon 语言;汤普森两说都不认。
真正把 B 逼出历史舞台的是下一台机器。PDP-11 按字节寻址,还要处理字符与后来的浮点;「地址加一」究竟移动一个字节还是一个字,取决于你指向什么。无类型模型在这里缺口暴露,类型系统 不再是学院装饰,而是编译器缩放指针的说明书。里奇由此补上类型,经 NB 走到 C,B 的遗产因此不是语法,而是证明:系统语言必须先能住进这台机器的内存,再谈抽象。
正则表达式:从 QED 到 grep
在 B 语言之前,汤普森还留下了一项影响更隐蔽的发明:正则表达式的第一个工程实现。
1960 年代中期,他在 CTSS 分时系统上实现 QED 文本编辑器时,把数学家斯蒂芬·克莱尼(Stephen Kleene)1950 年代提出的正则表达式记号第一次搬进了实用程序。汤普森的实现有一个今天看来极具前瞻性的技巧:他不解释执行匹配,而是把每个正则表达式当场编译成 IBM 7090 的机器码,让匹配以原生速度运行——这可以算是即时编译(JIT)思想的极早期实践。1967 年他为此申请专利,1971 年获准(美国专利 3,568,156,"文本匹配算法"),是史上最早的软件专利之一。
到贝尔实验室后,汤普森为 PDP-7 写出了 Unix 的标准编辑器 ed,正则表达式随之进入 Unix。没过多久,他把 ed 中"全局搜索并打印匹配行"的命令 g/re/p 抽出来做成了独立工具——grep 的名字就来自这条命令。
把编辑器命令抽成独立工具,看起来只是偷懒,其实改了文本处理的尺度。ed 要把整个文件装进内存才能随机改;正则表达式 一旦面对《联邦党人文集》那种篇幅,编辑器就会先撑爆。汤普森改成顺序扫过任意长的输入,只打印匹配行。道格拉斯·麦克罗伊当时要一个能按模式搜文件的东西,汤普森隔夜交出了这份私用程序;1973 年它进入 Unix 第四版,麦克罗伊后来说,grep 把「工具哲学」不可逆地嵌进了这套系统。
汤普森 1968 年发表在 CACM 上的匹配算法论文还回答了"为什么可以这么快":他的构造把正则表达式转换为状态机后同时跟踪所有可能的当前状态,而不是沿单一路径试探、失败后回溯。这保证了匹配时间与文本长度成线性关系,不会像回溯法那样在某些模式上指数级爆炸。半个世纪后,这一课仍在被反复重学——不少现代正则库因为选择回溯实现,重新引入了同样的陷阱。
Belle:一台专门下棋的机器
1970 年代末,汤普森与贝尔实验室同事乔·康登(Joe Condon)合作建造了 Belle——一台不靠通用计算机、而靠专用硬件下棋的机器。康登负责硬件,汤普森负责软件。设计思路朴素而直接:棋力主要来自搜索深度,搜索深度来自速度,于是他们把走法生成和局面评估做成专用电路,让穷举搜索跑得比通用机器上的任何程序都快。
Belle 于 1978 年首夺 ACM 北美计算机国际象棋锦标赛冠军,1980 年在奥地利林茨赢得第三届世界计算机国际象棋锦标赛,1983 年 10 月成为第一台获得美国棋联(USCF)大师等级分(2203)的机器。Belle 验证了"暴力搜索+专用硬件"路线的威力,这条路线一直通向 1997 年击败卡斯帕罗夫的深蓝。
UTF-8:全球互联网的字符编码基础
1992 年 9 月的一个晚上,汤普森与罗布·派克(Rob Pike)在新泽西州一家餐厅的餐垫纸(placemat)背面设计了 UTF-8 字符编码方案。
在此之前,字符编码是互联网的一大混乱来源:ASCII 只能表示英语字符,各国有各自不兼容的编码方案(中文有 GB2312、Big5,日文有 Shift-JIS 等),网页乱码是家常便饭。
UTF-8 的设计天才在于它的向后兼容性:ASCII 字符(0–127)在 UTF-8 中仍然用一个字节表示,完全与原有 ASCII 兼容;非 ASCII 字符则用 2–4 个字节编码,且有自我同步的边界标记,可以在任意字节位置开始解码,不会产生歧义。
这张餐垫纸要解决的,是 Plan 9 已经踩过的坑。他们先用过 ISO 10646 那种 UTF,编码不能自同步:从文件中间抓一把字节,找不到字符边界。X/Open 当时传阅的 FSS-UTF 方案,主要是为了让文件名里的斜杠不会藏进多字节序列、搞乱 Unix 文件系统。汤普森加上的关键约束是:任意位置都能在少于一个字符的代价内重新对齐。
1993 年他们在 USENIX 上公开这套设计;IETF 后来把 UTF-8 写成 RFC 3629,并把最长序列钉在四个字节。ASCII 字节不会出现在多字节序列内部,旧的按字节扫描的工具仍然可以安全地找换行、找斜杠。今天网页上的中文能跟英文待在同一份文件里,靠的不是「字符够多」,而是 Unix 式的字节流没有被国际字符集拆断。Unicode 与文本编码 后来把码点与字节的分离讲清楚,而 UTF-8 是让这套分离在互联网上跑起来的那条编码。
今天,UTF-8 是互联网上占主导地位的字符编码,覆盖约 98% 的网页,支持世界上几乎所有书写系统的字符。这张餐垫纸背面的涂写,是今天你能在网页上看到中文、阿拉伯文和表情符号的技术基础。
Go 语言:第二个操作系统级语言
汤普森晚年加入 Google。2007 年 9 月,他与罗布·派克、罗伯特·格里斯默(Robert Griesemer)开始设计 Go 编程语言,2009 年 11 月对外开源。
Go 的出发点是一个具体的工程痛点:在 Google 的代码规模下,C++ 的构建慢到以小时计,头文件依赖层层缠绕,而并发代码既必要又极易写错。三人的回答与 Unix 一脉相承——把语言做小:砍掉的特性远多于增加的,编译必须快到不打断思路。Go 用垃圾回收卸下内存管理负担,用 goroutine 加 channel 提供轻量级并发;这套并发模型的思想谱系可以追溯到托尼·霍尔(Tony Hoare)1978 年的 CSP(通信顺序进程)——并发单元之间不共享内存,而是靠传递消息协调,从机制上消解了一大类数据竞争。
Go 今天被广泛用于云基础设施(Docker、Kubernetes 都用 Go 写成),是云计算时代的系统语言担当。
图灵奖与"反英雄"气质
1983 年,汤普森与里奇共同获得图灵奖。次年他把获奖演讲写成《信任信任》("Reflections on Trusting Trust"),发表在《ACM 通讯》上。这篇短文问的不是某一家公司的产品有没有漏洞,而是一个制度问题:当你运行一份二进制时,你究竟在信任谁。
编译器夹在「你能读的源码」和「机器真正执行的代码」之间。审读源码只能检查你看见的那一层;把源码变成可运行程序的那道翻译,本身也是一份你无法仅凭阅读来核实的制度产物。汤普森的结论写得很直白:你无法信任自己没有从头造过的代码,更关键的也许是信任写出这些工具的人。编译器、汇编器、链接器,每一层都把信任从文本转移到了生产这些工具的机构与流程上。
后来的工程回应也是制度性的,而不是把源码再读一遍:用彼此独立的工具链交叉编译并比对结果,让构建可复现,把「我信任这份源码」改成「独立的第三方能否得到同一份产物」。信任的根不在你能读的文字里,而在能否被别的机构重复。这比后来多起供应链事件早了近四十年,预言的是制度缺口,不是某一种具体手法。
跨域连接
- 形态学:把功能切成小工具、用统一的文本流拼装,表达力来自组合而非单个工具的能力。这与少量语素按规则组合出无穷词形是同一策略,前提也相同:必须有一个稳定、可预测的接合面。推论可以立刻验证——一旦接口不统一(各家二进制格式、结构化输出),组合的收益当场消失。
- Unicode 与数字书写:那套编码把"从任意字节位置都能找回字符边界"写进了编码本身——首字节与续字节的高位模式互不重叠,流被截断后仍能重新同步。同时它对旧有的单字节文本逐字节兼容,于是既有工具无需改动就能转发。可用性来自这两条性质,而不是来自表达了多少字符。
- 教育与文凭:源码可读,使操作系统成了能被逐行讲授的教材,"读过内核"由此变成一代研究者共享的底层经验,比任何标准文件都更有效地统一了后来的设计直觉。这条链路断裂时后果也立刻显现:许可收紧后教学必须转向替代品,而一个学生自己写内核的故事正是在那条缝隙里开始的。
- 劳动与工作组织:他后来参与设计的语言,许多取舍——编译要快、语言要小、格式统一、长期拒绝复杂泛型——优化的是"大量中等水平工程师协作"这个目标函数,而不是单个程序的表达力。所以评价分歧其实来自评价者所处的组织规模:小团队嫌它啰嗦,大组织嫌别的语言失控。
- 软件供应链安全:《信任信任》的要害是审读源码不足以保证产物安全,因为编译器可以在编译它自己时重新植入后门——信任的根不在你能读的东西里。工程上的出路因此不是读得更仔细,而是让两条互不依赖的工具链产出可比对的结果,可重现构建正是在打断这条递归。
参考文献
- Thompson, K. Reflections on Trusting Trust. Communications of the ACM 27(8) (1984): 761–763. (图灵奖演讲,必读)
- Thompson, K. Regular Expression Search Algorithm. Communications of the ACM 11(6) (1968): 419–422. (非回溯正则匹配的原始论文)
- Condon, J. H. & Thompson, K. Belle Chess Hardware. In: Advances in Computer Chess 3, Pergamon Press (1982). (Belle 专用硬件的设计说明)
- Pike, R. & Thompson, K. Hello World or Καλημέρα κόσμε or こんにちは 世界. Proc. USENIX Winter Technical Conf. (1993). (UTF-8 的原始描述文档)
- Ritchie, D. M. & Thompson, K. The UNIX Time-Sharing System. Communications of the ACM 17(7) (1974): 365–375. (文件系统、进程与 shell 的公开描述)
- Yergeau, F. UTF-8, a transformation format of ISO 10646. RFC 3629, IETF (2003). (UTF-8 的互联网标准文本)
延伸阅读
- Kernighan, B. W. Unix: A History and a Memoir. Self-published (2019). (亲历者的历史叙述)