UNIX 分时系统文章谷歌翻译

原文链接:The UNIX time-sharing system | Proceedings of the fourth ACM symposium on Operating system principles

UNIX 是用于 Digital Equipment Corporation PDP-11/40 和 11/45 计算机的通用、多用户、交互式操作系统。 它提供了许多即使在大型操作系统中也很少见的功能,包括:(1)包含可拆卸卷的分层文件系统; (2) 兼容的文件、设备和进程间I/O; (3) 启动异步进程的能力; (4) 系统命令语言可供每个用户选择; (5) 100多个子系统,包括十几种语言。 本文讨论了文件系统和用户命令接口的性质和实现。
关键词和短语:分时、操作系统、文件系统、命令语言、PDP-11CR 类别:4.30、4.32


一、简介
UNIX 已经存在三个版本。 最早的版本(大约 196%70)在 Digital Equipment Corporation PDP-7 和 -9 计算机上运行。 第二个版本在未受保护的 PDP-11/20 计算机上运行。 本文仅描述 PDP-I 1/40 和 /45 [1] 系统,因为它更现代并且存在许多差异
它与旧版 UNIX 系统之间的差异是由于重新设计发现缺陷或缺乏的功能而产生的。 自 1971 年 2 月 PDP-11 UNIX 投入运行以来,大约有 40 个装置投入使用; 它们通常比此处描述的系统小。 他们中的大多数从事诸如专利申请和其他文本材料的准备和格式化、从贝尔系统内的各种交换机收集和处理故障数据以及记录和检查电话服务订单等应用。 我们自己的装置主要用于操作系统、语言、计算机网络和计算机科学的其他主题的研究,以及文档准备。 也许 UNIX 最重要的成就是证明了一个用于交互使用的强大操作系统不需要昂贵的设备或人力:UNIX 可以在成本低至 40,000 美元的硬件上运行,并且在该系统上花费了不到两年的时间。 主要系统软件。 然而,UNIX 包含许多即使在更大的系统中也很少提供的功能。 然而,我们希望 UNIX 的用户会发现最重要的特性
该系统的优点是其简单、优雅和易于使用。 除了系统本身之外,UNIX 下可用的主要程序有:汇编器、基于 QED [2] 的文本编辑器、链接加载器、符号调试器、具有类型和结构 (C) 的类似于 BCPL [3] 的语言的编译器、 BASIC 方言、文本格式化程序、Fortran 编译器、Snobol 解释器、自上而下的编译器-编译器 (TMC) [4]、自下而上的编译器-编译器 (YACC)、套用信函生成器、宏处理器 (M6) [5]、 和置换索引程序。

还有许多维护、实用、娱乐和新奇项目。 所有这些程序都是在本地编写的。 值得注意的是,该系统是完全自支撑的。 所有 UNIX 软件都是在 UNIX 下维护的; 同样,UNIX 文档是由 UNIX 编辑器和文本格式化程序生成和格式化的。

2、软硬件环境
我们的 UNIX 安装所在的 PDP-11/45 是一台 16 位字(8 位字节)的计算机,具有 144K 字节的核心内存; UNIX占用42K字节。
然而,该系统包含大量设备驱动程序,并为 I/O 缓冲区和系统表分配了大量空间; 能够运行上述软件的最小系统总共需要少至 50K 字节的内核。
vDv-11有一个1M字节固定头磁盘,用于文件系统存储和交换,四个移动头磁盘驱动器,每个在可移动磁盘盒上提供2.5M字节,以及一个使用可移动40M字节的移动头磁盘驱动器 字节磁盘包。 还有高速纸带阅读机——打孔式、九轨式
磁带和 DEctape(各种磁带设备,可以在其中寻址和重写单个记录)。 除了控制台打字机之外,还有 14 个连接到 100 系列数据集的变速通信接口和一个主要用于将打印输出打印到公用行式打印机的 201 数据集接口。 还有一些独一无二的设备,包括 Picturephone ® 接口、语音应答装置、语音合成器、照排机、数字交换网络和卫星 PDP-11/20(可生成矢量、曲线和图像)。 Tektronix 611 存储管显示屏上的字符。
UNIX 软件的大部分都是用上述 C 语言编写的 [6]。 该操作系统的早期版本是用汇编语言编写的,但在 1973 年夏天,它被用 C 重写。
新系统的规模比旧系统大约大三分之一。 由于新系统不仅更容易理解和修改,而且还包括许多功能改进,包括多道程序设计和在多个用户程序之间共享可重入代码的能力,因此我们认为这种大小的增加是可以接受的。

