Chinaunix首页 | 论坛 | 博客
  • 博客访问: 708235
  • 博文数量: 19
  • 博客积分: 5025
  • 博客等级: 大校
  • 技术积分: 290
  • 用 户 组: 普通用户
  • 注册时间: 2006-01-24 10:41
个人简介

null

文章分类

全部博文(19)

分类:

2006-01-24 14:45:48

 

Unix 分时系统的演化

Dennis M. Ritchie
Bell Laboratories, Murray Hill, NJ, 07974

译者声明:译者对译文不做任何担保,译者对译文不拥有任何权利并且不负担任何责任和义务。
原文:

摘要

本文提供对 Unix 操作系统早期开发的简要描述。它集中在文件系统、进程控制和管道方式命令的想法的演化上。对系统开发期间的社会环境也有所关注。

注意: *本文首次发表于“Language Design and Programming Methodology conference at Sydney, Australia, September 1979”。会议记录作为讲稿出版于“Computer Science #79: Language Design and Programming Methodology, Springer-Verlag, 1980”。这份稿件所基于的重印版本出现在“AT&T Bell Laboratories Technical Journal 63 No. 6 Part 2, October 1984, pp. 1577-93”。

介绍

在过去几年中,Unix 操作系统已经得到广泛的使用,以至于它特别的名字已经成为 Bell 实验室的一个商标。它的重要特征已经被众人所知晓。自从 1974 [1] 中所描述的那个首次发行之后,它经历了大量重写和修补,但是少有原则上更改。但是,Unix 生于 1969 年而不是 1974 年,它的开发历程成了一个鲜为人知并可能有所教益的故事。本文提供系统演化的技术和社会环境的历史记录。

起源

对于 Bell 实验室的计算机科学,1968-1969 年这段时期有些动荡。主要原因是实验室缓慢的、但完全不可避免的、从 Multics 计划中撤出。对于整个实验室计算社区,问题是让 Multics 迅速交付某种可用的系统是日益明显的错误,更不用说最初幻想的那个万能系统了。在此期间,Murray Hill 计算机中心还运行着一个昂贵的 GE 645 机器来不适当的模拟 GE 635。在此期间发生的另一个剧变是计算服务和计算研究在组织上的分离。

出自 Unix 开始时主要涉及到小组(K. Thompson,Ritchie, M. D. McIlroy, J. F. Ossanna)的立场,Multics 的倾覆有着直接感觉到的影响。在 Bell 实验室,我们是在 Multics 坚持实际工作的最后的人,所以我们仍寄希望于它的成功。更重要的是,Multics 对整个社区承诺的方便的交互式计算服务对于我们这个有限的小组是事实上可以获得的,最初曾在 CTSS 系统下开发 Multics,而后来是在 Multics 自身下。尽管 Multics 当时不能支持许多用户,它却能支持我们,尽管有高昂的花销。我们不想失去我们占据的舒适的小环境,因为不能获得类似的东西;甚至在 GE 操作系统下将在以后提供的分时服务都不存在。我们想要保留的不只是在其中编程的一个良好环境,还有可以围绕它组成团队的一个系统。我们获得的经验是公共计算的本质,如提供远程访问、分时机器,不只是在终端上键入程序来替代键盘穿孔机,还有鼓励密切交流。

所以在 1969 年期间, 我们开始尝试找出 Multics 的替代者。找寻采用了多种形式。整个 1969 年我们(主要是 Ossanna、Thompson、Ritchie)密集的游说获得一个中型机器,我们承诺为它写个操作系统;我们建议的机器是 DEC PDP-10 和 SDS (后来的 Xerox) Sigma 7。努力受挫了,我们的提议从未被明确的最终的否决,但也从未被明确的接受。有几次非常接近成功。在我们提出了一个敏锐的复杂提议的时候,对这个努力的最后的打击到来了。谋划要最小化财务费用,涉及到某种直接购买,某种第三方租借,并计划购进一个即将推出的 DEC KA-10 处理器和一个更易接受的 KI-10。提议被拒绝了,传言说 W. O. Baker(研究部的副主管)用‘Bell 实验室不能这样做事!’的评论来反对它。

实际上,回想起来这是非常明显的(并且当时也应该如此),我们要求实验室花费大量的金钱在这么少的人身上去进行这么模糊的一个计划。此外,我非常确定那时操作系统对于我们的管理者不是支持工作的一个有吸引力的领域。他们正在进行的不仅是从开发操作系统的努力失败中解脱自身,还有免于运行本地的计算中心。所以我们建议的买一个机器,一方面可能导致又一个 Multics,另一方面,如果我们生产了有用的东西,会导致他们需要负责又一个计算中心。

