IT 论文精读 · PAPER 33
Ken Thompson & Dennis Ritchie · Bell Labs · CACM 1974
你现在用的东西——Mac、iPhone、安卓手机、云端跑着的 Linux 服务器——追根溯源几乎都是同一个小系统的后代:UNIX。它 1969 年诞生在贝尔实验室,1974 年这篇论文第一次把它公开讲给全世界。它没发明什么惊天新硬件,而是用几个极其简单、又能互相拼接的点子,搭出了一个小到能塞进当年便宜机器、却好用到统治了此后半个世纪的操作系统。
在它之前,大型操作系统(当时的代表叫 Multics)追求「什么都能干」,结果又大又复杂、要昂贵的机器、还难用。更别扭的是:每种东西都有自己一套专门的用法——读硬盘一套命令、读磁带一套、跟打印机说话又一套;程序和程序之间也各说各话,很难拼到一起干活。
UNIX 的解法是「少即是多」,核心就两三招。
第一招:一切皆文件。不管是硬盘上的数据、键盘、打印机还是屏幕,在系统眼里都长成同一副模样——「一个文件」。于是「打开、读、写」这一套动作就能操作世上一切。好比家里所有电器都改用同一种插座:不用为每样东西记一套接法。
第二招:小工具 + 水管。UNIX 鼓励每个程序只干一件事、把它干好,然后提供一根「水管」(管道 pipe),把前一个程序的输出直接灌进后一个程序的输入。要完成复杂任务,不用写大程序,而是像搭乐高、接水管一样把小工具串起来——「列出在线用户 ┃ 数一数有几行」,一行就统计出在线人数。
第三招:树状文件夹。文件按一层套一层的文件夹组织成一棵树。你今天在电脑里点开的那一级级文件夹,就是从这里来的。
这几招合起来,让一个系统又小、又通用、还能自己长出新能力:想要新功能,就再写个小工具接进管道,不用改动系统本身。这种「小而正交的零件自由组合」的风格太成功了,以至今天几乎所有操作系统都继承了它,连它当年为自己发明的 C 语言也顺势成了编程世界的通用语。诚实说一句代价:它的权限管理很朴素(就几个读写标志),放到今天处处联网的世界已经不够细致,安全上得靠后人层层加固。
UNIX 用「一切皆文件 + 小工具用管道自由拼接 + 树状文件夹」几个极简点子,做出一个小到能跑在便宜机器、又通用到能无限组合的操作系统——今天的 Linux、macOS、安卓、iOS 都是它的思想后代。
想看文件系统结构图、fork/管道机制和它到底新在哪? → 切到精读版
UNIX 提出了一套极简、正交、可自由组合的操作系统设计:把文件抽象成无格式的字节流、让设备也表现为文件(一切皆文件)、把每个程序都接到统一的「标准输入/输出」上并用管道(pipe)自由串接、用 fork/exec 两步拆开进程创建、并首次用高级语言 C 写成整个系统。它证明了一小组精选的好想法足以撑起一个小而强的操作系统——今天几乎所有操作系统(Linux、macOS、Android、iOS、POSIX 世界)都是它的思想后代。
作者是 Ken Thompson 与 Dennis Ritchie,贝尔实验室(Bell Labs)。UNIX 始于 1969 年——Thompson 在一台没人用的 PDP-7 上写起来;这篇《The UNIX Time-Sharing System》1974 年发表于 CACM,是它第一次公开面世,描述的是当时的第三版。它上承对庞大 Multics 项目的反思(名字「UNICS」就是对 Multics 的双关调侃),下启 BSD、System V、Linux、macOS 整个操作系统谱系。两位作者因此获 1983 年图灵奖。
1960 年代末的大型分时系统(以 Multics 为典型)追求功能全面,代价是庞大、复杂、昂贵、难用。更深的痛点在接口层面:每类外设、每种文件往往各有一套访问方式——磁盘一套、磁带一套、终端一套;程序之间也彼此孤立,一个程序的输出很难顺手喂给另一个程序。想拼出复杂功能,往往只能再写一个更大的专用程序。
Thompson 想要的恰恰相反:一个小而顺手、供研究者自己天天用的交互式系统。核心问题是——能不能用极少数、彼此正交的原语(primitive),搭出一个通用、好用、还能自我扩展的系统?「旧世界的痛」可以一句话概括:大而全、专用接口林立、零件之间不可组合。UNIX 的全部设计,都是对这句话的回答。
UNIX 把文件定义得极其朴素:就是一串字节。系统不强加任何记录、块或内部结构——这串字节是文本、图片还是数据库,全由使用它的程序自己解释。这一步「什么都不规定」看似偷懒,实则是最大的通用性来源:任何数据都能装进同一种容器。
文件按树状目录组织,有唯一的根 /,用路径名(如 /usr/dan/x)从根一路定位到文件。关键设计是:目录本身也只是一种文件,它的内容是一张「名字 → 编号」的表;这个编号指向一个 i-node(索引节点),i-node 才真正记着文件的属主、权限、大小和数据块位置。名字与文件本体就此分离——目录里只有名字和 i-node 号,文件的「实体」在别处。
为什么这样设计:字节流最通用(不预设任何用途),路径树可无限扩展,而「名字/本体分离」让同一文件拥有多个名字(硬链接 link)、让改名和共享几乎不花代价。
这是 UNIX 最漂亮、也最有远见的一招:把每个硬件设备也做成文件系统里的一个特殊文件(special file),通常挂在 /dev 下。于是——往终端写字、往打印机送数据、往磁盘存文件,用的是完全相同的 open / read / write 系统调用。程序不必知道管道另一头是磁盘、终端还是磁带。
为什么重要:一套接口覆盖了世上一切 I/O。程序天生「设备无关」,它的输入输出可以被外部随意接到文件、终端或另一个程序上——这正是下面「重定向」和「管道」的地基。
打开一个文件,你拿到的是一个小整数——文件描述符(file descriptor)。之后 read / write / seek 全对这个整数操作,不区分顺序读还是随机读,缓冲等细节由系统藏起来。约定俗成:0 是标准输入、1 是标准输出(后来又补上 2 = 标准错误)。程序只管从 0 读、往 1 写,至于 0/1 接的是键盘、文件还是另一个程序,程序自己不用关心、也无法察觉——这就是 shell 里用 < 和 > 做输入输出重定向的原理。
命令解释器 shell 不是内核的一部分,而是一个可以随时替换的普通用户程序。正因为它只是普通程序,UNIX 才能把「每个程序只做一件事、都通过标准输入输出说话」这个约定,升级成真正的组合能力:用管道 | 把前一个程序的标准输出直接接到后一个程序的标准输入,像接水管一样串成流水线。经典例子 who | wc -l——who 列出在线用户、wc -l 数行数,两个各司其职的小工具拼起来就统计出在线人数,谁也不用为对方改一行代码。
新进程怎么诞生?UNIX 的答案出人意料地简单,且拆成两步。fork() 把当前进程整个复制一份,得到一个几乎一模一样的子进程;子进程再调 exec 把自己的内存映像替换成要运行的那个程序;父进程用 wait 等它结束。shell 执行一条命令,本质就是 fork + exec + wait;命令末尾加个 &,父进程就不等、把它丢到后台。为什么要把「造进程」和「装程序」拆成两步?因为在 fork 之后、exec 之前的那一小段空档里,子进程正好可以从容地重定向标准输入输出、接好管道,再变身成目标程序——重定向和管道的实现,全靠这个缝隙。
在当时,操作系统几乎都用汇编写死在某台机器上。UNIX 反其道而行:整个系统(含内核绝大部分)用高级语言 C 写成。好处是代码短、易读、易改,尤其是换一台机器时只需改动少量底层代码就能移植过去。作者自己把这一点视为 UNIX 成功最重要的原因——它让一个操作系统第一次能「跟着硬件走」,而不是被钉死在一台机器上。
这不是一篇靠跑分取胜的论文——它的「结果」是那个正在真实运转的系统本身。论文描述的第三版跑在一台普通的 PDP-11 上,内核常驻部分只有几十 KB 量级,却能在只有一两百 KB 内存的机器上撑起多个用户同时分时使用,并被贝尔实验室拿去干真活(尤其是文档排版)。它有力地证明了一件反直觉的事:不需要庞大复杂的系统和昂贵的硬件,一小组精心挑选、彼此正交的想法,就能搭出一个既小又强、还能不断自我扩展的操作系统。更硬的「结果」写在后面几十年——它以惊人的速度扩散到大学与业界,成了整个行业的共同底座。
很难找到影响更深远的系统论文。今天几乎所有操作系统都是 UNIX 的后代或深受其影响:Linux(云计算与安卓的底座)、BSD 与 macOS / iOS、以及把这套接口标准化的 POSIX。更重要的是,它留下了一整套通用词汇与思维方式——文件描述符、管道、fork、权限位、/dev、shell 脚本、树状目录、一切皆文件——今天每个程序员每天都在用,多数人甚至不知道它们出自这里。它的「小工具,每个只做好一件事,再用管道组合」的哲学,成了软件工程的一种经典范式;连它顺手发明的 C 语言也从这里走向世界。这份影响力,也是 Thompson 与 Ritchie 获得图灵奖的直接理由。
fork() 作为进程创建原语后来饱受批评:在现代多线程、大内存、多核的环境里,「先整个复制再替换」既低效又与线程语义冲突,有论文(A fork() in the road, 2019)直言它是个糟糕的抽象。① 一句话:UNIX 用一小组极简、正交、可自由组合的原语,做出一个又小又通用的分时操作系统。
② 痛点:此前的大型系统(如 Multics)大而全、专用接口林立、程序之间不可组合,还要昂贵硬件。
③ 文件=无格式字节流 + 树状目录;目录只存「名字→i 编号」,i-node 才记本体——名字与本体分离,带来链接、廉价改名与共享。
④ 一切皆文件:设备也是 /dev 下的特殊文件,和普通文件共用同一套 open/read/write,程序天生设备无关。
⑤ 文件描述符 + 标准输入输出(0/1/2)=重定向 < > 与管道 | 的地基;shell 只是可替换的普通程序。
⑥ 小工具用管道自由串接(who | wc -l),复杂任务靠组合而非写大程序——UNIX 组合哲学。
⑦ 进程=fork(整个复制)+ exec(替换映像)两步;拆成两步正是为了在缝隙里接好重定向与管道。
⑧ 整个系统用 C 写成 → 可移植,作者视之为成功首因;C 也由此走向世界。
⑨ 影响:Linux / macOS / Android / iOS / POSIX 皆其后代;文件描述符、管道、fork、shell 成为通用词汇;获 1983 图灵奖。
⑩ 局限:本版无网络;权限模型粗、setuid 有洞;字节流把结构推给应用;fork 被现代环境诟病;「Worse is better」两面性。