3. 文件系统
UNIX最重要的作用是提供文件系统。 从用户的角度来看,文件分为三种:普通磁盘文件、目录和特殊文件。
3.1 普通文件
文件包含用户放置在其中的任何信息,例如符号或二进制(对象)程序。
系统不需要特殊的结构。
文本文件仅由一串字符组成,各行由换行符分隔。 二进制程序是单词序列,当程序开始执行时它们将出现在核心内存中。 一些用户程序操作具有更多结构的文件:
汇编器生成并且加载器期望特定格式的目标文件。 然而,文件的结构是由使用它们的程序控制的,而不是由系统控制的。
3.2 目录
目录提供文件名和文件本身之间的映射,从而在整个文件系统上形成一个结构。 每个用户都有一个自己的文件目录; 他还可以创建子目录来包含方便一起处理的文件组。 目录的行为与普通文件完全相同,只是非特权程序无法写入目录,因此系统控制目录的内容。 但是,具有适当权限的任何人都可以像读取任何其他文件一样读取目录。 系统维护几个目录供其自己使用。 其中之一是根目录。 可以通过跟踪目录链中的路径来找到系统中的所有文件,直到到达所需的文件。 此类搜索的起点通常是根。 另一个系统目录包含所有提供给一般用途的程序; 即所有命令。 然而,正如将要看到的,程序并不需要驻留在该目录中才能被执行。 文件按 14 个或更少字符的序列命名。 当向系统指定文件名时,它可以是路径名的形式,路径名是由斜杠“/”分隔并以文件名结尾的目录名序列。 如果序列以斜线开头,则搜索从根目录开始。 名称 /alpha/beta/gamma 使系统在根目录中搜索 alpha 目录,然后在 alpha 中搜索 beta,最后在 beta 中查找 gamma。 Gamma 可以是普通文件、目录或特殊文件。 作为限制情况,名称“/”指的是根本身。
不以“/”开头的路径名会导致系统在用户的当前目录中开始搜索。
因此,名称 alpha/beta 指定当前目录的子目录 alpha 中名为 beta 的文件。
最简单的名称(例如 alpha)指的是在当前目录中找到的文件本身。 作为另一个限制情况,空文件名指的是当前目录。
相同的非目录文件可能以不同的名称出现在多个目录中。 这个功能称为/inking; 文件的目录条目有时称为链接。 UNIX 与允许链接的其他系统的不同之处在于,到文件的所有链接都具有相同的状态。 也就是说,特定目录中不存在文件; 文件的目录项仅由文件名和指向实际描述该文件的信息的指针组成。 因此,文件独立于任何目录条目而存在,尽管实际上文件是创建的
连同最后一个链接一起消失。
每个目录始终至少有两个条目。 每个目录中的名称 .... 指的是目录本身。 因此,程序可以读取名为“.”的当前目录。 不知道其完整路径名。
按照惯例,名称“..”指的是它出现的目录的父目录,即创建它的目录。
目录结构被限制为有根树的形式。 除了特殊条目“ ”和“..”之外,每个目录必须完全作为另一个目录(即其父目录)中的条目出现。 这样做的原因是为了简化访问目录结构子树的程序的编写,更重要的是,为了避免层次结构各部分的分离。 如果允许任意链接到目录,则很难检测到
从根目录到目录的最后一个连接被切断。3.3 特殊文件
特殊文件构成了 UNIX 文件系统最不寻常的特性。 UNIX 支持的每个 I/O 设备都至少与一个这样的文件相关联。 特殊文件的读取和写入就像普通磁盘文件一样,但读取或写入的请求会导致关联设备的激活。 每个特殊文件的条目都驻留在目录 /dev 中,尽管可以像普通文件一样链接到这些文件之一。 因此,例如,要在纸带上打孔,可以在文件/dev/ppt 上写入。
每条通信线路、每个磁盘、每个磁带驱动器以及物理核心内存都存在特殊文件。 当然,活动磁盘和核心特殊文件受到保护,不会受到随意访问。 以这种方式处理 I/O 设备有三个优点: 文件和设备 I/O 尽可能相似; 文件名和设备名具有相同的语法和含义,因此需要文件名作为参数的程序可以传递设备名; 最后,特殊文件与常规文件受到相同的保护机制。
3.4 可移动文件系统
尽管文件系统的根始终存储在同一设备上,但整个文件系统层次结构不必驻留在该设备上。 有一个挂载系统请求,它有两个参数:现有普通文件的名称,以及直接访问特殊文件的名称,该文件的关联存储卷(例如磁盘包)应具有包含其自己的独立文件系统的结构 目录层次结构。 挂载的作用是使对之前的普通文件的引用改为对可移动卷上文件系统的根目录的引用。
实际上,mount 将层次结构树的叶子(普通文件)替换为全新的子树(存储在可移动卷上的层次结构)。 安装后,可移动卷上的文件与永久文件系统中的文件实际上没有区别。 例如,在我们的安装中,根目录位于固定头磁盘上,而包含用户文件的大磁盘驱动器是由系统初始化程序挂载的; 四个较小的磁盘驱动器可供用户安装自己的磁盘包。 可安装的文件系统是通过写入其相应的特殊文件来生成的。 实用程序可用于创建空文件系统,或者可以简单地复制现有文件系统。
在不同设备上对文件进行相同处理的规则只有一个例外:一个文件系统层次结构与另一个文件系统层次结构之间不能存在链接。 强制执行此限制是为了避免繁琐的簿记,否则需要进行繁琐的簿记,以确保在最终卸载可移动卷时确保删除链接。 特别是,在所有文件系统的根目录中,无论可移动与否,名称“..”指的是目录本身而不是其父目录。

