1971-1973,C语言和管道

在贝尔实验室的个个部门中Unix无疑是成功的。几个月后,Thompson和Ritchie开始为一个新的手册工作。1972年6月中旬发布了第二版。前言部分写着:

在这个手册发行后几个月内,系统自身和它的操作方式都有了巨大的变化。

...

有更多的人花费时间参与到UNIX的开发中来。感谢L.L.Cherry,M.D.McIlroy,L.E.McMahon,R.Morris,以及J.F.Ossanna做出的贡献。

最后,UNIX已经安装到了10台机器上。

第一批安装用户当然是研究部门和法务部门。新泽西中部以外的首位用户是Neil Groundwater,那时他在纽约电话中心工作(如今在Sun公司)。他告诉我说:

1972年我在滨州州立大学完成学士学位后加入纽约电话公司。那是计算机专业的毕业生就业是很困难的,我被雇佣的原因是我在大学里有一些使用迷你计算机(ADAGE图形系统)的经验。1972年春天我去新泽西维泊尼的贝尔实验室工作。我在曼哈顿有个房子并且没有汽车,周一早上坐汽车去维泊尼对我来说很方便,然后在那里找个旅馆住下周五再返回纽约。一周都住在公司附近让我很快就沉浸在工作中。我对工作的记忆最多的是一个分享Unix系统如何使用的回忆:这和从烟囱内部向上爬非常想,你不得不从底层的方方面面一点点的开始。系统的很多部分的确是有文档,但是就像我们很多年后所说的“去看源码,卢克”。

我在维泊尼工作小组的任务是将外部设备的分析工作机械化,其中包括纽约的ESS(电子交换系统)站点。ESS的机器在中央办公室的电传打字机上产生呼叫失败消息(我们称之为“TN08”)。虽然Unix主机并不连接控制“交换”,但可以使用巧妙的线路让一个调制解调器连接电传打字机,另一个调制解调器连接到我们办公室(曼哈顿麦迪逊达到330号14层),如此就可以发送信号到一个多路复用端口。

1972年夏天纽约站点的硬件运到了:

DEC PDP-11/20处理器
56Kb核心内存
高速纸带读写器
ASR-33 电传控制台
DEC tape
RK11/RK05磁盘 2.4Mb
RF11固定磁头磁盘(最初由两个,后来又添加了3个)
DC11(6路)终端
DM11 16路多路复用器

我的第一个编程任务是为DM11多路复用器重写驱动来处理多个多路复用器(目标是3个)...

Unix的shell程序存放在七页行式打印纸中。没有任何的程序库,直接使用系统调用控制读写。shell中有很多我们今天仍在期盼的特性:输入输出重定向(不使用管道),元字符扩展(虽然是使用/etc/glob的外部程序执行扩展),还有少量的shell脚本。

一个有问题的程序可以很轻松的覆写内核代码。PDP-11的halt命令是代码“0”,因此你可以想象使用寄存器指针清空一个内存引用会使CPU立即停止工作。你可以通过编写程序或者使用键盘快捷键转储一个用户程序,这就是故障后调试。

开发程序通常会在上班时间之外或者在维泊尼。在维泊尼开发机的终端机被放置在一个公共房间,当几个人同时工作时,一个员工在运行a.out(链接器的默认输出文件名)之前可以广播一句“即将运行危险程序!”,其他员工看到这个就要赶快保存他们的文件了。

当某个员工要使用线性打印机时也会呼出一个类似的广播。pr myfile > /dev/lp将会把文件发往打印机,因为打印机没有脱机缓冲或者锁定机制,当两个人同时发送数据,就会将文件内容混淆在一起打印。最先喊出“线性打印机!”的人获得优先使用权。

Unix系统通过向连接到中央办公室的调制解调器发送数据和ESS办公室通信。但在中央办公室接收信号并没有发送给ESS的电传设备,而是发给了一个硬拷贝终端(类似于TI700的热敏打印机)。因为DM11的收发速度是不同的,电传接收的速度是110波特,而发送速度是300波特。

搜集错误报告后使用FORTRAN编写了一个程序,包含特定设备的特征的报告会被整理到一份“异常报告”。不稳定的电话网络可能会导致错误报告发送给外部设备,而不是当呼叫没完成时交换设备重新试着呼叫并且向终端打印错误报告。实际上有几种不同的把报告类型,但在新系统中TN08总是第一个被排查的设备。

启动PDP-11/20需要从启动地址(一个磁盘启动地址和一个纸带启动地址)加载到控制台开关,然后按下执行开关。启动ROM从一个DEC的满是二极管的电路板上加载(一个二极管是一个字节位)启动指令。

1973年在同一站点,我们添加了一个类似PDP-11/20配置的机器。它有一个光学读卡器读取“合适”的卡片,卡片上由面板办公室(一种老式交换设备)的技术人员在发送器死机时用铅笔绘制数据。“面板死机发送器”是他们称呼像TN08这样分析ESS设备的术语。如果你收集到足够多的报告,计算机可以识别重复的内容并且帮助你定位问题的情况。

