Multics 的失败与 Unix 的诞生

在这里插入图片描述

1969 年,贝尔实验室从 Multics 项目里撤出了自己的人。

留下的是一堆没写完的代码,和一台没人排队的 PDP-7。

Ken Thompson 在这台机器上开始写另一个系统。它要解决的问题和 Multics 一样:让多个人同时在线用一台机器。

它选的手段和 Multics 几乎相反。

今天你在一台板子上 echo 一行字符串进 /proc,就能指挥另一颗核。这份能力,源头就在那个反向的选择里。

在这里插入图片描述

一、Multics 想卖的是电

1960 年代的主流用法是批处理。程序写在卡片上,交给机房,几个小时后回来取结果。

机器只有一份,排队的是人。要用算力,先排一个时间。

Multics 要拆掉这条队。多人在线、同时用、像打电话一样随时可用。

项目由 MIT、贝尔实验室、通用电气三家合建。目标是“计算机公用事业”:算力像电一样从墙上取,插上就用。

电的特点是用户不需要知道电从哪来。对应到计算机上,就是三个“不该知道”:

用户不该知道文件在哪,不该知道内存还剩多少,不该知道程序被加载到了哪个地址。

这三条是硬指标。要让它们同时成立,系统得自己维护一套统一的地址空间,和一套能表达细粒度权限的模型。

靠应用各自去管,做不到。

于是 Multics 把文件、内存、权限全收进了内核的地址翻译这一层。

1969 年,贝尔实验室退出项目。

二、抽象没错,机器没跟上

Organick 在 1972 年那本书里把设计摊开写了一遍。今天翻,里面几乎每一条都是现代系统的标配。

  • 单一层存储:文件与内存共用一套段寻址,程序不用区分“在盘上”还是“在内存里”。
  • 段式虚拟内存:段有名字、长度可变、可共享,按需调入。
  • 环形保护:权限按 ring 分级,不只有内核态和用户态两档。
  • 动态链接:调用别的段的时候才链接,不必在编译期静态拼进来。

这四条都不是小修小补,每一条都要硬件配合。段表要硬件走,按需调入要硬件触发缺页,ring 要硬件记当前级别。

硬件不给,软件只能拿性能去换。

1969 年的机器也不给。目标机器的性能扛不住这个规模,系统本身也超出了当时能交付的复杂度。

另一头的成本是协作。三家单位共建一个操作系统,接口、进度、验收标准都要对齐。收益还没见到,账先到期了。

所以“Multics 失败”这句话要写准确。失败的原因不是那套抽象,是硬件与协作的成本。

后来的 Windows NT、macOS 的分页加段保护、动态链接,血缘都在这条线上。

超前十年和选错方向,是两件不同的事。

在这里插入图片描述

三、把设备降级成文件

Unix 没有推翻 Multics 的目标,它换掉了手段。

取舍只有一句:抽象能复用就复用,不新增系统调用。

Ritchie 和 Thompson 在 1974 年的论文里把结果写清楚了。文章发在《Communications of the ACM》17 卷 7 期,标题就叫《The UNIX Time-Sharing System》。

结论很短:文件是字节流,目录是文件,设备也是文件。三者从同一个命名空间里 open,然后用同一组 read、write 操作。

一句话换来三个后果。

第一个是驱动接口统一。驱动只要实现读、写、定位、控制几个入口,调用方不必知道对面是磁盘、串口,还是一段内核现算出来的文本。

落到 Linux 上,这几个入口就是一组函数指针。主设备号是选哪张表的索引,次设备号是传给表里函数的参数。

加一种新硬件,只要注册一张新表,用户态一行代码都不用改。

第二个是 shell 可以组合一切。管道把任意两个“文件”接起来,两个程序之间不用约定协议,只要一个是写端、一个是读端。

这条后果的威力在几十年后才显出来:cat、grep、sort 这些各自只做一件事的小程序,靠管道拼成了完整的工具链。