在 1969 年除了发生财务困扰之外,还做了技术性工作。在黑板上和潦草的笔记上,Thompson、R. H. Canaday 和 Ritchie 开发了一个文件系统的基本设计,它后来成为 Unix 的心脏。多数设计出自 Thompson,他是思考整个文件系统的推动力,但是我确信我贡献了设备文件的想法。Thompson 对创建一个操作系统的渴望在这个时期经历了多种形式;他还写了(在 Multics 上)对草拟的文件系统设计和程序的分页行为的一个非常详细的模拟。此外,他开始在 GE-645 的一个新操作系统上工作,深入到为这个机器写一个汇编器和一个初步的内核,我所记得它的最大成绩是键入一个问候消息。这个机器是如此的复杂性以至于基本的消息已经是一个非常显著的成就了,但在明确了 645 在实验室的时日要用月来衡量的时候,这项工作被放弃了。

也是在 1969 年期间,Thompson 开发了一个‘太空旅行’游戏。最初在 Multics 上写的,接着转移到  GECOS (GE 和后来的 Honeywell 635 上的操作系统)的 Fortran 中,只是模拟太阳系的主要成员的移动,玩家驾驶飞船四处旅行,观赏风景,并尝试在各个行星和月亮上的登陆。GECOS 版本在两方面是不能让人满意的: 首先,游戏的状态显示是别扭的并难于控制,因为你必须在上面键入命令,还有,游戏在这个大机器上花费大约 $75 的 CPU 时间。它不能占用很长时间,所以 Thompson 找到一个带有卓越的显示处理器的很少使用的 PDP-7 计算机;整个系统被用作一个 Graphic-II 终端。他和我重写了太空旅行来在这台机器上运行。事情比看起来更加野心勃勃;因为我们蔑视所有现存的软件,我们必须写一个浮点算术包,这个显示器的图形字符的逐点指定,和一个调试子系统,它在屏幕角上的位置持续的显示键入的内容。所有这些都是用在 GECOS 下运行的一个交叉汇编器的汇编语言写成的,并生成纸带来传送到 PDP-7 上。

太空旅行,尽管它是一个非常吸引人的游戏,主要用来介入为 PDP-7 准备程序需要的笨拙的技术。不久 Thompson 开始实现以前设计的纸面文件系统(可能‘粉笔文件系统’更准确些)。没有办法实验的一个文件系统是没有结果的一个命题,所以他继续用工作操作系统的其他需要来充实它,特别是进程的概念。接着出现了一小组用户级别实用工具: 复制、打印、删除和编辑文件,当然还有一个简单的命令解释器(shell)。直到这时所有的程序都使用 GECOS 写成并转移到 PDP-7 纸带上;而一旦汇编器完成了系统就可以支持自身了。直到 1970 年 Brian Kernighan 推荐了这个名字‘Unix’,它是‘Multics’有些背叛的双关语,我们今天知道的这个操作系统就诞生了。

PDP-7 Unix 文件系统

在结构上,PDP-7 Unix 的文件系统几乎同今天完全一样。它有:

1)
i-list: i-node 的一个线性数组,每个描述一个文件。i-node 包含的比现在要少,但基本信息是一样的: 文件的保护模式,它的类型和大小,和持有内容的物理块的列表。
2)
目录: 一种特殊的文件,包含名字和相关的 i-number 的一个序列。
3)
描述设备的特殊文件。在 i-node 中不明确的包含设备说明,而是用数字编码: 特定的 i-number 对应于特定的文件。

重要的文件系统调用也在开始时就出现了。Read、write、open、creat (sic)、 close: 有一个非常重要的例外将在后面将讨论,他们都类似于今天你所见到的。一个次要的区别是 I/O 的单位是字而不是字节,因为 PDP-7 是一个字寻址的机器。在实践中仅意味着处理字符流的所有程序都要忽略 null 字符,因为 null 被用来把文件填充成偶数个字符。另一个次要的区别,偶尔讨厌的区别是终端缺乏字和行删除处理。终端实际上总是在原始模式下。只有一些程序(特别是 shell 和编辑器)费力的去实现删除处理。

尽管相当类似于目前的文件系统,PDP-7 文件系统在一方面上有显著的不同: 这里没有路径名字,给系统的每个文件名字参数都是相对于当前目录的简单的名字(没有‘/’)。在通常 Unix 意义上的连接不存在。与一组精心安排的约定一起,它们是使路径名的缺乏能够被接受的主要方式。

link 调用有如下形式

link(dir, file, newname)