3.5 保护
尽管 UNIX 中的访问控制方案非常简单,但它有一些不寻常的功能。 系统的每个用户都被分配了一个唯一的用户标识号。
文件创建后,会标有其所有者的用户 ID。 还为新文件提供了一组七个保护位。 其中六个指定了所有者的独立读、写和执行权限
文件以及所有其他用户。
如果第七位打开,则每当文件作为程序执行时,系统都会暂时将当前用户的用户标识更改为文件创建者的用户标识。 用户 ID 的此更改仅在调用它的程序执行期间有效。
设置用户 ID 功能提供了可能使用其他用户无法访问的文件的特权程序。
例如,程序可能保存一个会计文件,除了程序本身之外,该文件不应被读取或更改。 如果程序的设置用户标识位打开,则它可以访问该文件,尽管给定程序的用户调用的其他程序可能会禁止这种访问。 由于任何程序的调用者的实际用户 ID 始终可用,因此 setuser-Io 程序可以采取任何所需的措施来满足其调用者的凭据。 该机制用于允许用户执行精心编写的调用特权系统条目的命令。 例如有一个系统入口可调用
仅由“超级用户”(如下)创建一个空目录。 如上所述,目录应该有“.”条目。 和 ”..”。 创建目录的命令由超级用户所有,并且设置了 set-user-ID 位。 在检查其调用者创建指定目录的授权后,它会创建该目录并创建“ ”和“..”条目。
由于任何人都可以在他自己的文件之一上设置 set-user-ID 位,因此这种机制通常无需管理干预即可使用。 例如,这种保护方案很容易解决[7]中提出的MOO计费问题。
系统识别一个特定的用户 ID(
“超级用户”)免受通常的限制
关于文件访问; 因此(例如)程序可能是
写入转储和重新加载文件系统,而不会受到保护系统的不必要干扰。
3.6 I/O 调用
执行 I/O 的系统调用旨在消除
各种设备和访问方式之间的差异。 “随机”和“顺序”I/O 之间没有区别,系统也没有强加任何逻辑记录大小。 普通文件的大小确定按其上写入的最高字节; 没有必要或不可能预先确定文件的大小。
为了说明 UNIX 中 I/O 的本质,下面用匿名语言总结了一些基本调用,该语言将指示所需的参数,而无需陷入机器语言编程的复杂性。 对系统的每次调用都可能导致错误返回,为简单起见,该错误未在调用序列中表示。
要读取或写入假定已存在的文件,必须通过以下调用打开它:
filep = 打开(名称,标志)
名称表示文件的名称。 可以给出任意路径名。 标志参数指示文件是否要被读取、写入或“更新”,即同时读取和写入。
返回值 filep 称为文件描述符。
它是一个小整数,用于在后续读取、写入或以其他方式操作文件的调用中标识该文件。
要创建新文件或完全重写旧文件,有一个 create 系统调用,如果给定文件不存在,则创建该文件;如果存在,则将其截断为零长度。 Create 还会打开新文件进行写入,并且与 open 一样返回一个文件描述符。
文件系统中没有用户可见的锁,也没有对可以打开文件进行读取或写入的用户数量进行任何限制。 尽管当两个用户同时写入文件时,文件内容可能会变得混乱,但实际上不会出现困难。 我们认为,在我们的环境中,锁对于防止同一文件的用户之间的干扰既不是必要的,也不是充分的。 它们是不必要的,因为我们不需要维护大型的单文件数据库
通过独立的进程。 它们是不够的,因为普通意义上的锁(即阻止一个用户在另一个用户正在读取的文件上写入)无法防止混淆,例如,当两个用户都使用编辑器编辑文件时,该编辑器会生成一个文件。
正在编辑的文件的副本。
应该说,当两个用户同时进行同一个文件写入、在同一个目录中创建文件、删除彼此打开的文件等不方便的活动时,系统有足够的内部互锁来维持文件系统的逻辑一致性。
除下述情况外,读取和写入都是按顺序进行的。 这意味着,如果文件中的特定字节是最后写入(或读取)的字节,则下一个 I/O 调用隐式引用随后的第一个字节。 对于每个打开的文件,都有一个由系统维护的指针,该指针指示要读取或写入的下一个字节。 如果读取或写入 n 个字节,则指针前进 n 个字节。
文件打开后,可以使用以下调用:
n = 读取(文件p、缓冲区、计数)
n = 写入(文件、缓冲区、计数)

在 filep 指定的文件和 buffer 指定的字节数组之间传输最多 count 个字节。
返回值n是实际传输的字节数。 在写入情况下,n 与 count 相同,除非出现 I/O 错误或特殊文件上的物理介质末尾等特殊情况; 在一次阅读中,
然而,n 可以毫无错误地小于 count。 如果读指针距离文件末尾太近,以至于读取 count 个字符会导致读取超出末尾,则仅传输足够的字节以到达文件末尾; 此外,类似打字机的设备永远不会返回超过一行的输入。 当 read 调用返回 n 等于 0 时,表示文件结束。
对于磁盘文件,当读指针等于文件的当前大小时,就会发生这种情况。 可以通过使用取决于所使用的设备的转义序列从打字机生成文件结尾。 写入文件的字节仅影响写指针位置和计数所暗示的字节; 文件的其他部分没有改变。 如果最后一个字节超出文件末尾,则文件将根据需要增长。
要进行随机(直接访问)I/O,只需将读或写指针移动到文件中的适当位置即可。
位置 = 查找(文件、基址、偏移量)
与 filep 关联的指针被移动到距文件开头、指针当前位置或文件末尾偏移字节的位置,具体取决于基址。 偏移量可能为负。 对于某些设备(例如纸带和打字机),查找调用将被忽略。 在位置中返回距指针移动到的文件开头的实际偏移量。

3.6.1 其他 I/O 调用。 还有几个额外的
与 I/O 和文件系统有关的系统条目
  不予讨论。 例如:关闭文件、获取文件的状态、更改文件的保护模式或所有者、创建目录、
建立指向现有文件的链接、删除文件。
4. 文件系统的实现
正如上面第 3.2 节中提到的,目录项仅包含关联文件的名称和指向文件本身的指针。 该指针是一个称为文件 i 号(索引号)的整数。 当文件
被访问时,它的 i 编号用作存储在目录所在设备的已知部分中的系统表(i 列表)的索引。 由此找到的条目(文件的 i 节点)包含文件的描述,如下所示。
1. 它的主人。
2.其保护位。
3. 文件的物理磁盘或磁带地址
内容。
4.它的大小。
368通讯 1974年7月
第17卷
ACM 编号 7