第三个是新东西都要伪装成文件。Research Unix 把进程状态也塞进了文件系统,才有了 /proc 这一支。

在这里插入图片描述

四、降级的账单

省下来的复杂度要还。文件语义被压平以后,有些东西塞不进去。

/proc 里的文件没有固定长度。ls -l 报的 size 常常是 0,wc -c 却能读出内容。

read 一次也拿不全,得循环到返回 0:

while ((n = read(fd, buf, sizeof buf)) > 0)
    write(1, buf, n);

lseek 对它不一定有意义。背后没有“第 N 个字节”,只有一个现算现给的生成器。

想确认一个节点到底是真文件还是生成器,看这两条就够了:

ls -l /proc/self/status     # size 常报 0
wc -c /proc/self/status     # 读出来却不是 0 字节

数据面和控制面也被拆开了。往设备节点写字符串能送数据,改波特率却不行。

参数只能走 ioctl,或者走 sysfs 里的属性文件。有些操作天生不是字节流,比如“创建一个端点”,用 write 表达不出来,只能再开一个入口。

这就是“一切皆文件”的真实边界:能用字节流表达的,open 一次就够;表达不了的,还是得留一个专用调用。

判断一个功能适不适合挂进文件树,先问它有没有“位置”这个概念。有位置、能顺序读写的,挂进去很自然;没有位置的,挂进去只能得到一个别扭的接口。

五、fork 和 exec 也是同一批取舍

同一批决定里还有一条容易被忽略:Unix 把“创建进程”和“加载程序”拆成了两步。

fork 复制当前进程,exec 把地址空间换成另一个可执行文件。

别的系统通常只给一个调用:用某个可执行文件建一个进程。Unix 把这一步拆开,代价是多一次地址空间复制。

好处是 shell 能在两步之间动手。重定向、管道、权限、工作目录,全插在这一格里。

子进程先 fork 出来,还没 exec 的时候,文件描述符已经被换成了管道的一端。等 exec 换上新程序,它看到的标准输入已经指向别处了。

重定向不用改程序,只需要改这一刻的进程状态。这也是 shell 能存在的前提。

今天 popen() 起的每一个子进程,走的还是这两步。

在这里插入图片描述

六、Multics 留下的东西,比它失去的多

Multics 常被写成失败案例,但它输掉的是项目,不是技术。

今天几乎所有主流系统都带着它的四个发明:

Multics 的做法今天的对应物你能在哪看到
段式虚拟内存进程地址空间的段映射cat /proc/self/maps
环形保护(ring 0-7)内核态 / 用户态两级dmesg 里的 protection fault
单一层存储(文件就是内存)mmap 一个文件mmap(2) 的返回值
动态链接ld.so 与 .soldd /bin/ls

它真正输在工程上:目标太大,一台机器要同时做分时、分段、多处理器、高可用,任何一项没做完,整台机器就不可用。贝尔实验室退出时给的评价很直接——系统能跑,但每一次演示都是一次赌博。

Unix 走了相反的路:先把"多人在线"这一件事做对,其余全部推迟。推迟的结果是可运行,可运行的结果是有人愿意继续改,继续改的结果才是可移植。

七、动手核一次

"设备即文件"这条继承自 Multics 的抽象,在板子上能一行验证:

ls -l /dev/null /dev/zero /dev/tty
echo hello > /dev/null; ls -l /dev/null

输出里每个设备都有主次设备号(1, 3 / 1, 5 / 5, 0)。内核分支不在"文件"这一层,而在打开时按设备号找到驱动;用户态只看到 open/read/write/close 四个动词。

再往下走一层,看这套抽象在进程里长什么样:

ls -l /proc/self/fd/

0 1 2 三个软链分别指向 /dev/pts/N、/dev/pts/N、/dev/pts/N——终端、终端、终端。重定向的本质就是把这三个软链换掉,而不是改程序。

八、名字是反着起的