这里的 dir 是在当前目录中的一个目录文件,file 是在那个目录中的一个现存的条目,而 newname 是这个连接的名字,它被增加到当前目录。因为 dir 必须在当前目录中,很明显的,现在的禁止到目录的连接还不是强制的;PDP-7 Unix 文件系统是一般有向图的形状。

所以每个用户不需要维护到所有感兴趣的目录的一个连接,有一个叫做 dd 的目录为每个用户包含一个目录条目。这样,要制作到在 ken 目录中 x 的文件的一个连接,我可以做

ln dd ken ken
ln ken x x
rm ken

这个方案提供了非常难用的子目录,这使它们在实践中不被使用。另一个重要的阻碍是在系统运行期间无法建立目录;所有的目录都是在从纸带重建文件系统的时候建立的,所以目录实际上是不可更新的资源。

dd 约定使 chdir 命令相对的方便了。它接受多个参数,并把当前目录依次切换成每个指名目录。所以

chdir dd ken

将移动到目录 ken。(顺便提一下,chdir 拼写为 ch;但我不记得在我们转到 PDP-11 的时候为什么要展开它。)

这个文件系统实现的最严重的麻烦,除了缺乏路径名字之外,还有难于更改它的配置;如前所述,目录和特殊文件只能在磁盘建立的时候制作。安装一个新设备是非常痛苦的,因为设备的代码广泛的散布系统中;例如有依次访问每个设备的多个循环。不要惊讶,没有挂装可移动磁盘组件的想法,因为这个机器只有一个单一的 fixed-head 磁盘。

实现文件系统的操作系统代码是当前方案的彻底的简化版本。一个重要的简化来自系统不是多道程序(multi-programmed)的事实;内存中在一个时刻只有一个程序,并只在发生对换的时候在进程之间传递控制。所以,例如这里有一个 iget 例程使一个指名的 i-node 可获得,但它把这个 i-node 留在一个恒定的、静态的位置中,而不是返回到一个活跃 i-node 的大表的一个指针。存在着当前的缓冲机制的祖先(有大约 4 个缓冲区),但本质上没有磁盘 I/O 和计算的重叠。这不只是为了简化而避免的。附加在 PDP-7 上的磁盘在那时是很快的;它每 2 微秒传输一个18-位字。另一方面,PDP-7 自身有一个 1 微秒的内存周期节拍,并且多数指令占用 2 个周期(一个用于指令自身,一个用于操作数)。但是,间接寻址指令要求 3 个周期,并且间接寻址是很常用的,原因是这个机器没有变址寄存器。最后,DMA 控制器不能在指令运行期间访问内存。结果是,如果在磁盘传输期间执行了任何间接寻址的指令,则磁盘就会招致溢出丢失(overrun)。这样在磁盘正在运行的时候,控制不能返回给用户,事实上也不能执行一般的系统代码。时钟和终端的中断例程,它们需要在所有时候都是可运行的,必须以非常奇怪的方式编码来避免间接寻址。

进程控制

对于‘进程控制’,我解释为进程建立和使用的机制;今天的系统调用 fork、exec、wait 和 exit 实现了这些机制。不象文件系统,它基本上在最近的时候就存在,进程控制方案在 PDP-7 Unix 已经使用之后经历了显著的突变。(在 PDP-11 系统中路径名的介入的确是一个可观的表示法改进,但不是基本结构上的改变。)

今天,shell 执行命令的方式可以总结如下:

1)
shell 从终端读取一行命令。
2)
它用 fork 建立一个子进程。
3)
子进程使用 exec 来从文件调入这个命令。
4)
此时,父 shell 使用 wait 来等待子(命令)进程通过调用 exit 来终止。
5)
父 shell 回到步骤 1)。

进程(独立执行的实体)存在于非常早期的 PDP-7 Unix 中。事实上正好有两个,附属于这个机器的两个终端一人一个。这里没有 fork、wait 或 exec。这里有 exit,但是它的意思是非常不同的,一会就能见到。shell 的主循环按下面这样运行。

1)
shell 关闭所有它打开的文件,接着是为标准输入和输出(文件描述符 0 和 1)打开的终端特殊文件。
2)
它从终端读一行命令。
3)
它连接到命令详细指定这个命令的文件,打开这个文件,并删除这个连接。接着它复制一个小引导程序到内存的顶端并跳转到那里;这个引导程序读入这个文件覆盖 shell 代码,接着跳转到这个命令的最先的位置(效果上是 exec)。
4)
这个命令做它的工作,接着通过调用 exit 终止。exit 调用导致系统读入 shell 的新鲜的拷贝覆盖终止了的命令,接着跳转到它的开始(这在效果上等同于跳转到步骤 1)。