5. 最后修改时间。
6、文件的链接数,即个数
它出现在目录中的次数。
7. 指示文件是否为目录的位。
8. 指示该文件是否为特殊文件的位。
9. 指示文件是“大”还是“小”的位。
open 或create 系统调用的目的是通过搜索显式或隐式命名的目录,将用户给出的路径名转换为i 编号。
文件打开后,其设备、i 编号和读/写指针将存储在由打开或创建返回的文件描述符索引的系统表中。 因此,在读取或写入文件的后续调用期间提供的文件描述符可以容易地与访问该文件所需的信息相关。 创建新文件时,会为其分配一个 i 节点,并创建一个包含文件名和 i 节点号的目录条目。 建立到现有文件的链接涉及使用新名称创建目录条目、从原始文件条目复制 i 编号以及增加链接计数
i 节点的字段。 删除(删除)文件是通过减少其目录项指定的 i 节点的链接计数并擦除目录项来完成的。 如果链接计数降至 0,则文件中的所有磁盘块都将被释放,i 节点将被释放& 所有包含文件系统的固定或可移动磁盘上的空间将被划分为多个 512 字节的块,逻辑上从 0 到取决于设备的限制。 每个文件的 i 节点中有 8 个设备地址的空间。 一个小(非特殊)文件适合八个或更少的块; 在这种情况下,存储块本身的地址。
对于大型(非特殊)文件,八个设备地址中的每一个都可以指向构成文件本身的块的 256 个地址的间接块。 因此,文件可能有 8.256-512 或 1,048,576 (22o ) 字节大。
前述讨论适用于普通文件。 当向 i 节点指示其特殊的文件发出 I/O 请求时,最后七个设备地址字无关紧要,第一个被解释为
构成内部设备名称的字节对。
这些字节分别指定设备类型和子设备号。 设备类型指示哪个系统例程将处理该设备上的 I/O; 例如,子设备号选择连接到特定控制器的磁盘驱动器或几个类似的打字机接口之一。
在此环境中,mount 系统调用(§3.4)的实现非常简单。 Mount 维护一个系统表,其参数是挂载期间指定的普通文件的 i 号和设备名,其对应的值是指示的特殊文件的设备名。 在此表中搜索每个(i 编号,设备)对,该对在打开或创建期间扫描路径名时出现; 如果找到匹配项,则 i 编号将替换为 1(也就是所有文件系统上根目录的 i 号),并且设备名称被表值替换。
对于用户来说,文件的读取和写入似乎都是同步且无缓冲的。 也就是说,在从读取调用返回之后,数据立即可用,相反,在写入之后,用户的工作空间可以被重用。 事实上,系统维护了一个相当复杂的缓冲机制,大大减少了访问文件所需的 I/O 操作数量。 假设进行了指定传输单个字节的写入调用。
UNIX 将搜索其缓冲区以查看受影响的磁盘块当前是否驻留在核心内存中; 如果没有,将从设备中读入。 然后受影响的字节在缓冲区中被替换,并在要写入的块列表中创建一个条目。 然后可能会从写调用返回,尽管实际的 I/O 可能要稍后才能完成。 反之,如果读取的是单个字节,则系统判断该字节所在的辅助存储块是否已经在系统的某个缓冲区中; 如果是,则可以立即返回该字节。 如果不是,则将该块读入缓冲区并选取字节
出去。
以 512 字节为单位读取或写入文件的程序比一次读取或写入单个字节的程序有优势,但增益并不是很大; 它主要来自于避免系统开销。 很少使用或没有大量 I/O 的程序可以相当合理地以尽可能小的单元进行读写。
i-list 的概念是 UNIX 的一个不寻常的特性。 在实践中,这种组织文件系统的方法已被证明相当可靠且易于处理。
对于系统本身而言,其优势之一是每个文件都有一个简短、明确的名称,该名称以简单的方式与访问文件所需的保护、寻址和其他信息相关。 它还允许使用非常简单且快速的算法来检查文件系统的一致性,例如验证每个设备中包含有用信息的部分和那些可以自由分配的部分是不相交的,并且一起耗尽了设备上的空间。 该算法独立于目录层次结构,因为它只需要扫描线性组织的 i-list。 同时,i-list 的概念带来了其他文件系统组织中没有的某些特性。 例如,存在谁应该为文件占用的空间付费的问题,因为文件的所有目录条目都具有相同的地位。 一般来说,向文件所有者收费是不公平的,因为一个用户可以创建文件,另一个用户可以链接到该文件,而第一个用户可以删除该文件。 第一个用户仍然是文件的所有者,但应向第二个用户收费。 最简单的合理公平算法似乎是在拥有文件链接的用户之间平均分摊费用。
当前版本的 UNIX 完全不收取任何费用,从而避免了这个问题。
369 通讯 1974 年 7 月
第17卷
ACM 编号 7