第三版在8个月后的1973年出现。贡献者名单添加了E.N.Pinson的名字,最重要的是:

最终,Unix的安装量达到了16台,超出期望。

目录之前的前言部分有一叠纸之多。在开始部分增加了一些章节,“如何通过终端交流”,“shell”,还有路径名,在“编写程序”和“文本处理”章节还有一些图表。它们以如下文字开头:

在Unix,要将程序源码输入一个文件,使用ed(1)。在Unix中有三个主要的编程语言,它们是汇编(参见as(1)),FORTRAN(参见fc(1)),和C(参见cc(1))。

C?C是什么?手册中关于cc的部分(1972年3月15日)告诉我们它是一个C语言编译器,并且引导你去看“C语言参考手册”。五年以后Brian Kernighan和Dennis Ritchie出版了The C programming Language,尽管手册更早的融合到了文档中。用这种新语言重写Unix(第四版)是极为重要的,而且C语言自身也成为计算机界重要的一部分。

Mike Mahoney向Dennis Ritchie询问关于C语言的设计细节:

C语言几乎就是Ken的B语言改进版。B语言实际上起源于系统的FORTRAN语言...总之,浪费了一天的时间后他意识到他一点也不想做一个FORTRAN编译器。所以他开发了非常简单的B语言,并且让它运行在PDP-7上,后来又移植到PDP-11。除了操作系统以外,辅助程序中不少都是用B语言写的。因为它是一个解释型语言,所以运行起来相当缓慢。对于B语言有两种看法,一种认为因为它的实现是解释器,所以它会一直很慢。第二种看法是,不同于我们之前用过的所有面向字设计的机器,我们现在用的机器是面向字节设计,而基于BCPL语言改进而来的B语言并不适合这种面向字节的机器。特别是B和BCPL都有名为指针的存储单元...这些对象在不同的机器上大小是不一样的,而B和BCPL都是面向相同对象大小设计的。从语言学上看这是B语言最大的限制。事实上不仅所有的对象都要有同样大小,而且所有指向对象的指针也不合适...因此,大概在同一时间,我尝试着向B语言加入类型,没过多久就又尝试着为它写一个编译器。语言第一次发生了改变。那时我们把更改后的语言叫做新的B语言,简称NB。它也是一种解释型语言,实际上我已经着手开发B的编译器...因为C语言是使用一个和它非常相似的语言写成,每一个步骤都很像...把它们都合并到C语言编译器,然后添加变量,添加类型结构。最后试着把它做成一个编译器。

编译器的基本结构机器码生成器的构建工作,基于我从印第安纳山贝尔实验室里某人听到的一个主意。尽管我从没有读到相关的论文,但是我从NB语言的代码生成器中得到了相关的技巧,然后成就了C。这一切都因为这位博士的论点。这个技术也运用到了用于交换系统和ESS机器的EPL语言,它支持了ESS编程语言。因此决定C语言成功两步中的第一步就是从B语言改进,加入类型结构,语法上不做太多其他的改变,然做出一个编译器。

第二部是一个缓慢的过程。它发生在几年之后,看起来真的有点慢。它来源于第一次试图用C语言重写Unix。Ken大概在1972年夏天开始尝试重写,但最后放弃了。这或许是因为他厌倦了或者还有其他什么原因的。但有两件事情是错误的,第一个是他的问题,他不能解决运行基础的协同程序,起始的多道程序——如何在进程间切换控制权,以及内核和不同进程间的关系。第二件事情是他没能解决合适的数据结构,在我看来这更重要一些。起初的C语言没有数据结构,因此创建对象表——比如进程表,文件表,以及各种表——是一件非常痛苦的事情...做起来非常笨重。我猜人们也在用FORTRAN做着同样的事情。

所有这些事让Ken在那个夏天放弃了。第二年,我添加数据结构,让编译器或许变得更好用一些,代码写起来也更方便。因此,在下一个夏天我们一起努力用C语言重写了整个Unix系统。

第三版中的创新内容还有管道。管道(还有过滤器)是非常简单的概念:它是把一个程序的输出连接到另一个程序的输入的同一机制。在管道出现之前,Dartmouth分时系统就拥有通讯文件,但那是一种无法通用的特殊方式。管道概念是由Doug McIlroy提出,在McIlroy的坚持下由Thompson具体实现(“它是我少有的在Unix管理中实行控制力的地方”)。McIlroy告诉Mike Mahoney:

大概在63年,Conway写了一篇关于协同程序的文章发表在CACM(ACM通讯)上。我在59,60年的时候发明了宏(宏是一个指令代指一些列的指令,它是一对多的映射)。如果你仔细思考宏,它主要包含了数据流的切换。我的意思是,你正在输入数据,突然你执行了一个宏调用,那就是说“在这里停止输入,去执行定义的命令”,而在宏的定义中你又会发现调用了另外一个宏。所以在64年之前,我就在某些地方谈论宏是一种“数据流调度器”。也是在64年,一篇文章一直钉在Brian的墙上,当我谈论缠绕的数据流就像花园里的水管时,他想通了一些事情。所以这个点子在我的脑袋里存在很长时间了。