关于这个原始实现的最有趣的事情是它预示(anticipate)了后来这个主题充分开发的程度。真的,它不支持后台进程,也不支持 shell 命令文件(更不用说管道和过滤器);但是 IO 重定向(通过‘<’和‘>’)不久就有了;下面会讨论它。重定向的实现非常直接;在上面步骤 3) shell 只是把它的标准输入或输出替代为适当的文件。对后续开发最至关紧要的是把 shell 实现为存储在一个文件中的一个用户级别程序,而不是操作系统的一部分。

这个进程控制方案的结构,是每个终端一个进程,类似于许多交互式系统,例如 CTSS、Multics、Honeywell TSS 和 IBM TSS 及 TSO。一般的这种系统要求特殊的机制来实现有用的设施,比如分开的计算和命令文件;Unix 在这个发展阶段不费心去提供这种特殊机制。它还展示了一些恼人的、特殊性质的问题。例如,一个新近重建的 shell 必须关闭所有它打开的文件,除去刚才执行的命令所有打开的文件,废除以前的 IO 重定向。接着它必须重新打开对应于它的终端的特殊文件,来读取一个新命令行。没有 /dev 目录(因为没有目录名字);此外,shell 不能跨越命令保留任何记忆,因为在每个命令之后它都被重新再次执行。这样需要一个进一步的文件系统约定: 每个目录都必须为对应于打开它的进程的终端包含一个 tty 条目。如果你意外的进入缺失这条目的一个目录,shell 将无望的循环;唯一的补救就是重新启动。(有时缺失连接可能是其他终端造成的。)

现代形式的进程控制是在几天之内设计并实现的。令人惊异的是它如此容易的适合现存系统;同时也很容易看出来,设计的某些稍微不同寻常的特征恰好出现,是因为它们体现了对现存的东西做简单的、易于编码的变更。一个好的例子是 fork 和 exec 功能的独立。建立新进程的最通用的模型涉及指定一个程序让这个进程来执行;在 Unix 中,一个复制的(forked)进程继续运行同它的父亲一样的程序直到它进行一次明确的 exec。这种功能的独立的确不是 Unix 独一无二的,并且实际上它是在 Berkeley 分时系统[2] 中出现的,Thompson 很熟悉它。尽管如此,它存在于 Unix 中最合理的原因主要是可以容易的实现 fork 而不用很多其他东西。系统已经处理多个(这时是两个)进程;有一个进程表,并且进程在主存和磁盘之间对换。fork 的最初实现只要求

1)
扩充进程表。
2)
增加一个 fork 调用,使用现存的对换 IO 原语把当前进程复制到磁盘对换区域,并对进程表作一些调整。

事实上,PDP-7 的 fork 调用需要正好 27 行汇编代码。当然,需要操作系统和用户程序的一些其他变更,并且其中一些是非常有趣和意外的。此外组合的 fork-exec 相当的复杂,好像是因为 exec 同样的不存在;shell 使用明显的 IO 已经完成了它的功能。

exit 系统调用,它以前是读入 shell 的一个新拷贝(实际上是一种自动的没有参数的 exec),相当的简单;在新版本中进程只需要清除它的进程表条目,并放弃控制。

奇怪的是,变成 wait 的原语比当前方案更加一般化。一对原语在指名的进程之间发送一个字的消息:

smes(pid, message)
(pid, message) = rmes()

smes 的目标进程不需要与接收者有任何父子相传的关系,除了 fork 向父进程和子进程返回与它们有关的 ID 之外,系统不提供任何交流进程 ID 的明显的机制。消息不排队,发送者延迟直到接收者读到这个消息。

消息设施使用如下: 父 shell,在建立一个进程去执行一个命令之后,通过 smes 发送一个消息到这个新进程;在这命令终止的时候(假定它不尝试读任何消息),这个 shell 的阻塞的 smes 调用返回一个错误指示目标进程不存在。所以 shell 的 smes 在效果上等价于 wait.

一个不同的协议,它更多的利用了消息提供的通用性,是在初始化程序和每个终端的 shell 之间使用的。初始化进程,它的 ID 被当作是 1,为每个终端建立一个 shell,并接着发起 rmes;每个 shell,在它读到它的输入文件的尽头的时候,使用 smes 来发送一个常规的‘I am terminating’消息到初始化进程,它为这些终端重建一个新的 shell 进程。

我不能想起消息的其他用途。这解释了为什么这个设施被当前系统的 wait 调用所替代,少了通用性,但更直接的适用于想要的目的。还可能与这个机制中明显的缺陷有关: 如果一个命令进程尝试使用消息来与其他进程通信,它将破坏 shell 的同步。shell 依赖发送用不被接收的消息;如果一个命令执行了 rmes,它将收到 shell 的假冒消息,并导致 shell 读另一行输入,如同这个命令已经终止了一样。如果证明需要通用性的消息,则这个缺陷可能已经被修补好了。