4.1 文件系统的效率
为了指示 UNIX 特别是文件系统的整体效率,对 7621-1ine 程序的汇编进行了计时。 装配是在机器上单独运行的; 总时钟时间为 35.9 秒,速率为每秒 212 行。
时间划分如下:63.5% 的汇编程序执行时间、16.5% 的系统开销、20.0% 的磁盘等待时间。 我们不会尝试对这些数字进行任何解释,也不会与其他系统进行任何比较,而只是指出我们总体上对系统的整体性能感到满意。
5. 过程和图像
图像是计算机执行环境。 它包括核心映像、通用寄存器值、打开文件的状态、当前目录等。 图像是伪计算机的当前状态。
进程是图像的执行。 当处理器代表进程执行时,映像必须驻留在核心中; 在执行其他进程期间,它保留在核心中,除非出现活动的、优先级较高的进程迫使其交换到固定头磁盘。
图像的用户核心部分分为三个逻辑部分。 程序文本段从虚拟地址空间中的位置 0 开始。 在执行期间,该段是写保护的,并且在执行同一程序的所有进程之间共享它的单个副本。 在虚拟地址空间中程序文本段上方的第一个 8K 字节边界处开始一个非共享的、可写的数据段,其大小可以通过系统调用来扩展。 从虚拟地址空间的最高地址开始是一个堆栈段,它随着硬件堆栈指针的波动而自动向下增长。
5.1 流程
除非 UNIX 正在引导自身运行,否则新进程只能通过使用 fork 系统调用来存在:
processid = fork(标签)
当一个进程执行fork时,它会分裂成两个独立执行的进程。 这两个进程拥有原始核心映像的独立副本,并共享任何打开的文件。 新进程的不同之处仅在于新进程被视为父进程:在父进程中,控制权直接从 fork 返回,而在子进程中,控制权被传递到位置标签。 fork调用返回的processid是其他进程的标识。
由于父进程和子进程中的返回点不一样,fork后存在的每个镜像都可以判断自己是父进程还是子进程。
370
5.2 管道
进程可以使用用于文件系统 I/O 的相同系统读取和写入调用与相关进程进行通信。 电话
filep = 管道( )
返回文件描述符 filep 并创建一个称为管道的进程间通道。 该通道与其他打开的文件一样,通过 fork 调用从映像中的父进程传递到子进程。 使用管道文件描述符的读取会等待,直到另一个进程使用同一管道的文件描述符进行写入。 此时,数据在两个进程的图像之间传递。 两个进程都不需要知道涉及的是管道,而不是普通文件。
尽管通过管道进行进程间通信是一个非常有价值的工具(参见§6.2),但它并不是一个完全通用的机制,因为管道必须由所涉及进程的共同祖先建立。
5.3 程序的执行
另一个主要的系统原语由execute(file, args, argo, ..., arg,+) 调用,它请求系统读入并执行由file 命名的程序,并向其传递字符串参数argl, arg..,, 。 ..,arg,+。 一般情况下,argl 应该和 file 是同一个字符串,这样程序就可以通过 来确定名称。 它被调用。 使用execute的进程中的所有代码和数据都将从文件中替换,但打开的文件、当前目录和进程间关系保持不变。 仅当调用失败时,例如由于找不到文件或未设置其执行权限位,才会从执行原语返回; 它类似于“跳转”机器指令而不是子例程调用。
5.4 进程同步
另一个进程控制系统调用 processid = wait() 导致其调用者暂停执行,直到其子进程之一完成执行。 然后 wait 返回已终止进程的 processid。 如果调用进程没有后代,则会返回错误。
子进程的某些状态也可用。
等待也可能代表来自孙子或更远的祖先的地位; 参见第 5.5 节。
5.5 终止
最后,
退出(状态)
终止进程、销毁其映像、关闭其打开的文件,并且通常会删除它。 当通过等待原语通知父级时,指示的状态可供父级使用; 如果父母已经终止,则祖父母可以使用该状态,
通讯 1974 年 7 月
第17卷
ACM 编号 7

等等。 进程也可能因各种非法行为或用户生成的信号而终止(下文第 7 节)。
6. 外壳
对于大多数用户来说,与 UNIX 的通信是借助称为 Shell 的程序进行的。 Shell 是一个命令行解释器:它读取用户键入的行并将其解释为执行其他程序的请求。 最简单的形式是,命令行由命令名称和命令参数组成,所有参数均以空格分隔:command argl arg~ • • - argn
Shell 将命令名称和参数分割成单独的字符串。 然后寻找一个名为command的文件; 命令可以是包含“/”字符的路径名,以指定系统中的任何文件。 如果找到命令,则将其带入核心并执行。 命令可以访问 Shell 收集的参数。 当命令完成时,Shell 恢复自己的执行,并表明它准备好接受另一个命令
输入提示字符。
如果找不到文件命令,Shell 会在命令前添加字符串 /bin/ 前缀,并再次尝试查找该文件。 目录/bin 包含所有常用命令。
6.1 标准输入/输出
上面第 3 节中对 I/O 的讨论似乎意味着程序使用的每个文件都必须由程序打开或创建,才能获取该文件的文件描述符。 然而,由 Shell 执行的程序
首先打开两个文件,文件描述符分别为 0 和 1。当这样的程序开始执行时,文件 1 打开以供写入,最好将其理解为标准输出文件。 除下述情况外,该文件是用户的打字机。 因此程序
希望写入信息或诊断信息的程序通常使用文件描述符 1。相反,文件 0 开始打开以供读取,并且希望读取用户键入的消息的程序通常读取该文件。
Shell 能够从用户的打字机、打印机和键盘更改这些文件描述符的标准分配。 如果命令的参数之一以“)”为前缀,则文件描述符 1 将,例如
命令的持续时间,请参阅以“)”命名的文件。 例如,

Is
通常在打字机上列出文件的名称
在当前目录中。 命令