贝尔实验室的人离开 Multics 之后,Thompson 和 Ritchie 在 PDP-7 上写另一个系统。他们给它起的名字叫 UNICS——Un-multiplexed Information and Computing Service,正面冲着 Multics 来的:不搞多路复用,不当公用事业,一台机器一个人用就行[6]。

这个命名里有判断:Multics 想把算力做成电网,Unix 只想把一台机器分给几个人。方向一小,剩下的每一处取舍都跟着变小——不要分段硬件,用软件模拟;不要七层环保护,两级就够;不要全局文件存储,一棵树就够。

后来让它活下来的不是设计,是可移植。1973 年系统用 C 重写,同一份代码换台机器重新编译就能跑[7]。可移植意味着别人跑得起来,跑得起来才有人愿意改,有人改才谈得上传播。

Multics 也没有当场死。它一直跑到 2000 年 10 月 30 日,最后一台机器在加拿大 Halifax 关机,寿命比绝大多数操作系统都长[6]。输掉的是项目规模,不是技术路线——今天你 cat /proc/self/maps 看到的段映射、用 mmap 把文件当内存、.so 的动态链接,都能追到那栋楼里。

九、第一个真实用户是专利部门

Unix 诞生后接到的第一个真实需求,不是操作系统研究,是排版:贝尔实验室的专利部门要处理大量的文本申请[9]。

于是有了 roff,以及后来被移植到各处的 troff。为了让文本能在不同程序之间流动,管道、重定向、stdin/stdout 这些机制被反复打磨——文本流成了默认的接口,因为它恰好是排版需要的形状[10]。

这条线索解释了 Unix 为什么长得像现在这样:一切皆文件、小工具组合、纯文本配置。它们不是抽象原则,是一次具体任务的副产品。

对比 Multics:那边的目标是"通用公用事业",接口要覆盖一切;Unix 只被要求"把这批文档处理掉"。任务越具体,抽象越锋利。

最后

1969 年那个决定,说到底是把复杂度从系统里搬到了名字空间里。

Multics 想让用户不必知道东西在哪,Unix 让所有东西都能被同一个动词打开。

前者要求硬件和协作都跟上,后者只要一个 open。所以活下来的是后者。

一台板子上 echo 一行字符串进 /proc 就能指挥另一颗核,欠的就是这份降级。

下一篇:《Unix 三分天下:贝尔实验室、BSD、System V》。

你在自己的机器上,第一次意识到“这也是个文件”,是哪个路径?
在这里插入图片描述

参考依据

  • [1] notebook
  • [2] Elliott I. Organick. The Multics System: An Examination of Its Structure. MIT Press, 1972. https://multicians.org/
  • [3] Dennis M. Ritchie, Ken Thompson. “The UNIX Time-Sharing System.” CACM 17(7), 1974. https://dl.acm.org/doi/10.1145/361011.361061
  • [4] The Unix Heritage Society — 原始源码、手册与档案 https://www.tuhs.org/
  • [5] docs/Comake D1/项目实战/第0章 通用技术基础.md —— 用户态 /dev/rpmsg_ctrl0 与 /proc/dualos 通道
  • [6] docs/hardware/板卡IO电压域与串口资源.md —— 串口节点与 console=ttyS0
  • [7] Dennis M. Ritchie,The Evolution of the Unix Time-sharing System(Bell Labs,1979/1984) https://www.bell-labs.com/usr/dmr/www/hist.html
  • [8] Dennis M. Ritchie,The Development of the C Language(HOPL-II,1993) https://www.bell-labs.com/usr/dmr/www/chist.html
  • [9] Dennis M. Ritchie,The Evolution of the Unix Time-sharing System(排版与文本工具的来源) https://www.bell-labs.com/usr/dmr/www/hist.html
  • [10] The Unix Heritage Society,roff/troff 与早期手册 https://www.tuhs.org/cgi-bin/utree.pl
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