在某种程度上,新的进程控制方案立即呈现出某些非常有价值的、但对实现不重要的特征;例如分离的进程(使用‘&’) 和递归的把 shell 作为命令使用。多数系统必须为文件提供某种特定的‘批作业子任务’设施和特定的一个命令解释器以区别于交互使用的命令解释器。

尽管多进程的想法确实非常容易的就介入(slipped in)了,但有些后果是未预期到的。其中最难忘的是在新系统提出并显然的工作了之后不久就变得明显了。在我们庆祝的时候,发现 chdir (改变当前目录)命令停止了工作。关于增补的 fork 如何破坏 chdir 调用做了大量的代码阅读和观念的反省。最终真相揭晓了: 在老系统中 chdir 是一个普通命令;它调整附属在这终端上的进程的(唯一的)进程的当前目录。在新系统中,chdir 命令正确的改变了为执行它而建立的进程的目录,但是这个进程迅速就终止了并对它的父shell 没有任何影响! 必须使 chdir 成为一个特殊命令,在 shell 内部执行。找出了许多命令式的功能有同样的性质,例如 login。

在系统过去和新的进程控制方案之间的另一个不匹配花了很长时间才变得明显。最初,与每个打开的文件相关联的读/写指针存储在打开这个文件的进程中。(这个指针指示在文件中下次读或写发生的位置。)  这个组织的问题只在我们尝试使用命令文件的时候才变得明显。假定一个简单的命令文件包含

ls
who

并被如下执行:

sh comfile >output

事件序列是

1)
主 shell 建立一个新进程,它打开 outfile 来接收标准并递归的执行 shell。
2)
新 shell 建立另一个 process 来执行 ls,它正确的写在文件 output 上并接着终止。
3)
建立另一个进程来执行下一个命令。但是,output 的 IO 指针是从 shell 复制来的,它仍是 0,因为 shell 从未在它的输出上写过了,而 IO 指针是与进程相关联的。效果上是 who 的输出覆写并销毁了前面的 ls 命令的输出。

这个问题的解决要求建立一个新的系统表来持有打开文件的 IO 指针,独立于打开它们的进程。

IO 重定向

使用‘>’和‘<’字符的、非常方便的 IO 重定向符号在最早的 PDP-7 Unix 系统中不存在,但是它确实出现的很早。象 Unix 的多数其他部分,它的灵感来自 Multics 的思想。Multics 有非常通用的 IO 重定向机制 [3],具体为命名 IO 流,它可以被动态重定向到各种设备、文件、甚至通过特殊的流处理模块。甚至在我们十年前熟悉的 Multics 版本中,就存在一个命令把后续的正常预定到终端的输出切换文件上,和另一个命令来把输出重新附属到终端上。在 Unix 下可以写

ls >xx

来得到在 xx 中的你的文件的一个列表,在 Multics 上表示为

iocall attach user_output file xx
list
iocall attach user_output syn user_i/o

尽管这是在 Multics 时代经常使用的笨拙的序列,并被完全直接集成到 Multics shell 中,在那时这个想法并未出现在我们或其他任何人那里。我推测其原因是 Multics 计划的 sheer size: IO 系统的实现者是在 Murray Hill 的 Bell Labs,而 shell 是在 MIT 做的。我们不能考虑更改那个 shell(那是他们的程序);相对的,shell 的监管者可能不知道 iocall 的这种使用,尽管笨拙。(The 1969 Multics manual [4] 列出 iocall 作为一个“作者维护”的非标准命令)。因为 Unix IO 系统和它的 shell 是在 Thompson 的独自控制之下,在正确想法浮出水面的时候,实现它只是大约一个小时的事情。

PDP-11 的出现

在 1970 年初,PDP-7 Unix 是正在消逝的关注。按今天的标准它是原始的,但它仍有能力比它的替代者提供更合意的编程环境。不过,很明显的是, PDP-7 这个我们不再拥有的机器,已经废弃了,在同一行列的后继者也没什么意思。在 1970 年早期我们提议获得一个 PDP-11,它刚由 Digital 推出。在某种意义上,这个提议只不过是从几年就开始做的一系列尝试中最新的一次。在两个重要的方面有所不同。首先,钱数(大约 $65,000)是比我们以前要求的少一个数量级;其次,达成的契约不只是写某种(未指定的)操作系统,而是建立一个系统,明确的设计用来编辑和格式化文本,现在这叫做‘字处理系统’。这个提议的推动力主要来自 J. F. Ossanna,他毕生致力于文本处理。如果说我们早先的提议太含混了,这个提议可能就太明确了;最初它也受到冷遇。但不久,通过 L. E. McMahon 的努力而获得了资金,在五月是填了一份 PDP-11 订单。