1 s )there
创建一个名为 There 的文件并将列表放置在那里。
因此,参数“)there”的意思是“将输出放在
那里。”另一方面,
编辑
通常进入编辑器,该编辑器接受来自
用户通过他的打字机。 命令
ed(脚本将脚本解释为编辑器命令的文件;因此
“(script”的意思是“从脚本获取输入。”
虽然“(”或“)”后面的文件名看起来是命令的参数,但实际上它完全由 Shell 解释,根本不传递给命令。 因此,每个命令中不需要特殊的编码来处理 I/O 重定向; 该命令只需在适当的情况下使用标准文件描述符 0 和 l。

6.2 过滤器
标准 I/O 概念的扩展用于将一个命令的输出定向到另一个命令的输入。
由竖线分隔的命令序列使 Shell 同时执行所有命令,并安排每个命令的标准输出传递到序列中下一个命令的标准输入。 因此在命令行中
ls lpr-2[操作
ls 列出当前目录下的文件名; 它的输出被传递给 pr,它用带日期的标题对其输入进行分页。 参数“--2”表示双列。 同样,pr 的输出被输入到 opr。
该命令将其输入假脱机到文件中以进行离线打印。
这个过程本来可以更笨拙地进行
ls ) 温度 1
pr -- 2(温度 1 )温度 2
操作(温度2
然后删除临时文件。 在缺乏重定向输出和输入的能力的情况下,更笨拙的方法是要求 /s 命令接受用户请求以对其输出进行分页、以多列格式打印并安排其输出的传递 离线。 实际上,期望 Is 等命令的作者提供如此广泛的输出选项会令人惊讶,而且实际上出于效率原因也是不明智的。 诸如 pr 之类的将其标准输入复制到其标准输出(经过处理)的程序称为
。筛选。 我们发现一些有用的过滤器可以执行字符音译、输入排序以及加密和解密。

6.3 命令分隔符:多任务处理
Shell 提供的另一个功能相对简单。 命令不必位于不同的位置
371 通讯 1974 年 7 月
第17卷
ACM 编号 7

线路; 相反,它们可以用分号分隔。
ls; 编辑
会先列出当前目录的内容,然后进入编辑器。
一个相关的功能更有趣。 如果命令后跟“&”,Shell 不会等待命令完成才再次提示; 相反,它会立即准备好接受新命令。 为了
例子,
作为源)输出&
导致源被组装,诊断输出被输出; 无论组装需要多长时间,Shell 都会立即返回。 当 Shell 不等待命令完成时,运行该命令的进程的标识为
打印。 该标识可用于等待命令完成或终止命令。
“&”可以在一行中使用多次:
作为源)输出&ls)文件&
在后台进行组装和列表。 在上面使用“&”的示例中,提供了打字机以外的输出文件; 如果不这样做,各个命令的输出就会混合在一起。 Shell 还允许在上述操作中使用括号。 例如
(日期;ls))x &
打印当前日期和时间,后跟文件 x 上的当前目录列表。 Shell 还会立即返回另一个请求。
6.4 Shell 作为命令:命令文件
Shell 本身就是一个命令,并且可以递归调用。 假设文件 tryout 包含作为源 mv a 的行。 输出测试程序
测试程序
my 命令导致文件 a.out 被重命名为 testprog,a.out 是汇编器的(二进制)输出,准备好执行。 因此,如果在控制台上键入上面的三行,则将汇编源代码,生成名为 testprog 的程序,并执行 testprog。 当线路处于试用状态时,命令
sh(tryout 会导致 Shell sh 执行命令
依次。 Shell 具有更多功能,包括替换参数以及从目录中指定的文件名子集构造参数列表的能力。 还可以根据字符串比较或给定文件的存在条件执行命令,并在归档命令序列内执行控制转移。

6.5 Shell的实现
现在可以了解 Shell 操作的概要了。 大多数时候,Shell 正在等待用户输入命令。 当键入结束行的换行符时,Shell 的 read 调用将返回。 Shell 分析命令行,
将参数置于适合执行的形式。 然后调用 fork。 子进程的代码当然仍然是 Shell 的代码,它尝试使用适当的参数执行执行。 如果成功,这将引入并开始执行程序
给出了谁的名字。 同时,由fork产生的另一个进程,即父进程,等待子进程死亡。 发生这种情况时,Shell 知道命令已完成,因此它键入提示符并读取打字机以获取另一个命令。
有了这个框架,后台进程的实现就很简单了; 每当命令行包含“&”时,Shell 只是避免等待它创建的进程来执行该命令。
令人高兴的是,所有这些机制都与标准输入和输出文件的概念非常契合。
当一个进程被 fork 原语创建时,它不仅继承其父进程的核心映像,还继承其父进程中当前打开的所有文件,包括文件描述符为 0 和 1 的文件。当然,Shell 使用这些文件来 读取命令行并写入其
提示和诊断,在一般情况下,它的子程序(命令程序)会自动继承它们。 然而,当给出带有“(”或“)”的参数时,子进程在执行执行之前,使标准 I/O 文件描述符 0 或 1 分别引用指定的文件。 这很容易,因为根据协议,在打开(或创建)新文件时会分配最小的未使用文件描述符; 只需要关闭文件 0(或 1)并打开指定的文件。 因为命令程序运行的进程在结束时简单地终止,所以当进程终止时,“(”或“)”后面指定的文件与文件描述符0或1之间的关联将自动结束。 因此 Shell 不需要知道文件的实际名称,这是它自己的标准
输入和输出,因为它不需要重新打开它们。 过滤器是标准 I/O 重定向的直接扩展,使用管道而不是文件。

一般情况下,Shell 的主循环永远不会终止。 (主循环包括属于父进程的 fork 返回的分支;即执行等待,然后读取另一个命令行的分支。)导致 Shell 终止的一件事是发现一个 end-of - 输入文件的文件条件。 因此,当 Shell 作为具有给定输入文件的命令执行时,如下所示
sh(编译文件
comfile 中的命令将被执行,直到到达 comfile 末尾; 那么实例
372 通讯 1974 年 7 月
第17卷
ACM 编号 7

sh 调用的 shell 将终止。 由于此 Shell 进程是另一个 Shell 实例的子进程,因此后者中执行的等待将返回,并且可能会处理另一个命令。
6.6 初始化
用户键入命令的 Shell 实例本身就是另一个进程的子进程。 UNIX 初始化的最后一步是创建单个进程并调用(通过执行)名为 init 的程序。 init的作用就是创建一个。 每个可由用户拨号的打字机通道的过程。 init 的各个子实例打开适当的打字机进行输入和输出。 由于调用 init 时没有打开文件,因此在每个进程中,打字机键盘将接收文件描述符 0 和打印机文件描述符 1。每个进程都会键入一条消息,请求用户登录并等待,读取打字机的信息。 回复。 一开始,没有人登录,因此每个进程都只是挂起。
最后有人输入了他的名字或其他身份信息。
适当的 init 实例被唤醒,接收登录行,并读取密码文件。 如果找到用户名,并且能够提供正确的密码,init 会更改用户的默认当前目录,将进程的用户 ID 设置为登录者的用户 ID,并执行 Shell。 此时,Shell 已准备好接收命令,并且登录协议已完成。
同时,init的主流路径(其自身所有子实例的父实例,稍后将成为Shell)进行等待。 如果其中一个子进程终止,或者是因为 Shell 发现文件结尾,或者是因为用户输入了不正确的名称或密码,则 init 的此路径只会重新创建已失效的进程,从而重新打开相应的输入和输出文件和类型 另一条登录消息。 因此,用户只需在 Shell 中键入文件结束序列而不是命令即可注销。
6.7 其他作为 Shell 的程序
如上所述的 Shell 旨在允许用户完全访问系统的设施,因为它将调用具有适当保护模式的任何程序的执行。 然而,有时需要不同的系统接口,并且此功能很容易安排。
回想一下,用户通过提供用户名和密码成功登录后,init 通常会调用 Shell 来解释命令行。 密码文件中的用户条目可能包含登录后要调用的程序的名称,而不是 Shell。 该程序可以自由地以任何它希望的方式解释用户的消息。
例如,秘书编辑系统用户的密码文件条目指定使用编辑器 ed 而不是 Shell。 因此,当编辑系统用户登录时,他们就在编辑器内部,可以

立即开始工作; 此外,还可以阻止他们调用不适合他们使用的 UN;X 程序。 实际上,事实证明,允许暂时退出编辑器以执行格式化程序和其他实用程序是可取的。
UNIX 上提供的几种游戏(例如国际象棋、二十一点、3D 井字棋)说明了更加严格的受限环境。 对于其中的每一个,密码文件中都存在一个条目,指定要调用适当的游戏程序而不是 Shell。 作为其中一款游戏的玩家登录的人们会发现自己仅限于游戏,无法从整体上研究 UNJX 可能更有趣的产品。
7. 陷阱
PDP-1 l 硬件检测许多程序错误,例如对不存在的内存的引用、未实现的指令以及在需要偶数地址时使用奇数地址。 此类故障会导致处理器陷入系统例程。 当发现非法行为时,除非另有安排,否则系统将终止进程并将用户的图像写入当前目录下的文件 core 中。 调试器可用于确定发生故障时程序的状态。
正在循环、产生不需要的输出或用户重新考虑的程序可以通过使用中断信号来停止,该中断信号是通过键入“删除”字符生成的。 除非采取特殊操作,否则该信号只会导致程序停止执行,而不生成核心映像文件。
还有一个退出信号用于强制生成核心映像。 因此,意外循环的程序可能会被停止,并在没有预先安排的情况下检查核心映像。
根据请求,硬件生成的故障以及中断和退出信号可以被进程忽略或捕获。 例如,Shell 会忽略退出以防止退出导致用户注销。编辑器捕获中断并返回到其命令级别。
这对于停止长打印输出而不丢失正在进行的工作非常有用(编辑器操纵它正在编辑的文件的副本)。 在没有浮点硬件的系统中,未实现的指令将被捕获,并解释浮点指令。

8. 视角
也许矛盾的是,UNIX 的成功很大程度上是由于它的设计目的不是为了满足任何预定义的目标。 第一个版本是在我们中的一个人(汤普森)对
可用的计算机设施,发现很少使用
通讯 1974 年 7 月
第17卷
ACM 编号 7

PDP-7 并着手创造一个更加友善的环境。 这种本质上是个人的努力非常成功,引起了剩余作者和其他人的兴趣,后来证明了购买 POP-11/20 的合理性,特别是支持文本编辑和
格式化系统。 当 11/20 逐渐被淘汰时,事实证明 UNIX 的有用性足以说服管理层投资 PDP-11/45。 我们在整个努力中的目标,一旦明确表达出来,
他们始终致力于与机器建立舒适的关系,并致力于探索操作系统的想法和发明。 我们不需要满足别人的要求,我们对这种自由心怀感激。
回顾过去,可以看到影响 UNIX 设计的三个考虑因素。
首先,既然我们是程序员,我们自然会设计系统以方便编写、测试和运行程序。 我们对编程便利性的渴望的最重要体现是系统被安排为交互式使用,尽管原始版本仅支持一个用户。 我们相信,正确设计的交互系统比“批处理”系统更高效、更令人满意。 此外,这样的系统相当容易适应非交互式使用,但反之则不然。
其次,系统及其软件一直存在相当严格的尺寸限制。 鉴于对合理效率和表现力的部分对立的渴望,尺寸限制不仅鼓励经济,而且鼓励设计的一定优雅。
这可能是“通过苦难得救”哲学的一个几乎不加掩饰的版本,但在我们的例子中它起作用了。
第三,几乎从一开始,该系统就能够并且确实进行了自我维护。 这个事实比看起来更重要。 如果系统的设计者被迫使用该系统,他们很快就会意识到其功能和表面的缺陷,并有强烈的动力去纠正它们,以免为时已晚。 由于所有源程序始终可用且易于在线修改,因此当其他人发明、发现或建议新想法时,我们愿意修改和重写系统及其软件。
本文讨论的 UNIX 方面至少清楚地展示了这些设计考虑因素中的前两个。 例如,从编程的角度来看,文件系统的接口非常方便。
尽可能低的接口级别旨在消除各种设备和文件之间以及直接访问和顺序访问之间的区别。 不需要大型的“访问方法”例程来使程序员免受系统调用的影响; 事实上,所有用户程序要么直接调用系统,要么使用一个小型库程序,只有几十条指令长,它缓冲许多字符并一次读取或写入它们。

编程便利性的另一个重要方面是不存在由文件系统或其他系统调用部分维护和依赖的具有复杂结构的“控制块”。 一般来说,程序地址空间的内容是程序的属性,我们试图避免对该地址空间内的数据结构施加限制。

考虑到所有程序都应可与任何文件或设备一起用作输入或输出的要求,从空间效率的角度来看,还希望将与设备相关的考虑因素推入操作系统本身。 唯一的选择似乎是加载用于处理每个设备的所有程序的例程,这在空间上是昂贵的,或者依赖于在实际需要时动态链接到适合每个设备的例程的某种方法,这也是昂贵的 在开销或硬件中。
同样,过程控制方案和命令界面也被证明既方便又高效。 由于 Shell 作为普通的、可交换的用户程序运行,因此它不消耗系统本身的有线空间,并且可以以很少的成本使其功能强大。 特别是,考虑到 Shell 作为一个进程执行的框架,该进程生成其他进程来执行命令,I/O 重定向、后台进程、命令蝇和用户可选择的系统接口的概念都变得基本上实现起来微不足道。

8.1 影响
'UNIX 的成功并不在于新的发明,而在于对一组精心挑选的丰富思想的充分利用,特别是表明它们可以成为实现小而强大的操作系统的关键。
分叉操作,本质上是我们实现的,存在于伯克利分时系统中 [8]。 在很多方面我们都受到 Multics 的影响,它提出了 I/O 系统调用的特定形式 [9] 以及 Shell 的名称及其一般功能。 Shell 应该创建进程的概念
Multics 的早期设计也向我们建议了每个命令,尽管在该系统中后来出于效率原因而被放弃。 TENEX [10] 使用了类似的方案。

9. 统计
以下来自 UNIX 的统计数据显示了系统的规模以及如何使用这种规模的系统。 我们那些不参与文档准备的用户倾向于使用该系统进行程序开发,尤其是语言工作。 重要的“应用程序”程序很少。
374 通讯 1974 年 7 月
第17卷
ACM 编号 7

9.1 总体
72 用户群体
最多 14 个同时用户
300 个目录
4400 个文件
使用了 34000 个 512 字节辅助存储块
9.2 每天(每天 24 小时,每周 7 天)
有一个“后台”进程运行在
尽可能低的优先级; 它用于吸收任何空闲的
CPU 时间。 它已被用来产生百万位数字
常数 e - 2 的近似值,现在是
生成复合伪素数(基数为 2)。
1800 个命令
4.3 CPU 小时(后台除外)
70 连接小时
30个不同的用户
75 次登录
9.3 命令CPU使用率(1%截止)
15.7% C 编译器 1.7% Fortran 编译器
15.2% 用户程序 1.6% 删除文件
11.7% 编辑器 1.6% 磁带存档
5.8% Shell(用作玉米)- 1.6% 文件系统一致性
命令,包括 com- 检查
mand times) 1.4% 库维护者
5.3% 国际象棋 1.3% 连接/打印文件
3.3% 列出目录 1.3% 分页和打印文件
3.1% 文档格式化程序 1.1% 打印磁盘使用率
1.6% 备份转储程序 1.0% 复制文件
1.8% 汇编程序
9.4 命令访问(1% 截止)
15.3% 编辑器 1.6% 调试器
9.6% 列出目录 1.6% Shell(用作命令)
6.3% 删除文件 1.5% 打印磁盘可用性
6.3% C 编译器 1.4% 列出正在执行的进程
6.0% 连接/打印文件 1.4% 汇编程序
6.0% 用户程序 1.4% 打印参数
3.3% 列出登录人员 1.2% 复制文件
系统1.1%分页和打印文件
3.2% 重命名/移动文件 1.1% 打印当前日期/时间
3.1% 文件状态 1.1% 文件系统一致性
1.8% 库维护者检查
1.8% 文档格式化程序 1.0% 磁带存档
1.6% 有条件地执行另一个命令
9.5 可靠性
我们的可靠性统计数据比其他统计数据更加主观。 以下结果符合我们的综合记忆。 时间跨度超过一年,是非常早期的年份 11/45。
由于软件无法处理硬件问题导致反复出现电源故障陷阱,导致文件系统丢失(五分之一)。 该磁盘上的文件已备份三天。
“崩溃”是指计划外的系统重新启动或停止。 大约每隔一天就会发生一起事故; 其中大约三分之二是由与硬件相关的 dif375 故障引起的,例如电源骤降和莫名其妙的处理器随机位置中断。 其余的都是软件故障。 最长的不间断运行时间约为两周。 服务呼叫平均每三周一次,但高度集中。
总的正常运行时间大约占我们 365 天 24 小时计划的 98%。
致谢。 我们感谢 R.H. Canaday,
L.L.樱桃和L.E. McMahon 对 uNIX 做出的贡献。 我们特别感谢
R. Morris、M.D. McIlroy 和 J.F. Ossanna 的创造力、深思熟虑的批评和持续支持。

你可能感兴趣的:(unix,服务器)