在同一时间,Thompson和Ritchie正在他们的黑板上描绘一个文件系统,我在这个黑板上画出如何通过将进程连接在一起处理数据,我在寻找一种前缀语言将进程连接在一起。最后我失败了,因为你很容易就会说“cat这个,grep那个”,或者“谁去cat去grep”等等。你很容易就说出这些,从这里可以很容易看出我们习惯于这样说。但是这些命令都有很多的参数,而不仅仅只是简单的输入输出,它们有各种选项,在语法上很难将这些选项附加到前缀语句中的各个命令,cat到grep到who(例如cat(grep(who)))。句法上不知道该怎样描述清楚。所以我在黑板上用这种语言写的漂亮程序没有足够强壮去应对现实中的情况,我们后来的确也没有实现它。

在1970年到1972年这段时间我一次又一次的说,“我们要怎样才能把事情做好?”,我放弃了一个又一个的提议。突然有一天我想起一种shell的类似水管的语法,Ken说“我这就实现它!”。他已经听过很多诸如此类的提议,那天绝对是让人难以置信的一天,他说“我要把它做出来”。 他并没有完全按照我的提议用系统调用实现管道,而是发明了一种更轻量更好的方式,也就是我们今天一直在用的这种。他采用了我提出的笨拙语法。

他将管道放进Unix,他将这种概念(这是McIlroy指着黑板,上面写着:f>g>c)放到shell里,这一切只用了一晚的时间。那时候大多数的程序都不能接受标准输入,因为并不真的需要。所以它们都有一个文件参数,gerp有一个文件参数,cat也有一个。Thompson发觉这些程序不能符合管道的使用方式,那个晚上他把这些程序也一起修改了。我不知道他是怎么做到的,第二天早上我们有了一次狂欢。

后来成为PWD主管的Dick Haight对August Mohr说:

我很偶然的在他们实现管道的那天见到了实验室成员。显然对于每个成员,系统成功使用管道简直是一个太过美妙的事情。没人会拒绝放弃这个新发明。

在那晚之前,Unix没有“工具箱”的概念。管道的发明为接下来几年对软件的探索探索之路奠定基础,这种理念成为一种独特的哲学。每次提到工具箱,McIlroy都会说:管道创建了它。

Mahoney问他:“管道之后Unix看起来有什么不一样”?McIlroy回答:

是的,每个人都开始提到那种哲学,“这是Unix哲学。一个程序只做一件事情,并把事情做好。写不同的程序,让它们一起工作。让程序处理文本流,因为那是普适的接口”。在管道之前这些工具观点还没有形成,但管道出现后它们也都随之而来。

Brian Kernighan参与到工具和工具箱之中,但最初他只是对Thompson实现的管道做了一处修改。他用>替换了^。他对Mahoney说:

某种意义上说,管道让Unix成为真正实用的系统。如果没有它你并不是没法做很多这类事情,因为I/O重定向的出现要比管道早不少,虽然也没有特别早,但它确实是比管道要早。我的意思是,它并不是一个新主意。你现在用管道能做的大多数事情之前也是可以做到,只不过没有现在这么方便。我是说,这大概可以类比为用罗马计数法代替阿拉伯计数法。也就是说之前你并不是不能算数,只是会更难,可能在智力上有更多的约束。但现在这一切都被一个中间的箭头符号取代了,甚至在我不知道的情况下就发生了。我记得那个奇怪的>>符号,或者其他人用的什么符号,突然都变成一个竖线,所有事情都开始遵从这条规则。从那时起我可以用这种简洁的方式表达我要做的事情——比如运行who命令收集输出内容到一个文件里,通过wordcount数一数有多少行就知道有多少用户在线,然后说“看看把who连接到wordcount是如此的简单”。还可以把who连接到grep等诸如此类的组合。可以把这些从没有想过的命令放在一起合作,这是如此简单你每次只需要用键盘将它们组合在一起然后就能得到想要的结果。我觉得当我们开始有意识地考虑工具,然后你可以将它们组合在一起,如果你这样做了它们真的就会开始协作。

还有一件事,Doug McIlroy告诉我Thompson将管道符号替换成“|”,“在伦敦的一次谈话中,因为他实在无法忍受我丑陋的语法”。

依靠将程序组合在一起,而不是每个程序自身,Unix从此有了强大的力量。

Unix现在有自己的一套语言。它有自己的气质和哲学。在AT&T贝尔实验室它有一个少部分热心用户组成的小组,但它还没有真正的观众。

Unix哲学

  • 一个程序只做一件事,并把事情做好
  • 让程序互相协作
  • 让程序处理文本流,因为它是普适接口

译者注

results matching ""

    No results matching ""