夏天结束的时候处理器到来了,但 PDP-11 是个新产品以至于直到 12 月还没获得磁盘。在此期间,在 PDP-7 上使用交叉编译器写了一个根本的、只在内存的 Unix。多数时候,这个机器呆在角落里, 做三个月的作业列举在 6×8 棋盘上所有闭合的跳马巡回。

第一个 PDP-11 系统

一旦磁盘到来,系统迅速的完成了。在内部结构中,PDP-11  Unix 的第一个版本只体现了比 PDP-7 系统相对次要的改进;写它主要是大量转移工作。例如,没有多道程序;在任何时候在内存中只存在一个用户的程序。在另一方面,在到用户的界面上有重要的改变: 出现了现在的目录结构,带有完整的路径名字, 还有现代的形式 exec 和 wait,和便利设施如终端的字符删除和行删除处理。可能对企业最有趣的事情是它的小尺寸: 有 24K 字节的内存(16K 给系统,8K 给用户),和有 1K 块(512K 字节)的一个磁盘。文件限制到 64K 字节。

在定了 PDP-11 的订单的时候,好像是很自然的,或者是权宜之计,承诺了专注于字处理的一个系统。在硬件推延到来期间,PDP-7 Unix 日益增长的使用证明建立 PDP-11 Unix 作为开发工具是正确的,适合在写一个更加专用的系统时使用。到了 1971 年春天,普遍的认同了没有人会对零碎的 Unix 有一点兴趣。所以,我们把 roff 文本格式化器转移到了 PDP-11 汇编语言,起步于从  McIlroy 在 Multics 上的 BCPL 版本转移来的 PDP-7 版本,它受到 J. Saltzer 在 CTSS 上的 runoff 程序的影响。在初夏,编辑器和格式化器到手了,我们准备好履行我们的契约为专利部准备专利应用提供文本处理服务。当时,他们正在为了这个目的评估一个商用系统;我们提供的主要优势(除了参与一个内部实验的疑点之外)有两点: 首先,我们支持 Teletype 的 model 37 终端,它带有一个扩展的活字箱,可以打印他们需要的多数数学符号;其次,我们迅速的赋予 roff 生成行编号页面的能力,这是专利办公室需要的而其他系统不能处理的。

在 1971 年的后半段,我们支持专利部的三个打字员,他们整天繁忙的打字、编辑并格式化专利应用,同时我们尝试完成自己的工作。Unix 获得了在适度的硬件上提供有价值的服务的声誉,并且这个时期可能在利益/设备比率上标记了一个顶点;在没有内存保护并只有一个单一的 .5 MB 磁盘的一个机器上,每个新程序的测试都需要细心和大胆,因为这可能轻易的使系统崩溃,打字员的几个小时的工作都意味着把更多信息推出到 DECtape 上,原因是磁盘非常小。

实验在尝试但很成功。不仅是专利部接受了 Unix,并成为在实验室中众多小组中第一个认可我们工作的,而且我们得到了充分的信任度,说服我们的主管去获得第一个 PDP 11/45 系统。我们从此集聚了很多硬件,并持续的致力于软件,但因为多数有价值的工作已经发表了,(例如在系统自身[1, 5, 6, 7, 8, 9]),这里没有必要重复。

管道

Unix 对操作系统和命令语言文化的最受称赞的贡献就是管道,用在命令的管道线中。当然,基本想法决不是新的;管道只是协同例程(coroutine)的一种特殊形式。甚至实现都不是空前的,尽管我们当时不知道它;Dartmouth 的分时系统[10]的‘通信文件’与 Unix 管道非常类似,但他们好像没有这么完整的开发使用。

管道在 1972 年出现于 Unix,正好在系统的 PDP-11 版本运转之后,来自 M. D. McIlroy 的建议(或者是坚决主张),它是以协同例程为特征的非层次控制流的的长期倡导者。在管道实现的很多年前,他就建议命令应当被当作二元操作符来考虑,它的左右操作数指定输入和输出文件。这样一个‘copy’实用工具将是如下的命令

inputfile copy outputfile

要做一个管道线,命令操作符可以堆叠起来。这样,要排序 input,整齐的标页数,并离线打印结果,你可以写

input sort paginate offprint

在今天的系统中,这将对应于

sort input | pr | opr

这个想法,在黑板上解释了一个下午,激发了我们的兴趣但没有燃起任何立即的行动。对这个想法提出了几个异议: 中缀表示法好像太激进了(我们更习惯于键入‘cp x y’来复制 x 到 y);并且我们不知如何区分命令参数和输入或输出文件。还有,命令执行的一个输入一个输出的模式好像太局限了。多么缺乏想象力呀!

一段时间之后,感谢 McIlroy 的坚持,管道最终被安装到操作系统中(一个相对简单的工作),并介入了新的表示法。它使用与同 I/O 重定向相同的字符。例如,上述管道线可以写为

sort input >pr>opr>

想法是紧随‘>’之后的可以是一个文件,来制定输出到这个文件的重定向,或一个命令,把前面的命令的输出定向为这个命令的输入。在这个例子中需要尾随的‘>’来指定 opr 的(不存在的)输出应当被定向到控制台;否则命令 opr 根本就不执行;转而建立一个文件 opr。

新设施被狂热的认可了,并且术语‘过滤器’马上就出现了。更改了许多命令来在管道线中使用。例如,没有人能想象到有人需要 sort 或 pr 实用工具在没有给出明显的参数的时候排序或打印它的标准输入。

不久这个表示法的一些问题就变得明显了。最讨厌的是一个愚蠢的词法问题: 在‘>’之后的字符串是用空白来界定的,所以要在例子中给 pr 一个参数,你必须引用:

sort input >"pr -2">opr>

其次,尝试给予一般性,管道表示法接受‘<’作为输入重定向在某种程度上对应于‘>’;这意味着这个表示法是不一致的。例如你还可以写,

opr

甚至

pr <"sort input"< >opr>

使用‘<’和‘>’的管道表示法只幸存了几个月;它被替代为提供给你一个唯一的操作符来分隔一个管道线的成员。尽管老的表示法有特有的魔力和内在的一致性,新的表示法的确更加出众。当然,它也有局限性。 它是不加遮掩的线性的,尽管在有的情况下要求多个重定向的输入和输出。例如,什么是比较两个程序的输出的最佳方式? 什么是调用有两个并行输出流的程序的正确表示法?

我在上面的 IO 重定向章节提及到 Multics 提供一种机制,IO 流可以定向通过处理模块,在去到(或来自)设备或文件的路途上作为发起者或接受者。因此好像 Multics 中的流接合是 Unix 管道的直接先驱,如同 Multics IO 重定向的确是 Unix 版本的先驱。事实上我不认为这是真的,或者只在微弱的意义上是真的。不仅协同例程已经是众所周知的,而且他们的具体化为 Multics 可接合的 IO 模块要求特殊的编码,以至于不能用于其它任何目的。Unix 管道线的天才之处恰好是它用经常以单一的方式使用的命令构造出来的。需要看到这个可能性并发明这个表示法的精神飞跃是真正巨大的。

高级语言

最初的 PDP-7 Unix 系统的所有程序是用汇编语言写成的,并且是赤裸的汇编语言,这里没有宏。此外,没有装载器或连接器,所以每个程序都必须是自身完整的。出现的第一个有趣的语言是 McIlroy 实现的 McClure 的 TMG[11] 的一个版本。在获得了 TMG 的时候,Thompson 决定不能装作提供实际的计算服务而不提供 Fortran,所以他坐下来用 TMG 写一个 Fortran。我记得,处理 Fortran 的企图持续了一周。他转而创作了一个新语言 B[12] 的定义和一个编译器。B 受 BCPL[13] 语言的深刻影响;其他影响是 Thompson 对 spartan 语法的尝试,和编译器必须适应的非常小的空间。这个编译器生成简单的解释性代码;尽管它和它生成的程序非常慢,它却使生命更加舒适了。一旦获得了到常规的系统调用的接口,我们立即就开始享受到使用一个合理的语言来写通常叫做‘系统程序’的东西的乐趣:它们是编译器、汇编器、和连接器。(尽管有人认为我们在 Multics 下使用的 PL/I 是不切合实际的,它却比汇编语言好的多。) 在其他程序中,PDP-7 B 的 PDP-11 交叉编译器是用 B 写的,并且经过了一段时间之后,PDP-7 自身的 B 编译器也从 TMG 转移到了 B。

在 PDP-11 到来的时候,B 几乎立即就转移到它上面了。实际上,多精度‘桌面计算器’程序 dc 的一个版本是最先在 PDP-11 上运行的程序之一,恰好在磁盘到来之前。但是,B 没有立即接管下来。只有短暂的想法用 B 重写操作系统而不是汇编器,对于多数实用工具也是同样的。甚至汇编器都用汇编语言重写。采用这个途径主要是因为解释性代码的缓慢。更小但仍在实际上很重要的是面向字的 B 语言和字节寻址的 PDP-11 之间的不匹配。

所以,在 1971 年,开始做的工作了变成了 C 语言[14]。从 BCPL 到 B 到 C 的语言开发的故事在别的地方[15]讲述了,不需要在这里重复。可能最重要的分水岭出现在 1973 年期间,这时操作系统内核用 C 重写了。在这个转折点上系统呈现了它的现代形式;最深远的改变是引入了多道程序。还有一些外在可见的改变,此外系统的内部结构变得更加合理和全面。这些努力的成功使我们确信 C 是给系统编程的一个几乎通用的工具,而不只是给简单应用的玩具。

今天,仍用汇编写的唯一重要的 Unix 程序就是汇编器自身;事实上所有的实用工具程序是用 C 写的,多数应用程序也是如此,尽管这是 Fortran、Pascal、和 Algol 68 等的领地。毫无疑问,Unix 的成功多数来自可读性,可修改性,和它的软件的可移植性,而这都来自它们的用高级语言表达。

结论

关于陈年回忆的令人欣慰的事情之一就是他们倾向于呈现出玫瑰色的光芒。当在这里描述的时候,Unix 的早期版本所提供的编程环境好像是非常粗糙和原始的。我确信如果强行回到 PDP-7 我将发现它难以忍受的限制和缺乏方便的工具。然而,在当时好像不是这样;记忆决定了什么是好的和什么是持久的,和帮助创造使生命更美好的改进的乐趣。十年后,我希望我们回头看看,依然有同样的进步和连续结合在一起的混和感觉。

致谢

我要感谢 S. P. Morgan、K. Thompson 和 M. D. McIlroy 提供了早期的文档并挖掘了记忆。

因为我对描述想法的演化最有兴趣,本文只在非常重要的地方把想法和工作归属于个人。读者一般来说不应有所误解,这里不明确的指代词‘我们’指的是‘Thompson 和做一些协助的我’。

引用

1.
D. M. Ritchie and K. Thompson, `The Unix Time-sharing System, C. ACM 17 No. 7 (July 1974), pp 365-37.
2.
L. P. Deutch and B. W. Lampson, `SDS 930 Time-sharing System Preliminary Reference Manual,' Doc. 30.10.10, Project Genie, Univ. Cal. at Berkeley (April 1965).
3.
R. J. Feiertag and E. I. Organick, `The Multics input-output system,' Proc. Third Symposium on Operating Systems Principles, October 18-20, 1971, pp. 35-41.
4.
The Multiplexed Information and Computing Service: Programmers' Manual, Mass. Inst. of Technology, Project MAC, Cambridge MA, (1969).
5.
K. Thompson, `Unix Implementation,' Bell System Tech J. 57 No. 6, (July-August 1978), pp. 1931-46.
6.
S. C. Johnson and D. M. Ritchie, Portability of C Programs and the Unix System,' Bell System Tech J. 57 No. 6, (July-August 1978), pp. 2021-48.
7.
B. W. Kernighan, M. E. Lesk, and J. F. Ossanna. `Document Preparation,' Bell Sys. Tech. J., 57 No. 6, pp. 2115-2135.
8.
B. W. Kernighan and L. L. Cherry, `A System for Typesetting Mathematics,' J. Comm. Assoc. Comp. Mach. 18, pp. 151-157 (March 1975).
9.
M. E. Lesk and B. W. Kernighan, `Computer Typesetting of Technical Journals on Unix,' Proc. AFIPS NCC 46 (1977), pp. 879-88.
10.
Systems Programmers Manual for the Dartmouth Time Sharing System for the GE 635 Computer, Dartmouth College, Hanover, New Hampshire, 1971.
11.
R. M. McClure, `TMG--A Syntax-Directed Compiler,' Proc 20th ACM National Conf. (1968), pp. 262-74.
12.
S. C. Johnson and B. W. Kernighan, `The Programming Language B,' Comp. Sci. Tech. Rep. #8, Bell Laboratories, Murray Hill NJ (1973).
13.
M. Richards, `BCPL: A Tool for Compiler Writing and Systems Programming,' Proc. AFIPS SJCC 34 (1969), pp. 557-66.
14.
B. W. Kernighan and D. M. Ritchie, The C Programming Language, Prentice-Hall, Englewood Cliffs NJ, 1978. Second Edition, 1979.
15.
D. M. Ritchie, S. C. Johnson, and M. E. Lesk, `The C Programming Language,' Bell Sys. Tech. J. 57 No. 6 (July-August 1978) pp. 1991-2019.

© 1996 Lucent Technologies Inc. All rights reserved.

阅读(6379) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~