Plan9
到了1980年代中期,计算机的趋势从大型中央化分时共享计算机向网络化的更小型的个人计算机,通常是UNIX“工作站”转变。人们已经厌倦了超载的、官僚主义的分时共享计算机,渴望转向小型、自我维护的系统,即使这意味着计算能力的净损失。随着微型计算机变得更快,甚至可以恢复这种损失,这种计算方式至今仍很受欢迎。
然而,在追求个人工作站的过程中,它们的一些弱点被忽视了。首先,它们所运行的操作系统UNIX本身就是一个旧的分时共享系统,在它之后产生的想法上遇到了困难。图形和网络功能是在UNIX的使用寿命后期加入的,仍然集成得不好且难以管理。更重要的是,早期专注于拥有私人计算机使得机器网络难以像旧的单块分时共享系统一样无缝地服务。分时共享将管理和成本分摊集中起来;个人计算机则分裂、民主化,最终加剧了管理问题。选择一个旧的分时共享操作系统来运行这些个人计算机使得它们很难平稳地联系在一起。
Plan9于1980年代后期开始,旨在尝试两全其美:
使用便宜的现代微型计算机作为计算元件,构建一个集中管理和成本有效的系统。
这个想法是从工作站中构建一个分时共享系统,但是采用了一种新颖的方式。不同的计算机将处理不同的任务:人们办公室中的小型便宜机器将作为终端提供访问大型中央共享资源的功能,例如计算服务器和文件服务器。对于中央计算机来说,即将到来的共享内存多处理器显然是理想的候选。这一哲学很像剑桥分布式系统[NeHe82]。早期的口号是从许多小系统中构建UNIX,而不是从许多小UNIX中构建系统。
UNIX的问题太深,无法修复,但其中的一些想法还不错。最好的想法是它使用文件系统来协调资源的命名和访问,甚至那些传统上不被视为文件的设备资源。对于 Plan9,我们采用了这个想法,通过设计一个称为9P的网络协议,让机器可以访问远程系统上的文件。在此基础上,我们建立了一个命名系统,使人们和他们的计算代理可以在网络中构建自定义的资源视图。这是Plan9首次开始显得不同:Plan9用户构建一个私有的计算环境,并在需要时重新创建它,而不是在私有机器上进行所有计算。很快就清楚了,这种模式比我们预料的要丰富,对进程级命名空间和类似文件系统的资源的想法在整个系统中得到了扩展,包括进程、图形甚至网络本身。
到了1989年,系统已经足够稳定,我们中的一些人开始将其用作我们的独占计算环境。这意味着带着我们在UNIX上使用过的许多服务和应用程序一起转移。我们利用这个机会重新审视了许多问题,不仅仅是内核驻留问题,我们认为UNIX处理得不好的问题。Plan9具有新的编译器、语言、库、窗口系统和许多新应用程序。许多旧工具被舍弃,而带来的工具则被打磨或重新编写。
为什么要这样全面?操作系统、库和应用程序之间的区别对操作系统研究人员很重要,但对用户来说并不重要。重要的是干净的功能。通过构建一个完整的新系统,我们能够解决我们认为应该解决的问题。例如,在内核中没有真正的“tty驱动程序”;这是窗口系统的工作。在现代世界中,多厂商和多架构计算是必不可少的,然而通常的编译器和工具假定程序正在本地运行;我们需要重新思考这些问题。最重要的是,一个系统的测试是它提供的计算环境。生产一个更有效地运行旧UNIX核心软件的方法是空洞的工程;我们更感兴趣的是底层系统架构所提出的新思路是否鼓励了更有效的工作方式。因此,虽然Plan9为运行POSIX命令提供了模拟环境,但它是系统的一个旁支。绝大多数系统软件都是在“本地”的Plan9环境中开发的。
拥有一个全新的系统有益处。首先,我们的实验室有建造实验外围板的历史。为了便于编写设备驱动程序,我们需要一个以源代码形式提供的系统(即使在UNIX诞生的实验室中,也不能保证这一点)。此外,我们想要重新分发我们的工作,这意味着软件必须是本地制作的。例如,我们可以使用一些供应商的C编译器来制作我们的系统,但即使我们克服了交叉编译的问题,我们也将很难重新分发结果。
本文作为该系统的概述。它从最低的构建块讨论了系统的架构,到用户看到的计算环境。它还作为Plan9程序员手册的介绍,伴随着该手册。有关本文中的主题的更多详细信息可以在手册的其他地方找到。
设计
该系统的视图建立在三个原则之上。首先,资源的命名和访问类似于层次文件系统中的文件。其次,有一个名为9P的标准协议,用于访问这些资源。第三,不同服务提供的不相交的层次结构被合并到单个私有层次文件名空间中。Plan 9的非凡特性源于这些原则的一致、积极的应用。
一个大型的Plan 9安装有多台计算机组成网络,每台计算机提供一种特定类型的服务。共享多处理器服务器提供计算能力;其他大型机器提供文件存储。这些机器位于空调机房,通过高性能网络连接。低带宽网络(如以太网或ISDN)将这些服务器连接到办公室和家庭的工作站或个人计算机,Plan 9术语中称为终端。图1显示了这种安排。
http://cat.0xffff.me/_static/imgs/plan9-1.png
现代计算机的风格为每个用户提供专用的工作站或个人电脑。Plan 9的方法则不同。具有屏幕、键盘和鼠标的各种机器都提供对网络资源的访问,因此它们在功能上相当于旧的分时共享系统所连接的终端。然而,当有人使用系统时,该终端会暂时地被该用户个性化。Plan 9不是通过定制硬件来实现个性化,而是通过给公共可见资源命名本地的个人名称来提供自定义的软件视图的能力。这种自定义是通过使用本地的个人名称来组装公共空间的个人视图和全局可访问资源的机制来实现的。由于网络的最重要的资源是文件,因此该视图的模型是面向文件的。
客户端的本地Namespace提供了一种自定义用户对网络的视图的方式。网络中提供的服务都导出文件层次结构。对于用户重要的服务都聚集在一起形成定制的Namespace;对于不立即感兴趣的服务则被忽略。这与“统一全局Namespace”的想法不同。在Plan 9中,服务有已知的名称,文件由这些服务导出,但视图是完全本地的。类比一下,“我的房子”和说话人家的精确地址之间的差别。后者可以被任何人使用,但前者更容易说出来,在不同的人说出来时,也有不同的含义,但这不会导致混淆。类似地,在Plan 9中,名称/dev/cons总是指用户的终端,/bin/date则是运行日期命令的正确版本,但这些名称代表哪些文件取决于诸如执行日期的机器的体系结构等情况。因此,Plan 9具有遵循全球公认的约定的本地Namespace;正是这些约定在存在本地名称的情况下保证了理智的行为。
9P协议被结构化为一组事务,它们向(本地或远程)服务器发送请求并返回结果。9P控制文件系统,而不仅仅是文件:它包括解析文件名和遍历服务器提供的文件系统名称层次结构的过程。另一方面,客户端的Namespace仅由客户端系统持有,而不是在服务器上或与服务器一起持有,这与Sprite [OCDNW88]等系统不同。此外,文件访问是按字节级别进行的,而不是块级别的,这将9P与NFS和RFS等协议区分开来。Welch的一篇论文比较了Sprite、NFS和Plan 9的网络文件系统结构[Welc94]。
这种方法是针对传统文件设计的,但可以扩展到许多其他资源。Plan 9服务导出文件层次结构,包括I/O设备、备份服务、窗口系统、网络接口等。其中一个例子是进程文件系统/proc,它提供了一种清晰的方式来检查和控制正在运行的进程。先驱系统也有类似的想法[Kill84],但Plan 9将文件比喻推到了更远的地步[PPTTW93]。文件系统模型是众所周知的,无论是系统构建者还是一般用户都很熟悉,因此使用类似文件的接口构建服务易于实现、易于理解和易于使用。文件带有保护、命名和访问的约定,既适用于本地访问,也适用于远程访问,因此采用这种方式构建的服务是分布式系统的完美选择。(这与“面向对象”的模型不同,在这种模型中,必须为每个对象类重新考虑这些问题。)接下来的部分将通过实际示例说明这些思想。
The Command-level View
Plan 9旨在在带有窗口系统的屏幕上使用。它没有UNIX意义上的“电传打字机”概念。裸系统的键盘处理是基本的,但一旦窗口系统8½ [Pike91]运行起来,文本就可以通过弹出菜单的“剪切和粘贴”操作进行编辑,可以在窗口之间复制等等。8½允许编辑过去的文本,不仅限于当前输入行上的文本。8½的文本编辑能力足以取代shell中的特殊功能,例如历史记录、分页和滚动以及邮件编辑器。8½窗口不支持光标寻址,除了一个终端仿真器,用于简化连接到传统系统,Plan 9中没有光标寻址软件。
每个窗口都在单独的Namespace中创建。在窗口中对Namespace进行的调整不会影响其他窗口或程序,因此可以安全地尝试对Namespace进行本地修改,例如在调试时从转储文件系统中替换文件。调试完成后,可以删除窗口,实验装置的所有痕迹都会消失。类似的论点也适用于每个窗口拥有的环境变量、注释(类似于UNIX信号)等私有空间。
每个窗口都运行一个应用程序,例如shell,其标准输入和输出连接到窗口的可编辑文本。每个窗口还具有一个私有位图和通过文件(如/dev/mouse、/dev/bitblt和/dev/cons,类似于UNIX的/dev/tty)对键盘、鼠标和其他图形资源进行复用访问。这些文件由8½提供,它被实现为一个文件服务器。与X窗口不同的是,X窗口中一个新的应用程序通常会创建一个新的窗口来运行,而8½图形应用程序通常在它开始的窗口中运行。一个应用程序可以创建一个新窗口,这是可能且高效的,但这不是该系统的风格。再次对比X,在X中,远程应用程序通过网络调用X服务器来启动运行,而远程8½应用程序则像通常在/dev中看到的窗口的鼠标、bitblt和cons文件一样工作;它不知道这些文件是否是本地的。它只是读写这些文件来控制窗口;网络连接已经存在并复用了。
使用的意图方式是在终端上运行交互式应用程序,如窗口系统和文本编辑器,并在远程服务器上运行计算或文件密集型应用程序。不同的窗口可能在不同的机器上通过不同的网络运行程序,但通过使所有窗口的Namespace等效,这是透明的:相同的命令和资源在计算执行的任何地方都可用,名称相同。
Plan 9的命令集与UNIX的相似。这些命令可以分为几类。一些是旧任务的新程序:像ls、cat和who这样的程序具有熟悉的名称和功能,但是是新的、更简单的实现。例如,who是一个shell脚本,而ps只有95行的C代码。有些命令与它们的UNIX祖先基本相同:awk、troff等已经转换为ANSI C并扩展以处理Unicode,但仍是熟悉的工具。有些是为旧领域而编写的全新程序:shell rc、文本编辑器sam、调试器acid等程序用于取代具有类似功能的更为知名的UNIX工具。最后,大约一半的命令是全新的。
兼容性不是该系统的要求。在旧命令或符号看起来足够好时,我们保留了它们。当它们不适用时,我们替换它们。
文件服务器
一个中央文件服务器存储永久文件,并使用9P导出文件层次结构以在网络上呈现。该服务器是一个独立的系统,只能通过网络访问,并旨在出色地完成其工作。它不运行用户进程,只运行编译到启动镜像中的一组固定例程。主层次结构导出的不是一组磁盘或分离的文件系统,而是一个代表许多磁盘上的文件的单个树形结构。该层次结构由许多用户在各种网络上共享。服务器导出的其他文件树包括临时存储和备份服务(如下所述)等专用系统。
该文件服务器有三个存储级别。我们的中央服务器具有约100兆字节的内存缓冲区、27千兆字节的磁盘和一个写一次、读多次(WORM)唱片机中的350千兆字节的大容量存储。磁盘是WORM的缓存,内存是磁盘的缓存。由于它们的速度要快得多,而且看到的流量比它们缓存的级别多一个数量级,所以每个级别都很快。文件系统中可寻址的数据可能比磁盘的大小要大,因为它们只是缓存。我们的主文件服务器具有约40千兆字节的活动存储。
文件服务器最不寻常的特点来自于它使用WORM设备进行稳定存储。每天早上5点钟,文件系统的转储会自动发生。文件系统被冻结,自上次转储以来修改的所有块都排队等待写入WORM。一旦块被排队,服务就会恢复,而倒推文件系统的只读根目录会显示为所有已采取的转储的层次结构,以其日期命名。例如,目录/n/dump/1995/0315是1995年3月15日早上出现的文件系统镜像的根目录。排队块需要几分钟,但在后台运行的将块复制到WORM的进程可能需要数小时。
转储文件系统的使用有两种方式。首先是由用户自己使用,他们可以直接浏览转储文件系统,或者将其中的一部分附加到其命名空间。例如,为了跟踪错误,可以直接使用三个月前的编译器,或者使用昨天的库链接程序。拥有所有文件的每日快照使得查找特定更改的时间或查找特定日期所做的更改变得容易。人们可以自由地对文件进行大量的假设更改,因为他们知道,可以通过一个单独的复制命令来还原它们。没有备份系统,而是因为转储在文件名空间中,因此可以使用标准工具(如cp、ls、grep和diff)解决备份问题。
另一个(非常罕见的)用途是完整的系统备份。在发生灾难时,可以通过清除磁盘缓存并将活动文件系统的根设置为转储根的副本来初始化活动文件系统。尽管易于执行,但这并不应该轻视:除了丢失转储日期后所做的任何更改外,此恢复方法还会导致系统非常缓慢。缓存必须从WORM重新加载,这比磁盘慢得多。文件系统需要几天时间才能重新加载工作集并恢复其完整性能。
转储中的文件的访问权限与转储时相同。普通实用程序在转储中具有普通权限,无需任何特殊安排。但是,转储文件系统是只读的,这意味着转储中的文件无论其权限位如何都不能被写入;实际上,由于目录是只读结构的一部分,因此甚至无法更改权限。
一旦一个文件被写入WORM,它就不能被删除,所以我们的用户永远不会看到 “请清理你的文件 “的信息,也没有df命令。我们认为WORM 是一个无限的资源。唯一的问题是它需要多长时间来填充。我们的WORM已经为一个大约有50个用户的社区服务了5年,每天都吸收转储,总共消耗了 jukebox 中65%的存储空间。在这段时间里,制造商已经改进了技术,将单个磁盘的容量提高了一倍。如果我们升级到新的介质,我们将拥有比原来的空 jukebox 更多的自由空间。技术创造的存储空间比我们使用的速度快。
不寻常的文件服务器 Plan 9的特点是有各种各样的服务器,为不寻常的服务提供类似文件的接口。其中许多是由用户级进程实现的,尽管这种区别对它们的客户来说并不重要;一项服务是由内核、用户进程还是远程服务器提供的,与它的使用方式无关。有几十个这样的服务器;在本节中,我们将介绍三个有代表性的服务器。
也许Plan9中最引人注目的文件服务器是8½,即窗口系统。它在其他地方有详细的讨论[Pike91],但在这里值得简单解释一下。8½提供了两个界面:对坐在终端的用户来说,它提供了一种传统的多窗口交互方式,每个窗口都在运行一个应用程序,都由鼠标和键盘控制。对客户程序来说,视图也是相当传统的:在窗口中运行的程序在/dev中看到一组文件,名称是鼠标、屏幕和控制台。想在窗口中打印文本的程序会写到/dev/cons;要读取鼠标,他们会读取/dev/mouse。在Plan 9风格中,位图图形是通过提供一个文件/dev/bitblt来实现的,客户在这个文件上写编码信息来执行图形操作,如bitblt(RasterOp)。不寻常的是这是如何做到的:8½是一个文件服务器,将/dev中的文件提供给每个窗口中运行的客户端。尽管每个窗口对其客户端来说都是一样的,但每个窗口在/dev中都有一组不同的文件。8½通过提供多组文件,使客户对终端的资源进行多重访问。每个客户都有一个私有的名字空间,其中有一组不同的文件,这些文件在所有其他窗口中的表现是一样的。这种结构有很多优点。其一是8½提供它自己实现所需的相同文件–它复用了自己的接口–因此它可以作为自己的一个客户端,递归地运行。另外,考虑到UNIX中/dev/tty的实现,它需要在内核中使用特殊的代码来重定向开放调用到适当的设备。相反,在8½中,相应的服务会自动出现:8½将/dev/cons作为其基本功能,没有任何额外的事情要做。当一个程序想从键盘上读取数据时,它就会打开/dev/cons,但它是一个私有文件,而不是一个具有特殊属性的共享文件。同样,本地Namespace使之成为可能;关于其中文件的一致性的约定使之成为自然。
8½有一个独特的功能,它的设计使之成为可能。因为它是作为一个文件服务器实现的,所以它有能力推迟响应特定窗口的读取请求。这种行为是通过键盘上的一个保留键来切换的。切换一次就会暂停客户端对该窗口的读取;再次切换就会恢复正常的读取,即吸收任何已经准备好的文本,一次一行。这允许用户在应用程序看到之前在屏幕上编辑多行输入文本,避免了调用一个单独的编辑器来准备文本,如邮件信息。一个相关的特性是直接从定义显示器上的文本的数据结构中回答读数:文本可以被编辑,直到其最后的换行使准备好的文本行可以被客户端读取。即使如此,在该行被读取之前,客户端将读取的文本也可以被改变。例如,在输入了
% make
rm *
到shell,用户可以在任何时候在最后的换行上退格,直到make完成,从而延缓rm命令的执行,甚至可以在rm之前用鼠标指一下,然后输入另一条要先执行的命令。
在Plan 9中没有ftp命令,相反,一个名为ftpfs的用户级文件服务器会拨号到FTP站点,代表用户登录,并使用FTP协议检查远程目录中的文件。对本地用户来说,它提供了一个文件层次,连接到本地Namespace中的/n/ftp,反映了FTP站点的内容。换句话说,它把FTP协议翻译成9P,以提供Plan 9对FTP站点的访问。实现起来很棘手;ftpfs必须做一些复杂的缓存以提高效率,并使用启发式方法来解码远程目录信息。但结果是值得的:所有的本地文件管理工具,如cp、grep、diff,当然还有ls,都可以用于FTP提供的文件,就像它们是本地文件一样。其他系统如Jade和Prospero也利用了同样的机会[Rao81, Neu92],但由于本地Namespace和实现9P的简单性,这种方法更自然地适合Plan 9而不是其他环境。
一个服务器,exportfs,是一个用户进程,它从自己的名字空间中抽取一部分,通过将9P请求转化为对Plan 9内核的系统调用,使其对其他进程可用。它导出的文件层次可能包含来自多个服务器的文件。Exportfs通常作为一个远程服务器运行,由一个本地程序启动,要么是import,要么是cpu。Import对远程机器进行网络调用,在那里启动exportfs,并将其9P连接附加到本地Namespace。例如、
import helix /net
使Helix的网络接口在本地/net目录中可见。Helix是一个中央服务器,有许多网络接口,所以这允许一台有网络的机器访问Helix的任何网络。经过这样的导入,本地机器可以在连接到Helix的任何网络上进行调用。另一个例子是
import helix /proc
这使得Helix的进程在本地/proc中可见,允许本地调试器检查远程进程。
cpu命令将本地终端连接到一个远程CPU服务器。它的工作方向与import相反:在调用服务器后,它启动一个本地的exportfs,并将其挂载在服务器上一个进程的名字空间中,通常是一个新创建的shell。然后,它重新安排Namespace,使本地设备文件(例如由终端的窗口系统提供的文件)在服务器的/dev目录中可见。因此,运行cpu命令的效果是在一个快速的机器上启动一个shell,一个与文件服务器更紧密联系的shell,其Namespace与本地的类似。所有本地设备文件都是远程可见的,因此远程应用程序可以完全访问本地服务,如位图图形、/dev/cons等等。这与rlogin不同,rlogin在远程系统上不做任何重现本地Namespace的事情,也与文件共享不同,例如NFS,它可以实现一些Namespace的等同性,但不能实现对本地硬件设备、远程文件和远程CPU资源的访问。cpu命令是一个独特的透明机制。例如,在运行cpu命令的窗口中启动一个窗口系统是合理的;在那里创建的所有窗口会自动启动CPU服务器上的进程。
Configurability and administration
Plan 9中组件的统一互连使得Plan 9的安装有可能以许多不同的方式进行配置。一台笔记本电脑可以作为一个独立的Plan 9系统;在另一个极端,我们的设置有中央多处理器CPU服务器和文件服务器以及几十个终端,从小型PC到高端图形工作站。正是这样的大型装置最能代表Plan 9的运作方式。
系统软件是可移植的,同样的操作系统在所有硬件上运行。除了性能之外,比如说SGI工作站上的系统外观与笔记本电脑上的系统外观是一样的。由于计算和文件服务是集中的,而终端没有永久的文件存储,所有的终端在功能上是相同的。这样一来,Plan 9就具有老式分时系统的一个良好特性,即用户可以坐在任何一台机器前,看到同样的系统。在现代工作站社区中,机器往往被人们所拥有,他们通过在本地磁盘上存储私人信息来定制机器。我们拒绝这种使用方式,尽管系统本身也可以这样使用。在我们的小组中,我们有一个实验室,里面有许多可以公开使用的机器–一个终端室–用户可以坐在其中任何一台机器上工作。
中央文件服务器不仅集中了文件,而且还集中了文件的管理和维护。事实上,一台服务器是主服务器,保存着所有的系统文件;其他服务器提供额外的存储空间,或者可用于调试和其他特殊用途,但系统软件驻留在一台机器上。这意味着每个程序都有一份适用于每个架构的二进制文件,所以安装更新和错误修复是很简单的。还有一个单一的用户数据库;不需要同步不同的/etc/passwd文件。另一方面,依赖一个中央服务器确实限制了安装的规模。
集中式文件服务的另一个例子是Plan 9管理网络信息的方式。在中央服务器上有一个目录,/lib/ndb,它包含了管理本地以太网和其他网络的所有必要信息。所有的机器都使用同一个数据库与网络对话;不需要管理一个分布式的命名系统,也不需要保持并行文件的更新。要在本地以太网上安装一台新机器,选择一个名称和IP地址,并将其添加到/lib/ndb中的一个文件中;安装中的所有机器将能够立即与它对话。要开始运行,将机器插入网络,打开它,并使用BOOTP和TFTP来加载内核。其他都是自动的。
最后,自动转储文件系统将所有用户从维护其系统的需要中解放出来,同时在没有磁带、特殊命令或支持人员参与的情况下提供对备份文件的轻松访问。这种服务所带来的生活方式的改善是难以言表的。
Plan 9可以在各种硬件上运行,而不限制如何配置安装。在我们的实验室,我们选择使用中央服务器,因为它们可以摊薄成本和管理。这是一个很好的决定的标志是,我们的廉价终端在大约五年内仍然是舒适的工作场所,比必须提供完整计算环境的工作站要长很多。然而,我们确实对中央机器进行了升级,因此,即使是老式的Plan 9终端,其计算能力也会随着时间的推移而提高。避免定期升级终端所节省的资金转而用在最新、最快的多处理器服务器上。我们估计这样做的成本大约是联网工作站的一半,但却提供了对更强大机器的普遍访问。
C 语言编程 Plan 9的实用程序是用几种语言编写的。一些是shell的脚本,rc[Duff90];少数是用一种新的类似C的并发语言Alef[Wint95]编写的,下面会介绍。不过,绝大多数是用ANSI C[ANSIC]的方言编写的。在这些程序中,大多数是全新的程序,但也有一些是来自我们研究的UNIX系统[UNIX85]的前ANSI C代码。这些程序已被更新为ANSI C语言,并为可移植性和清洁性进行了重写。
Plan 9 C方言有一些小的扩展,在其他地方有描述[Pike95],还有一些主要限制。最重要的限制是,编译器要求所有的函数定义都有ANSI原型,所有的函数调用都出现在函数的原型声明的范围内。作为一个风格上的规则,原型声明被放在一个头文件中,由所有调用该函数的文件包含。每个系统库都有一个相关的头文件,对该库中的所有函数进行声明。例如,标准的Plan 9库被称为libc,所以所有的C源代码文件都包括
另一个限制是,C语言编译器只接受ANSI要求的预处理指令的一个子集。主要的遗漏是#if,因为我们认为它根本没有必要,而且经常被滥用。而且,它的效果可以通过其他方式更好地实现。例如,用于在编译时切换功能的#if可以写成一个普通的if语句,依靠编译时的常量折叠和死代码消除来丢弃目标代码。 在Plan 9中,即使有#ifdef,也很少使用条件编译。系统中唯一依赖架构的#ifdefs是在图形库的低级例程中。相反,我们避免这种依赖性,或者在必要时,将它们隔离在单独的源文件或库中。除了使代码难以阅读之外,#ifdefs还使我们无法知道哪些源代码被编译到二进制文件中,或者受其保护的源代码是否能正常编译或工作。它们使软件的维护变得更加困难。 标准的Plan 9库与ANSI C和POSIX [POSIX]的大部分内容重叠,但在适合Plan 9的目标或实现的情况下会有分歧。当一个函数的语义改变时,我们也会改变其名称。例如,代替UNIX的creat,Plan 9有一个create函数,它需要三个参数,原来的两个加上第三个,像open的第二个参数一样,定义返回的文件描述符是为读、写还是为两者打开。这种设计是由9P实现创建的方式所迫使的,但它也简化了创建初始化临时文件的常见用法。
另一个与ANSI C不同的地方是,Plan 9使用了一个叫做Unicode[ISO10646, Unicode]的16位字符集。虽然我们没有完全实现国际化,但Plan 9在其所有软件中统一处理所有主要语言的表示。为了简化程序之间的文本交换,字符被我们设计的编码打包成一个字节流,称为UTF-8,现在已经被接受为一个标准[FSSUTF]。它有几个吸引人的特性,包括字节顺序的独立性、与ASCII的向后兼容以及易于实现。
要使现有的软件适应一个大的字符集,并采用表示字符的字节数不固定的编码,有很多问题。ANSI C解决了其中的一些问题,但没有解决所有问题。它没有选择一个字符集编码,也没有定义所有必要的I/O库例程。此外,它所定义的函数也有工程问题。由于该标准留下了太多的问题没有解决,我们决定建立我们自己的接口。另一篇论文有详细介绍[Pike93]。
有一小类Plan 9程序不遵循本节讨论的惯例。这些程序是由UNIX社区导入并维护的;tex就是一个有代表性的例子。为了避免每次发布新版本时都要重新转换这些程序,我们建立了一个移植环境,称为ANSI C/POSIX环境,或APE [Tric95]。APE 由独立的包含文件、库和命令组成, 尽可能地符合严格的 ANSI C 和基本的 POSIX 规范。为了移植基于网络的软件,如X Windows,有必要在这些规范中加入一些扩展,如BSD的网络功能。
可移植性和编译 Plan 9可以在不同的处理器架构上移植。在一个单一的计算环节中,使用几种架构是很常见的:也许窗口系统运行在英特尔处理器上,与基于MIPS的CPU服务器相连,文件驻留在SPARC系统上。为了使这种异质性透明化,必须有关于程序间数据交换的约定;为了使软件维护简单明了,必须有关于跨体系结构编译的约定。 为了避免字节顺序问题,在实际情况下,程序之间的数据是以文本形式进行通信的。但有时,数据量大到必须使用二进制格式;这种数据以字节流的形式进行通信,并对多字节值进行预先定义的编码。在极少数情况下,如果一个格式复杂到足以由一个数据结构来定义,那么这个结构永远不会作为一个单元来交流;相反,它被分解成各个字段,被编码为一个有序的字节流,然后由接收者重新组装起来。这些约定影响了从内核或应用程序状态信息到由编译器生成的对象文件中介的数据。 包括内核在内的程序,经常通过文件系统接口来呈现它们的数据,这种访问机制本身就是可移植的。例如,系统时钟由文件/dev/time中的一个十进制数字表示;时间库函数(没有时间系统调用)读取该文件并将其转换为二进制。同样地,内核并没有将应用进程的状态编码为私有内存中的一系列标志和比特,而是在与每个进程相关的/proc文件系统中的名为status的文件中呈现一个文本字符串。Plan 9的ps命令是微不足道的:它在经过一些小的重新格式化后,打印出所需的状态文件的内容;此外,在
import helix /proc
本地ps命令报告Helix的进程状态。
每个支持的架构都有自己的编译器和加载器。C和Alef编译器产生的中间文件是可移植编码的;其内容对目标架构是唯一的,但文件的格式与编译处理器类型无关。当一个给定架构的编译器在另一种类型的处理器上编译,然后用来编译一个程序时,在新架构上产生的中间文件与在本地处理器上产生的中间文件是相同的。从编译器的角度来看,每次编译都是一次交叉编译。
尽管每个架构的加载器只接受由该架构的编译器产生的中间文件,但这些文件可以由在任何类型的处理器上执行的编译器产生。例如,有可能在486上运行MIPS编译器,然后在SPARC上使用MIPS装载器来生成MIPS可执行文件。
由于Plan 9可以在各种架构上运行,即使是在单一的安装中,区分编译器和中间名称可以简化从单一源码树进行的多架构开发。每个架构的编译器和加载器都有唯一的名字;没有cc命令。这些名字是通过将与目标架构相关的代码字母与编译器或加载器的名字连接起来而得到的。例如,字母’8’是Intel x86处理器的代码字母;C编译器被命名为8c,Alef编译器被命名为8al,加载器被称为8l。同样,编译器的中间文件的后缀是.8,而不是.o。
Plan 9的编译程序mk是make的一个亲戚,它从名为\(cputype和\)objtype的环境变量中读取当前和目标架构的名称。默认情况下,当前的处理器是目标,但在调用mk之前,将$objtype设置为另一个架构的名称,会导致交叉构建:
% objtype=sparc mk
构建SPARC架构的程序,而不考虑执行的机器。$objtype的值选择了一个与架构相关的变量定义文件,该文件配置了编译器和加载器的使用。虽然思想简单,但这种技术在实践中效果很好:Plan9中的所有应用程序都是从一个单一的源码树中构建的,可以并行地构建各种架构而不发生冲突。
Parallel programming
Plan 9对并行编程的支持有两个方面。首先,内核提供了一个简单的进程模型和一些精心设计的用于同步和共享的系统调用。第二,一种叫做Alef的新的并行编程语言支持并发编程。尽管用C语言编写并行程序是可能的,但Alef是首选的并行语言。
在新的操作系统中,有一种趋势是实现两类进程:正常的UNIX式进程和轻量级的内核线程。相反,Plan 9提供了一个单一类别的进程,但允许精细控制进程的资源共享,如内存和文件描述符。在Plan 9中,单一类别的进程是一种可行的方法,因为内核有一个高效的系统调用接口和廉价的进程创建和调度。
并行程序有三个基本要求:管理进程间共享的资源,与调度器的接口,以及使用自旋锁的细粒度的进程同步。在Plan 9上,新的进程是用rfork系统调用创建的。Rfork需要一个参数,即一个bit vector,指定父进程的哪些资源应该在子进程中被共享、复制或重新创建。rfork控制的资源包括Namespace、环境、文件描述符表、内存段和notes(Plan 9对UNIX信号的模拟)。其中一个位控制rfork调用是否会创建一个新的进程;如果这个位是关闭的,那么对资源的修改就发生在调用的进程中。例如,一个进程调用rfork(RFNAMEG)来断开其Namespace与父进程的连接。Alef使用了一个细粒度的 fork,其中所有的资源,包括内存,都在父代和子代之间共享,类似于在许多系统中创建一个内核线程。
rfork是正确的模型的一个迹象是它被使用的方式的多样性。除了在库例程fork中的典型使用外,很难找到两个对rfork的调用具有相同的位集;程序用它来创建许多不同形式的共享和资源分配。一个只有两类进程的系统–常规进程和线程–无法处理这种多样性。
有两种方法来共享内存。首先,rfork的一个标志导致父代的所有内存段与子代共享(除了堆栈,无论如何,堆栈都是写时分叉的)。另外,可以使用segattach系统调用附加一个新的内存段;这样的内存段将总是在父代和子代之间共享。
rendezvous系统调用为进程提供了一种同步的方式。Alef用它来实现通信通道、排队锁、多个读写器锁、以及睡眠和唤醒机制。Rendezvous需要两个参数,一个标签和一个值。当一个进程用一个标签调用rendezvous时,它就会睡眠,直到另一个进程提出一个匹配的标签。当一对标签匹配时,两个进程之间会交换值,两个交会调用都会返回。这个基元足以实现全套的同步例程。
最后,自旋锁是由用户级别的架构相关库提供的。大多数处理器提供原子测试和设置指令,可以用来实现锁。一个明显的例外是MIPS R3000,所以SGI Power系列多核处理器在总线上有特殊的锁硬件。用户进程通过使用segattach系统调用将硬件锁的页面映射到他们的地址空间来获得对锁硬件的访问。
在系统调用中的Plan 9进程将被阻塞,不管它的 “重量 “如何。这意味着,当一个程序希望从一个慢速设备上读取数据而不阻塞整个计算时,它必须分叉一个进程来为它做读取工作。解决办法是启动一个卫星进程来进行I/O,并通过共享内存或管道将答案传递给主程序。这听起来很麻烦,但在实践中却很容易和有效;事实上,大多数交互式Plan 9应用程序,甚至是用C语言编写的相对普通的应用程序,如文本编辑器Sam [Pike87],都是作为多进程程序运行的。
Plan9中对并行编程的内核支持是几百行可移植的代码;少数简单的基元使问题在用户层面得到干净的处理。尽管这些基元在C语言中运行良好,但在Alef中它们的表现力特别强。从属I/O进程的创建和管理可以用Alef的几行代码来写,为任意进程之间的数据流复用提供了一致的手段。此外,在语言中而不是在内核中实现它,可以确保所有设备之间的语义一致,并提供一个更通用的复用原语。与UNIX的select系统调用相比:select只适用于一组有限的设备,在内核中规定了一种多路复用的方式,不能跨网络扩展,很难实现,也很难使用。
在Plan9中,并行编程很重要的另一个原因是,多线程的用户级文件服务器是实现服务的首选方式。这种服务器的例子包括编程环境Acme[Pike94]、Namespace导出工具exportfs[PPTTW93]、HTTP守护程序以及网络名称服务器cs和dns[PrWi93]。像Acme这样复杂的应用证明,仔细的操作系统支持可以减少编写多线程应用的难度,而不需要将线程和同步原语移入内核。
Implementation of Name Spaces
用户进程使用三个系统调用构建Namespace:mount、bind和unmount。mount系统调用将一个由文件服务器提供的树附加到当前Namespace。在调用mount之前,客户端必须(通过外部手段)以文件描述符的形式获得与服务器的连接,该文件描述符可以被写入和读取以传输9P消息。该文件描述符代表一个管道或网络连接。 mount调用将一个新的层次结构附加到现有的名字空间上。另一方面,bind系统调用在名字空间的另一点上复制了现有名字空间的某一块。unmount系统调用允许组件被移除。 使用bind或mount,多个目录可以在Namespace的一个点上堆叠。用Plan 9的术语来说,这是一个联合目录,其行为类似于组成目录的串联。绑定和挂载的标志参数指定了联盟中新目录的位置,允许在联盟的前部或后部添加新的元素,或者完全替换它。当在一个联合目录中进行文件查找时,联合目录的每一个组成部分都被依次搜索,并采取第一个匹配;同样,当联合目录被读取时,每个组成目录的内容都被依次读取。联合目录是Plan 9Namespace中最广泛使用的组织特征之一。例如,目录/bin是由/\(cputype/bin(程序二进制文件)、/rc/bin(shell脚本)以及可能由用户提供的更多目录组成的。这种结构使得shell中的\)PATH变量成为不必要的。
One question raised by union directories is which element of the union receives a newly created file. After several designs, we decided on the following. By default, directories in unions do not accept new files, although the create system call applied to an existing file succeeds normally. When a directory is added to the union, a flag to bind or mount enables create permission (a property of the name space) in that directory. When a file is being created with a new name in a union, it is created in the first directory of the union with create permission; if that creation fails, the entire create fails. This scheme enables the common use of placing a private directory anywhere in a union of public ones, while allowing creation only in the private directory.
联盟目录提出的一个问题是联盟中的哪个元素接收新创建的文件(One question raised by union directories is which element of the union receives a newly created file.)。经过几次设计,我们决定采用以下方法。默认情况下,联盟中的目录不接受新文件,尽管应用于现有文件的创建系统调用会正常成功。当一个目录被添加到联盟中时,一个绑定或挂载的标志在该目录中启用了创建权限(名字空间的一个属性)。当一个文件在联合体中以一个新的名字被创建时,它被创建在联合体的第一个目录中,并具有创建权限;如果创建失败,整个创建就会失败。这个方案使人们能够在公共目录的联盟中的任何地方放置一个私有目录,同时只允许在私有目录中创建。
按照惯例,内核设备文件系统被绑定到/dev目录中,但是为了引导Namespace的建立过程,有必要有一个符号,允许直接访问没有现有Namespace的设备。设备驱动所服务的树的根目录可以使用语法#c来访问,其中c是一个识别设备类型的唯一字符(通常是一个字母)。简单的设备驱动程序服务于一个包含几个文件的单层目录。例如,每个串行端口由一个数据和一个控制文件表示:
% bind -a ’#t’ /dev
% cd /dev
% ls -l eia*
- rw-rw-rw- t 0 bootes bootes 0 Feb 24 21:14 eia1
- rw-rw-rw- t 0 bootes bootes 0 Feb 24 21:14 eia1ctl
- rw-rw-rw- t 0 bootes bootes 0 Feb 24 21:14 eia2
- rw-rw-rw- t 0 bootes bootes 0 Feb 24 21:14 eia2ctl
bind程序是对bind系统调用的封装;它的-a标志将新目录定位在联盟的末端。数据文件eia1和eia2可以被读写以通过串行线进行通信。与其在这些文件上使用特殊操作来控制设备,不如将命令写入文件eia1ctl和eia2ctl来控制相应的设备;例如,将文本字符串b1200写入/dev/eia1ctl将该线的速度设置为1200波特。与UNIX的ioctl系统调用相比:在Plan 9中,设备由文本信息控制,不存在字节顺序问题,有明确的读写语义。使用shell脚本来配置或调试设备是很常见的。
正是9P协议的普遍使用,将Plan 9的组件连接在一起,形成一个分布式系统。Plan 9没有为每个服务(如rlogin、FTP、TFTP和X窗口)发明一个独特的协议,而是以对文件对象的操作来实现服务,然后使用一个单一的、有据可查的协议在计算机之间交换信息。与NFS不同,9P将文件视为字节序列而不是块。与NFS不同,9P也是有状态的:客户执行远程过程调用以建立指向远程文件服务器中对象的指针。这些指针被称为文件标识符或FID。所有对文件的操作都提供一个fid来识别远程文件系统中的一个对象。
9P协议定义了17条信息,提供了验证用户、在文件系统层次结构中导航fids、复制fids、执行I/O、改变文件属性以及创建和删除文件的方法。它的完整规范在《程序员手册》[9man]的第5节中。下面是访问由服务器提供的名称层次结构的程序。一个文件服务器连接是通过管道或网络连接建立的。一个初始会话消息在客户和服务器之间进行双边认证。然后,一个附加消息将客户端建议的fid连接到服务器文件树的根。附加信息包括执行附加的用户的身份;此后,所有从根fid衍生出来的fid都有与该用户相关的权限。多个用户可以共享连接,但每个人都必须执行附加信息以建立他或她的身份。
walk 消息在文件系统层次结构中的一个层次上移动一个fid。clone消息接收一个已建立的fid并产生一个副本,该副本指向与原文件相同的文件。它的目的是在不丢失目录上的fid的情况下,能够走到目录中的一个文件。open消息将一个fid锁定在层次结构中的一个特定文件上,检查访问权限,并为fid的I/O做准备。读和写消息允许在文件的任意偏移处进行I/O;传输的最大尺寸由协议定义。clunk消息表示客户端对一个fid没有进一步的使用。remove消息的行为与clunk类似,但会导致与该fid相关的文件被删除,并且服务器上的任何相关资源都会被取消分配。
9P有两种形式:在管道或网络连接上发送的RPC消息和内核内的过程性接口。由于内核设备驱动程序是可以直接寻址的,因此不需要通过消息来与它们通信;相反,每个9P事务都是通过直接的过程调用来实现的。对于每个fid,内核在一个叫做通道的数据结构中保持一个本地表示,所以所有由内核执行的对文件的操作都涉及到与该fid相连的通道。最简单的例子是一个用户进程的文件描述符,它是一个通道数组的索引。内核中的一个表提供了一个入口点列表,与每个设备的9P信息一一对应。一个系统调用,如来自用户的读取,通过该表转化为一个或多个过程调用,以通道中存储的类型字符为索引:procread,eiaread,等等。每个调用至少需要一个通道作为参数。一个特殊的内核驱动,称为mount驱动,将过程调用转换为消息,也就是说,它将本地过程调用转换为远程调用。实际上,这个特殊的驱动成为远程文件服务器所提供的文件的本地代理。本地调用中的通道指针被转换为传输消息中的相关fid。
挂载驱动是系统采用的唯一RPC机制。所提供的文件的语义,而不是对它们进行的操作,创造了一个特殊的服务,如cpu命令。挂载驱动对与文件服务器共享通信通道的客户之间的协议信息进行解复用。对于每个传出的RPC消息,挂载驱动分配了一个缓冲区,用一个小的唯一的整数来标记,称为标签。对RPC的回复也被贴上了同样的标签,挂载驱动使用这个标签来匹配回复和请求。
内核对名字空间的表示被称为mount table,它存储了一个通道之间的绑定列表。挂载表的每个条目包含一对通道:一个从通道和一个到通道。每当行走成功地将一个通道移动到名字空间中的一个新位置时,挂载表就会被查阅,看是否有一个 “从 “通道与新的名字相匹配;如果有的话,”到 “通道就会被克隆并替换成原来的。联合目录是通过将 “to “通道转换为一个通道列表来实现的:一个成功走到联合目录的通道会返回一个 “to “通道,该通道形成一个通道列表的头部,每个通道代表联合目录的一个组成部分。如果在联合目录的第一个目录中找不到文件,就会跟踪该列表,克隆下一个组件,并在该目录上尝试walk。
Plan 9中的每个文件都由一组整数唯一标识:通道的类型(用作函数调用表的索引),服务器或设备编号,以区别于其他相同类型的服务器(由驱动程序在本地决定),以及由两个32位数字组成的qid,称为路径和版本。路径是设备驱动程序或文件服务器在创建文件时分配的唯一文件号。版本号在文件被修改时被更新;正如下一节所描述的,它可以被用来保持客户端和服务器之间的缓存一致性。
类型和设备号类似于UNIX的主要和次要设备号;qid类似于i-number。设备和类型将通道连接到设备驱动,而qid则是识别该设备中的文件。如果从走动中恢复的文件与挂载表中的一个条目具有相同的类型、设备和qid路径,它们就是同一个文件,并从挂载表中进行相应的替换。这就是Namespace的实现方式。
文件缓存 9P协议没有明确支持在客户端缓存文件。中央文件服务器的大内存作为其所有客户端的共享缓存,这减少了网络中所有机器所需的内存总量。尽管如此,还是有充分的理由在客户端缓存文件,例如与文件服务器的连接速度很慢。 每当文件被修改时,qid的版本字段就会被改变,这使得做一些弱一致性的缓存形式成为可能。最重要的是客户端对可执行文件的文本和数据段进行缓存。当一个进程执行一个程序时,该文件被重新打开,qid的版本与缓存中的版本进行比较;如果它们匹配,则使用本地副本。同样的方法也可以用来建立一个本地缓存文件服务器。这个用户级的服务器插在与远程服务器的9P连接上,监控流量,将数据复制到本地磁盘。当它看到对已知数据的读取时,它直接回答,而写入的数据则立即传递–缓存是写过的,以保持中央副本的最新状态。这对终端上的进程是透明的,不需要对9P做任何改动;它在通过串行线路连接的家用机器上运行良好。类似的方法也可以应用于在未使用的本地内存中建立一个普通的客户端缓存,但在Plan 9中没有这样做。
网络和通信设备 网络接口是常驻内核的文件系统,类似于前面描述的EIA设备。呼叫的建立和关闭是通过向与设备相关的控制文件写入文本字符串实现的;信息的发送和接收是通过读写数据文件实现的。设备的结构和语义对所有的网络都是通用的,因此,除了文件名的替换之外,使用以太网上的TCP进行调用的程序与使用Datakit上的URP进行调用的程序相同[Fra80]。
This example illustrates the structure of the TCP device:
% ls -lp /net/tcp
d-r-xr-xr-x I 0 bootes bootes 0 Feb 23 20:20 0
d-r-xr-xr-x I 0 bootes bootes 0 Feb 23 20:20 1
- rw-rw-rw- I 0 bootes bootes 0 Feb 23 20:20 clone
% ls -lp /net/tcp/0
- rw-rw—- I 0 rob bootes 0 Feb 23 20:20 ctl
- rw-rw—- I 0 rob bootes 0 Feb 23 20:20 data
- rw-rw—- I 0 rob bootes 0 Feb 23 20:20 listen
- r–r–r– I 0 bootes bootes 0 Feb 23 20:20 local
- r–r–r– I 0 bootes bootes 0 Feb 23 20:20 remote
- r–r–r– I 0 bootes bootes 0 Feb 23 20:20 status
%
最上面的目录,/net/tcp,包含一个克隆文件和每个连接的目录,编号为0到n。每个连接目录对应一个TCP/IP连接。打开克隆文件保留一个未使用的连接并返回其控制文件。读取控制文件返回文本连接号,因此用户进程可以构建新分配的连接目录的全名。本地、远程和状态文件是诊断性的;例如,远程包含了远程端的地址(对于TCP,IP地址和端口号)。
调用是通过编写一个以网络特定地址为参数的连接消息来启动的;例如,要打开一个Telnet会话(端口23)到IP地址为135.104.9.52的远程机器,其字符串为:
connect 135.104.9.52!23
对控制文件的写入会被阻止,直到连接建立起来;如果目的地无法到达,写入会返回一个错误。一旦连接建立,telnet应用程序就会读写数据文件,与远程Telnet守护进程对话。在另一端,Telnet守护进程将开始写下
announce 23
到它的控制文件,以表明它愿意接受对这个端口的呼叫。这样的守护程序在Plan9中被称为监听器。 网络设备的统一结构不能隐藏不同网络的寻址和通信的所有细节。例如,Datakit使用的是文本的、分层的地址,与IP的32位地址不同,所以给一个控制文件的应用程序仍然必须知道它代表什么网络。与其让每个应用程序知道每个网络的地址,Plan 9把这些细节隐藏在一个叫做cs的连接服务器中。Cs是一个安装在已知位置的文件系统。它提供了一个单一的控制文件,应用程序用它来发现如何连接到一个主机。应用程序写下它想建立的连接的符号地址和服务名称,并读回要打开的克隆文件的名称和要提交给它的地址。如果机器之间有多个网络,cs会提出一个可能的网络和地址列表,依次进行尝试;它使用启发式方法来决定顺序。例如,它首先显示最高带宽的选择。 一个叫拨号的库函数与cs对话以建立连接。一个使用拨号的应用程序不需要改变,甚至不需要重新编译,就能适应新的网络;cs的接口隐藏了细节。 Plan9中网络的统一结构使得导入命令成为构建网关的全部需要。
Kernel structure for networks
用于建立Plan 9通信通道的内核管道被称为流[Rit84][Presotto]。一个流是一个连接物理或伪设备和用户进程的双向通道。用户进程在流的一端插入和删除数据;代表设备行事的内核进程在另一端操作。一个流包括一个处理模块的线性列表。每个模块都有一个上游(面向进程)和下游(面向设备)的put例程。调用流两端的模块的put例程,将数据插入流中。每个模块都会调用后面的模块来向上或向下发送数据。像UNIX流[Rit84]一样,Plan 9的流可以动态配置。
The IL Protocol
9P协议必须在一个可靠的传输协议之上运行,并有限定的消息。9P没有从传输错误中恢复的机制,而且系统假定从通信通道中的每一次读取都会返回一个单一的9P消息;它不会解析数据流以发现消息的边界。管道和一些网络协议已经具有这些特性,但标准的IP协议却没有。TCP不给消息划界,而UDP[RFC768]不提供可靠的按顺序交付。 我们设计了一个新的协议,叫做IL(Internet Link),用于在IP上传输9P消息。它是一个基于连接的协议,在机器之间提供可靠的顺序信息传输。由于一个进程只能有一个未完成的9P请求,所以在IL中不需要流量控制。与TCP一样,IL也有自适应超时功能:它将确认和重传时间与网络速度相匹配。这使得该协议在互联网和本地以太网上都能表现良好。另外,IL不做盲目重传,以避免增加繁忙网络的拥堵。全部细节见另一篇论文[PrWi95]。 在Plan9中,IL的实现比TCP要小,要快。IL是我们的主要互联网传输协议。
Overview of authentication
认证确定了访问资源的用户的身份。请求资源的用户被称为客户端,授予资源访问权的用户被称为服务器。这通常是在9P附加消息的支持下完成的。一个用户可能在一个认证交换中是客户,在另一个认证交换中是服务器。服务器总是代表一些用户行事,要么是正常的客户,要么是一些管理实体,所以认证被定义为用户之间的认证,而不是机器。 每个Plan 9用户都有一个相关的DES[NBS77]认证密钥;用户的身份是通过加密和解密被称为挑战的特殊信息的能力来验证的。由于知道一个用户的密钥就可以访问该用户的资源,所以Plan 9认证协议从不传输包含明文密钥的信息。 认证是双边的:在认证交换结束时,每一方都确信对方的身份。每台机器都以内存中的DES密钥开始交换。在CPU和文件服务器的情况下,服务器的密钥、用户名和域名从永久存储中读取,通常是非易失性RAM。在终端的情况下,密钥是由用户在启动时输入的密码得出的。一台特殊的机器,即认证服务器,为其管理域中的所有用户维护一个密钥数据库,并参与认证协议。 认证协议如下:在交换挑战后,一方与认证服务器联系,创建用每一方的秘密密钥加密的权限授予票,并包含一个新的对话密钥。每一方都会解密自己的票据,并使用对话密钥来加密另一方的挑战。 这种结构有点像Kerberos [MBSS87],但避免了其对同步时钟的依赖。同样与Kerberos不同的是,Plan 9认证支持 “speech for “关系[LABW91],使一个用户拥有另一个用户的权限;这就是CPU服务器代表其客户运行进程的方式。 Plan 9的认证结构建立了安全的服务,而不是依赖于防火墙。防火墙需要为每一个穿墙的服务编写特殊的代码,而Plan 9的方法允许在一个地方对所有的服务进行认证-9P。例如,cpu命令在互联网上安全地工作。
Authenticating external connections
常规的Plan 9认证协议不适合基于文本的服务,如Telnet或FTP。在这种情况下,Plan 9用户用称为认证器的手持式DES计算器进行认证。认证器为用户持有一个密钥,与用户的正常认证密钥不同。用户使用一个4位数的PIN码 “登录 “到认证器上。一个正确的PIN码可以使认证器与服务器进行挑战/回应交换。由于正确的挑战/回应交换仅一次有效,而且密钥从未在网络上发送,因此该程序不容易受到重放攻击,但与Telnet和FTP等协议兼容。
Special users
Plan 9没有超级用户。每台服务器负责维护自己的安全,通常只允许从控制台访问,控制台有密码保护。例如,文件服务器有一个独特的管理用户,叫做adm,具有特殊的权限,只适用于在服务器的物理控制台输入的命令。这些权限涉及服务器的日常维护,如添加新用户和配置磁盘和网络。这些权限不包括修改、检查或改变任何文件权限的能力。如果一个文件是由一个用户阅读保护的,只有该用户可以授予其他人访问权。
CPU服务器有一个相当的用户名,允许对该服务器上的资源进行管理访问,如用户进程的控制文件。这种权限是必要的,例如,为了杀死流氓进程,但不超出该服务器的范围。另一方面,通过受保护的非易失性RAM中的密钥,管理用户的身份被证明给认证服务器。这使得CPU服务器可以对远程用户进行认证,包括对服务器本身的访问以及CPU服务器代表他们进行代理的时候。 最后,一个叫做none的特殊用户没有密码,并且总是被允许连接;任何人都可以声称自己是none。None的权限受到限制;例如,它不允许检查转储文件,只能读取世界可读的文件。 无的背后的想法类似于FTP服务中的匿名用户。在Plan 9上,访客FTP服务器被进一步限制在一个特殊的受限Namespace内。它切断了访客用户与系统程序的联系,如/bin的内容,但通过将本地文件明确地绑定到空间中,使其对访客可用。一个受限的名字空间比通常的导出临时目录树的技术更安全;其结果是在不被信任的用户周围形成了一种笼子。
The cpu command and proxied authentication
当为一个用户(比如说彼得)调用CPU服务器时,其意图是彼得希望以自己的权限运行进程。为了实现这一属性,CPU服务器在收到调用时做了以下工作。首先,监听器分叉出一个进程来处理这个呼叫。这个进程改变为用户 None,以避免在它被破坏时泄露权限。然后,它执行认证协议,以验证呼叫用户真的是Peter,并向Peter证明机器本身是值得信赖的。最后,它使用认证协议重新连接到所有相关的文件服务器,以确定自己是彼得。在这种情况下,CPU服务器是文件服务器的客户端,代表Peter执行认证交换的客户端部分。只有当CPU服务器的管理用户名被允许代表Peter发言时,认证服务器才会给进程票据来完成这个任务。 关系的speaks[LABW91]被保存在认证服务器上的一个表中。为了简化对不同认证域中的用户计算的管理,它还包含不同域中的用户名之间的映射,例如说一个域中的用户rtm与另一个域中的用户rtmorris是同一个人。
File Permissions
将服务构建为文件系统的好处之一是,所有权和权限问题的解决方案会自然而然地出现。在UNIX中,每个文件或目录都有单独的读、写和执行/搜索的权限,这些权限分别属于文件的所有者、文件的组以及其他任何人。组的概念是不寻常的:任何用户名都有可能是一个组名。一个组只是一个用户和组内其他用户的列表。惯例做出了区分:大多数人的用户名没有组成员,而组有一长串附加的名字。例如,传统上sys组有所有的系统程序员,而系统文件是由sys组访问的。考虑一下存储在服务器上的用户数据库的以下两行:
pjw:pjw:
sys::pjw,ken,philw,presotto
第一个是建立用户pjw作为一个普通用户。第二个是将用户sys建立为一个组,并列出四个用户是该组的成员。空的冒号分隔的字段是空间,用于指定一个用户作为组长。如果一个组有一个组长,该用户对该组有特殊的权限,例如可以自由地改变该组中文件的组权限。如果没有指定领导者,该组的每个成员都被认为是平等的,就像每个人都是领导者一样。在我们的例子中,只有pjw可以向他的组添加成员,但所有sys的成员都是该组的平等伙伴。 普通文件由创建它们的用户拥有。组的名称是从存放新文件的目录中继承的。设备文件被特殊对待:内核可以安排适合访问该文件的用户的文件所有权和权限。 进程文件就是一个很好的例子,它由进程的所有者拥有并受读保护。如果所有者想让其他人访问进程的内存,例如让程序的作者调试一个损坏的图像,应用于进程文件的标准chmod命令就可以完成这项工作。 文件权限的另一个不寻常的应用是转储文件系统,它不仅由与原始数据相同的文件服务器提供服务,而且由相同的用户数据库代表。因此,dump中的文件被赋予与常规文件系统中的文件相同的保护;如果一个文件由pjw拥有并受阅读保护,一旦它在dump文件系统中,它仍然由pjw拥有并受阅读保护。另外,由于dump文件系统是不可改变的,文件不能被改变;它永远是受读保护的。缺点是,如果文件是可读的,但应该是受读保护的,那么它将永远可读,而且用户名也很难再使用。
Performance
作为衡量Plan 9内核性能的一个简单措施,我们比较了在Plan 9上和在SGI的IRIX Release 5.3上进行一些简单操作的时间,SGI Challenge M上运行着100MHz的MIPS R4400和1百万字节的二级缓存。测试程序是用Alef编写的,用相同的编译器编译,并在相同的硬件上运行,所以唯一的变量是操作系统和库。 该程序测试了进行上下文切换的时间(Plan 9上的rendezvous,IRIX上的blockproc);琐碎的系统调用(rfork(0)和nap(0));以及轻量级的fork(rfork(RFPROC)和sproc(PR_SFDS|PR_SADDR))。它还测量了在管道上从一个进程向另一个进程发送一个字节的时间以及两个进程之间在管道上的吞吐量。结果见表1。
尽管 “Plan9 “的时间并不惊人,但它们表明,该内核与商业系统相比是有竞争力的。
Discussion
Plan9有一个相对传统的内核;系统的新颖性在于内核以外的部分以及它们的互动方式。在构建Plan 9时,我们把系统的所有方面都考虑在内,在解决方案最适合的地方解决问题。有时,解决方案跨越了许多组件。一个例子是异质指令架构的问题,它由编译器(不同的代码字符,可移植的目标代码)、环境(\(cputype和\)objtype)、Namespace(绑定在/bin中)和其他组件来解决。有时,许多问题可以在一个地方得到解决。最好的例子是9P,它集中了命名、访问和认证。9P确实是系统的核心;可以说,Plan 9内核主要是一个9P复用器。 Plan 9对文件和命名的关注是其表现力的核心。特别是在分布式计算中,事物的命名方式对系统有着深刻的影响[Nee89]。本地Namespace和全局约定的结合,使网络资源相互连接,避免了维护全局统一Namespace的困难,而像文件一样命名一切,使系统易于理解,甚至对新手来说。考虑一下dump文件系统,对于熟悉分层文件系统的人来说,它的使用是微不足道的。在更深的层次上,将所有的资源建立在一个统一的接口之上,使互操作性变得容易。一旦一个资源输出了一个9P接口,它就可以与系统的任何其他部分透明地结合起来,建立不寻常的应用;细节是隐藏的。这听起来可能是面向对象的,但也有区别。首先,9P定义了一套固定的 “方法”;它不是一个可扩展的协议。更重要的是,文件被很好地定义和理解,并预装了熟悉的访问、保护、命名和网络方法。对象,尽管其通用性,并没有定义这些属性。通过将 “对象 “简化为 “文件”,Plan 9获得了一些免费的技术。 尽管如此,还是有可能把基于文件的计算的想法推得太远。将系统中的每一个资源转换成文件系统是一种隐喻,而隐喻是可以被滥用的。克制的一个好例子是/proc,它只是一个进程的视图,而不是一个代表。为了运行进程,通常的fork和exec调用仍然是必要的,而不是做一些像
cp /bin/date /proc/clone/mem
这种例子的问题在于,它们要求服务器做不在其控制范围内的事情。为这样的命令赋予意义的能力并不意味着意义会从回答它所产生的9P请求的结构中自然落下。作为一个相关的例子,Plan 9并没有把机器的网络名称放在文件名空间中。网络接口提供了一个非常不同的命名模式,因为在这种文件上使用打开、创建、读取和写入,不会提供一个合适的地方来编码任意网络的调用设置的所有细节。这并不意味着网络接口不能像文件一样,只是它必须有一个更严格的结构定义。 下一次我们会采取什么不同的做法?实施中的一些元素是不令人满意的。在内核中使用流来实现网络接口,可以将协议动态地连接在一起,例如将同一个TTY驱动附加到TCP、URP和IL连接上,但是Plan 9没有使用这种可配置性。(然而,在研究UNIX系统中,它被利用了,流就是为这个系统发明的)。用静态I/O队列代替流将简化代码并使其更快。 尽管Plan 9的主要内核可以在许多机器上移植,但文件服务器是单独实现的。这造成了几个问题:必须编写两次的驱动程序,必须修复两次的错误,以及文件系统代码的可移植性较弱。解决方法很简单:文件服务器内核应该作为常规操作系统的一个变体来维护,没有用户进程,并由特殊的编译内核进程来实现文件服务。对文件系统的另一个改进是改变内部结构。WORM点唱机是硬件中最不可靠的部分,但由于它保存着文件系统的元数据,所以它必须存在,以便为文件提供服务。系统可以被重组,使WORM只是一个备份设备,而文件系统本身驻留在磁性磁盘上。这将不需要改变外部接口。 尽管Plan 9有每个进程的名字空间,但除了直接继承外,它没有机制将一个进程的名字空间的描述给另一个进程。例如,cpu命令在一般情况下不能重现终端的名字空间;它只能重新解释用户的登录配置文件,并对诸如要加载的二进制目录的名字进行替换。这就错过了运行cpu之前的任何本地修改。相反,它应该可以捕获终端的Namespace,并将其描述传送给远程进程。 尽管存在这些问题,”Plan9 “运作良好。它已经成熟地成为支持我们研究的系统,而不是研究本身的主题。实验性的新工作包括开发更快的网络接口,在客户端内核中进行文件缓存,封装和导出Namespace,以及在服务器崩溃后重新建立客户端状态的能力。现在的注意力集中在使用该系统来建立分布式应用。 Plan 9成功的原因之一是我们在日常工作中使用它,而不仅仅是作为一个研究工具。积极的使用迫使我们解决出现的缺点,并调整系统以解决我们的问题。通过这个过程,Plan 9已经成为一个舒适的、富有成效的编程环境,同时也是进一步进行系统研究的工具。
References
[9man] Plan 9 Programmer’s Manual, Volume 1, AT&T Bell Laboratories, Murray Hill, NJ, 1995.
[ANSIC] American National Standard for Information Systems - Programming Language C, American National Standards Institute, Inc., New York, 1990.
[Duff90] Tom Duff, ‘‘Rc - A Shell for Plan 9 and UNIX systems’’, Proc. of the Summer 1990 UKUUG Conf., London, July, 1990, pp. 21-33, reprinted, in a different form, in this volume.
[Fra80] A.G. Fraser, ‘‘Datakit - A Modular Network for Synchronous and Asynchronous Traffic’’, Proc. Int. Conf. on Commun., June 1980, Boston, MA.
[FSSUTF] File System Safe UCS Transformation Format (FSS-UTF), X/Open Preliminary Specification, 1993. ISO designation is ISO/IEC JTC1/SC2/WG2 N 1036, dated 1994-08-01.
[ISO10646] ISO/IEC DIS 10646-1:1993 Information technology - Universal Multiple-Octet Coded Character Set (UCS) — Part 1: Architecture and Basic Multilingual Plane.
[Kill84] T.J. Killian, ‘‘Processes as Files’’, USENIX Summer 1984 Conf. Proc., June 1984, Salt Lake City, UT.
[LABW91] Butler Lampson, Martín Abadi, Michael Burrows, and Edward Wobber, ‘‘Authentication in Distributed Systems: Theory and Practice’’, Proc. 13th ACM Symp. on Op. Sys. Princ., Asilomar, 1991, pp. 165-182.
[MBSS87] S. P. Miller, B. C. Neumann, J. I. Schiller, and J. H. Saltzer, ‘‘Kerberos Authentication and Authorization System’’, Massachusetts Institute of Technology, 1987.
[NBS77] National Bureau of Standards (U.S.), Federal Information Processing Standard 46, National Technical Information Service, Springfield, VA, 1977.
[Nee89] R. Needham, ‘‘Names’’, in Distributed systems, S. Mullender, ed., Addison Wesley, 1989
[NeHe82] R.M. Needham and A.J. Herbert, The Cambridge Distributed Computing System, Addison-Wesley, London, 1982
[Neu92] B. Clifford Neuman, ‘‘The Prospero File System’’, USENIX File Systems Workshop Proc., Ann Arbor, 1992, pp. 13-28.
[OCDNW88] John Ousterhout, Andrew Cherenson, Fred Douglis, Mike Nelson, and Brent Welch, ‘‘The Sprite Network Operating System’’, IEEE Computer, 21(2), 23-38, Feb. 1988.
[Pike87] Rob Pike, ‘‘The Text Editor sam’’, Software - Practice and Experience, Nov 1987, 17(11), pp. 813-845; reprinted in this volume.
[Pike91] Rob Pike, ‘‘8½, the Plan 9 Window System’’, USENIX Summer Conf. Proc., Nashville, June, 1991, pp. 257-265, reprinted in this volume.
[Pike93] Rob Pike and Ken Thompson, ‘‘Hello World or Καλημέρα κόσμε or こんにちは 世界’’, USENIX Winter Conf. Proc., San Diego, 1993, pp. 43-50, reprinted in this volume.
[Pike94] Rob Pike, ‘‘Acme: A User Interface for Programmers’’, USENIX Proc. of the Winter 1994 Conf., San Francisco, CA,
[Pike95] Rob Pike, ‘‘How to Use the Plan 9 C Compiler’’, Plan 9 Programmer’s Manual, Volume 2, AT&T Bell Laboratories, Murray Hill, NJ, 1995.
[POSIX] Information Technology—Portable Operating System Interface (POSIX) Part 1: System Application Program Interface (API) [C Language], IEEE, New York, 1990.
[PPTTW93] Rob Pike, Dave Presotto, Ken Thompson, Howard Trickey, and Phil Winterbottom, ‘‘The Use of Name Spaces in Plan 9’’, Op. Sys. Rev., Vol. 27, No. 2, April 1993, pp. 72-76, reprinted in this volume.
[Presotto] Dave Presotto, ‘‘Multiprocessor Streams for Plan 9’’, UKUUG Summer 1990 Conf. Proc., July 1990, pp. 11-19.
[PrWi93] Dave Presotto and Phil Winterbottom, ‘‘The Organization of Networks in Plan 9’’, USENIX Proc. of the Winter 1993 Conf., San Diego, CA, pp. 43-50, reprinted in this volume.
[PrWi95] Dave Presotto and Phil Winterbottom, ‘‘The IL Protocol’’, Plan 9 Programmer’s Manual, Volume 2, AT&T Bell Laboratories, Murray Hill, NJ, 1995.
[RFC768] J. Postel, RFC768, User Datagram Protocol,DARPA Internet Program Protocol Specification, August 1980.
[RFC793] RFC793, Transmission Control Protocol,DARPA Internet Program Protocol Specification, September 1981.
[Rao91] Herman Chung-Hwa Rao, The Jade File System, (Ph. D. Dissertation), Dept. of Comp. Sci, University of Arizona, TR 91-18.
[Rit84] D.M. Ritchie, ‘‘A Stream Input-Output System’’, *AT&T Bell Laboratories Technical Journal,*63(8), October, 1984.
[Tric95] Howard Trickey, ‘‘APE — The ANSI/POSIX Environment’’, Plan 9 Programmer’s Manual, Volume 2, AT&T Bell Laboratories, Murray Hill, NJ, 1995.
[Unicode] The Unicode Standard, Worldwide Character Encoding, Version 1.0, Volume 1, The Unicode Consortium, Addison Wesley, New York, 1991.
[UNIX85] UNIX Time-Sharing System Programmer’s Manual, Research Version, Eighth Edition, Volume 1. AT&T Bell Laboratories, Murray Hill, NJ, 1985.
[Welc94] Brent Welch, ‘‘A Comparison of Three Distributed File System Architectures: Vnode, Sprite, and Plan 9’’, Computing Systems, 7(2), pp. 175-199, Spring, 1994.
[Wint95] Phil Winterbottom, ‘‘Alef Language Reference Manual’’, Plan 9 Programmer’s Manual, Volume 2, AT&T Bell Laboratories, Murray Hill, NJ, 1995.
到了1980年代中期,计算机的趋势从大型中央化分时共享计算机向网络化的更小型的个人计算机,通常是UNIX“工作站”转变。人们已经厌倦了超载的、官僚主义的分时共享计算机,渴望转向小型、自我维护的系统,即使这意味着计算能力的净损失。随着微型计算机变得更快,甚至可以恢复这种损失,这种计算方式至今仍很受欢迎。
然而,在追求个人工作站的过程中,它们的一些弱点被忽视了。首先,它们所运行的操作系统UNIX本身就是一个旧的分时共享系统,在它之后产生的想法上遇到了困难。图形和网络功能是在UNIX的使用寿命后期加入的,仍然集成得不好且难以管理。更重要的是,早期专注于拥有私人计算机使得机器网络难以像旧的单块分时共享系统一样无缝地服务。分时共享将管理和成本分摊集中起来;个人计算机则分裂、民主化,最终加剧了管理问题。选择一个旧的分时共享操作系统来运行这些个人计算机使得它们很难平稳地联系在一起。
Plan9于1980年代后期开始,旨在尝试两全其美:
使用便宜的现代微型计算机作为计算元件,构建一个集中管理和成本有效的系统。
这个想法是从工作站中构建一个分时共享系统,但是采用了一种新颖的方式。不同的计算机将处理不同的任务:人们办公室中的小型便宜机器将作为终端提供访问大型中央共享资源的功能,例如计算服务器和文件服务器。对于中央计算机来说,即将到来的共享内存多处理器显然是理想的候选。这一哲学很像剑桥分布式系统[NeHe82]。早期的口号是从许多小系统中构建UNIX,而不是从许多小UNIX中构建系统。
UNIX的问题太深,无法修复,但其中的一些想法还不错。最好的想法是它使用文件系统来协调资源的命名和访问,甚至那些传统上不被视为文件的设备资源。对于 Plan9,我们采用了这个想法,通过设计一个称为9P的网络协议,让机器可以访问远程系统上的文件。在此基础上,我们建立了一个命名系统,使人们和他们的计算代理可以在网络中构建自定义的资源视图。这是Plan9首次开始显得不同:Plan9用户构建一个私有的计算环境,并在需要时重新创建它,而不是在私有机器上进行所有计算。很快就清楚了,这种模式比我们预料的要丰富,对进程级命名空间和类似文件系统的资源的想法在整个系统中得到了扩展,包括进程、图形甚至网络本身。
到了1989年,系统已经足够稳定,我们中的一些人开始将其用作我们的独占计算环境。这意味着带着我们在UNIX上使用过的许多服务和应用程序一起转移。我们利用这个机会重新审视了许多问题,不仅仅是内核驻留问题,我们认为UNIX处理得不好的问题。Plan9具有新的编译器、语言、库、窗口系统和许多新应用程序。许多旧工具被舍弃,而带来的工具则被打磨或重新编写。
为什么要这样全面?操作系统、库和应用程序之间的区别对操作系统研究人员很重要,但对用户来说并不重要。重要的是干净的功能。通过构建一个完整的新系统,我们能够解决我们认为应该解决的问题。例如,在内核中没有真正的“tty驱动程序”;这是窗口系统的工作。在现代世界中,多厂商和多架构计算是必不可少的,然而通常的编译器和工具假定程序正在本地运行;我们需要重新思考这些问题。最重要的是,一个系统的测试是它提供的计算环境。生产一个更有效地运行旧UNIX核心软件的方法是空洞的工程;我们更感兴趣的是底层系统架构所提出的新思路是否鼓励了更有效的工作方式。因此,虽然Plan9为运行POSIX命令提供了模拟环境,但它是系统的一个旁支。绝大多数系统软件都是在“本地”的Plan9环境中开发的。
拥有一个全新的系统有益处。首先,我们的实验室有建造实验外围板的历史。为了便于编写设备驱动程序,我们需要一个以源代码形式提供的系统(即使在UNIX诞生的实验室中,也不能保证这一点)。此外,我们想要重新分发我们的工作,这意味着软件必须是本地制作的。例如,我们可以使用一些供应商的C编译器来制作我们的系统,但即使我们克服了交叉编译的问题,我们也将很难重新分发结果。
本文作为该系统的概述。它从最低的构建块讨论了系统的架构,到用户看到的计算环境。它还作为Plan9程序员手册的介绍,伴随着该手册。有关本文中的主题的更多详细信息可以在手册的其他地方找到。
设计
该系统的视图建立在三个原则之上。首先,资源的命名和访问类似于层次文件系统中的文件。其次,有一个名为9P的标准协议,用于访问这些资源。第三,不同服务提供的不相交的层次结构被合并到单个私有层次文件名空间中。Plan 9的非凡特性源于这些原则的一致、积极的应用。
一个大型的Plan 9安装有多台计算机组成网络,每台计算机提供一种特定类型的服务。共享多处理器服务器提供计算能力;其他大型机器提供文件存储。这些机器位于空调机房,通过高性能网络连接。低带宽网络(如以太网或ISDN)将这些服务器连接到办公室和家庭的工作站或个人计算机,Plan 9术语中称为终端。图1显示了这种安排。
现代计算机的风格为每个用户提供专用的工作站或个人电脑。Plan 9的方法则不同。具有屏幕、键盘和鼠标的各种机器都提供对网络资源的访问,因此它们在功能上相当于旧的分时共享系统所连接的终端。然而,当有人使用系统时,该终端会暂时地被该用户个性化。Plan 9不是通过定制硬件来实现个性化,而是通过给公共可见资源命名本地的个人名称来提供自定义的软件视图的能力。这种自定义是通过使用本地的个人名称来组装公共空间的个人视图和全局可访问资源的机制来实现的。由于网络的最重要的资源是文件,因此该视图的模型是面向文件的。
客户端的本地名称空间提供了一种自定义用户对网络的视图的方式。网络中提供的服务都导出文件层次结构。对于用户重要的服务都聚集在一起形成定制的名称空间;对于不立即感兴趣的服务则被忽略。这与“统一全局名称空间”的想法不同。在Plan 9中,服务有已知的名称,文件由这些服务导出,但视图是完全本地的。类比一下,“我的房子”和说话人家的精确地址之间的差别。后者可以被任何人使用,但前者更容易说出来,在不同的人说出来时,也有不同的含义,但这不会导致混淆。类似地,在Plan 9中,名称/dev/cons总是指用户的终端,/bin/date则是运行日期命令的正确版本,但这些名称代表哪些文件取决于诸如执行日期的机器的体系结构等情况。因此,Plan 9具有遵循全球公认的约定的本地名称空间;正是这些约定在存在本地名称的情况下保证了理智的行为。
9P协议被结构化为一组事务,它们向(本地或远程)服务器发送请求并返回结果。9P控制文件系统,而不仅仅是文件:它包括解析文件名和遍历服务器提供的文件系统名称层次结构的过程。另一方面,客户端的名称空间仅由客户端系统持有,而不是在服务器上或与服务器一起持有,这与Sprite [OCDNW88]等系统不同。此外,文件访问是按字节级别进行的,而不是块级别的,这将9P与NFS和RFS等协议区分开来。Welch的一篇论文比较了Sprite、NFS和Plan 9的网络文件系统结构[Welc94]。
这种方法是针对传统文件设计的,但可以扩展到许多其他资源。Plan 9服务导出文件层次结构,包括I/O设备、备份服务、窗口系统、网络接口等。其中一个例子是进程文件系统/proc,它提供了一种清晰的方式来检查和控制正在运行的进程。先驱系统也有类似的想法[Kill84],但Plan 9将文件比喻推到了更远的地步[PPTTW93]。文件系统模型是众所周知的,无论是系统构建者还是一般用户都很熟悉,因此使用类似文件的接口构建服务易于实现、易于理解和易于使用。文件带有保护、命名和访问的约定,既适用于本地访问,也适用于远程访问,因此采用这种方式构建的服务是分布式系统的完美选择。(这与“面向对象”的模型不同,在这种模型中,必须为每个对象类重新考虑这些问题。)接下来的部分将通过实际示例说明这些思想。
The Command-level View
Plan 9旨在在带有窗口系统的屏幕上使用。它没有UNIX意义上的“电传打字机”概念。裸系统的键盘处理是基本的,但一旦窗口系统8½ [Pike91]运行起来,文本就可以通过弹出菜单的“剪切和粘贴”操作进行编辑,可以在窗口之间复制等等。8½允许编辑过去的文本,不仅限于当前输入行上的文本。8½的文本编辑能力足以取代shell中的特殊功能,例如历史记录、分页和滚动以及邮件编辑器。8½窗口不支持光标寻址,除了一个终端仿真器,用于简化连接到传统系统,Plan 9中没有光标寻址软件。
每个窗口都在单独的名称空间中创建。在窗口中对名称空间进行的调整不会影响其他窗口或程序,因此可以安全地尝试对名称空间进行本地修改,例如在调试时从转储文件系统中替换文件。调试完成后,可以删除窗口,实验装置的所有痕迹都会消失。类似的论点也适用于每个窗口拥有的环境变量、注释(类似于UNIX信号)等私有空间。
每个窗口都运行一个应用程序,例如shell,其标准输入和输出连接到窗口的可编辑文本。每个窗口还具有一个私有位图和通过文件(如/dev/mouse、/dev/bitblt和/dev/cons,类似于UNIX的/dev/tty)对键盘、鼠标和其他图形资源进行复用访问。这些文件由8½提供,它被实现为一个文件服务器。与X窗口不同的是,X窗口中一个新的应用程序通常会创建一个新的窗口来运行,而8½图形应用程序通常在它开始的窗口中运行。一个应用程序可以创建一个新窗口,这是可能且高效的,但这不是该系统的风格。再次对比X,在X中,远程应用程序通过网络调用X服务器来启动运行,而远程8½应用程序则像通常在/dev中看到的窗口的鼠标、bitblt和cons文件一样工作;它不知道这些文件是否是本地的。它只是读写这些文件来控制窗口;网络连接已经存在并复用了。
使用的意图方式是在终端上运行交互式应用程序,如窗口系统和文本编辑器,并在远程服务器上运行计算或文件密集型应用程序。不同的窗口可能在不同的机器上通过不同的网络运行程序,但通过使所有窗口的名称空间等效,这是透明的:相同的命令和资源在计算执行的任何地方都可用,名称相同。
Plan 9的命令集与UNIX的相似。这些命令可以分为几类。一些是旧任务的新程序:像ls、cat和who这样的程序具有熟悉的名称和功能,但是是新的、更简单的实现。例如,who是一个shell脚本,而ps只有95行的C代码。有些命令与它们的UNIX祖先基本相同:awk、troff等已经转换为ANSI C并扩展以处理Unicode,但仍是熟悉的工具。有些是为旧领域而编写的全新程序:shell rc、文本编辑器sam、调试器acid等程序用于取代具有类似功能的更为知名的UNIX工具。最后,大约一半的命令是全新的。
兼容性不是该系统的要求。在旧命令或符号看起来足够好时,我们保留了它们。当它们不适用时,我们替换它们。
文件服务器
一个中央文件服务器存储永久文件,并使用9P导出文件层次结构以在网络上呈现。该服务器是一个独立的系统,只能通过网络访问,并旨在出色地完成其工作。它不运行用户进程,只运行编译到启动镜像中的一组固定例程。主层次结构导出的不是一组磁盘或分离的文件系统,而是一个代表许多磁盘上的文件的单个树形结构。该层次结构由许多用户在各种网络上共享。服务器导出的其他文件树包括临时存储和备份服务(如下所述)等专用系统。
该文件服务器有三个存储级别。我们的中央服务器具有约100兆字节的内存缓冲区、27千兆字节的磁盘和一个写一次、读多次(WORM)唱片机中的350千兆字节的大容量存储。磁盘是WORM的缓存,内存是磁盘的缓存。由于它们的速度要快得多,而且看到的流量比它们缓存的级别多一个数量级,所以每个级别都很快。文件系统中可寻址的数据可能比磁盘的大小要大,因为它们只是缓存。我们的主文件服务器具有约40千兆字节的活动存储。
文件服务器最不寻常的特点来自于它使用WORM设备进行稳定存储。每天早上5点钟,文件系统的转储会自动发生。文件系统被冻结,自上次转储以来修改的所有块都排队等待写入WORM。一旦块被排队,服务就会恢复,而倒推文件系统的只读根目录会显示为所有已采取的转储的层次结构,以其日期命名。例如,目录/n/dump/1995/0315是1995年3月15日早上出现的文件系统镜像的根目录。排队块需要几分钟,但在后台运行的将块复制到WORM的进程可能需要数小时。
转储文件系统的使用有两种方式。首先是由用户自己使用,他们可以直接浏览转储文件系统,或者将其中的一部分附加到其命名空间。例如,为了跟踪错误,可以直接使用三个月前的编译器,或者使用昨天的库链接程序。拥有所有文件的每日快照使得查找特定更改的时间或查找特定日期所做的更改变得容易。人们可以自由地对文件进行大量的假设更改,因为他们知道,可以通过一个单独的复制命令来还原它们。没有备份系统,而是因为转储在文件名空间中,因此可以使用标准工具(如cp、ls、grep和diff)解决备份问题。
另一个(非常罕见的)用途是完整的系统备份。在发生灾难时,可以通过清除磁盘缓存并将活动文件系统的根设置为转储根的副本来初始化活动文件系统。尽管易于执行,但这并不应该轻视:除了丢失转储日期后所做的任何更改外,此恢复方法还会导致系统非常缓慢。缓存必须从WORM重新加载,这比磁盘慢得多。文件系统需要几天时间才能重新加载工作集并恢复其完整性能。
转储中的文件的访问权限与转储时相同。普通实用程序在转储中具有普通权限,无需任何特殊安排。但是,转储文件系统是只读的,这意味着转储中的文件无论其权限位如何都不能被写入;实际上,由于目录是只读结构的一部分,因此甚至无法更改权限。
一旦一个文件被写入WORM,它就不能被删除,所以我们的用户永远不会看到 “请清理你的文件 “的信息,也没有df命令。我们认为WORM 是一个无限的资源。唯一的问题是它需要多长时间来填充。我们的WORM已经为一个大约有50个用户的社区服务了5年,每天都吸收转储,总共消耗了 jukebox 中65%的存储空间。在这段时间里,制造商已经改进了技术,将单个磁盘的容量提高了一倍。如果我们升级到新的介质,我们将拥有比原来的空 jukebox 更多的自由空间。技术创造的存储空间比我们使用的速度快。
不寻常的文件服务器 Plan 9的特点是有各种各样的服务器,为不寻常的服务提供类似文件的接口。其中许多是由用户级进程实现的,尽管这种区别对它们的客户来说并不重要;一项服务是由内核、用户进程还是远程服务器提供的,与它的使用方式无关。有几十个这样的服务器;在本节中,我们将介绍三个有代表性的服务器。
也许Plan9中最引人注目的文件服务器是8½,即窗口系统。它在其他地方有详细的讨论[Pike91],但在这里值得简单解释一下。8½提供了两个界面:对坐在终端的用户来说,它提供了一种传统的多窗口交互方式,每个窗口都在运行一个应用程序,都由鼠标和键盘控制。对客户程序来说,视图也是相当传统的:在窗口中运行的程序在/dev中看到一组文件,名称是鼠标、屏幕和控制台。想在窗口中打印文本的程序会写到/dev/cons;要读取鼠标,他们会读取/dev/mouse。在Plan 9风格中,位图图形是通过提供一个文件/dev/bitblt来实现的,客户在这个文件上写编码信息来执行图形操作,如bitblt(RasterOp)。不寻常的是这是如何做到的:8½是一个文件服务器,将/dev中的文件提供给每个窗口中运行的客户端。尽管每个窗口对其客户端来说都是一样的,但每个窗口在/dev中都有一组不同的文件。8½通过提供多组文件,使客户对终端的资源进行多重访问。每个客户都有一个私有的名字空间,其中有一组不同的文件,这些文件在所有其他窗口中的表现是一样的。这种结构有很多优点。其一是8½提供它自己实现所需的相同文件–它复用了自己的接口–因此它可以作为自己的一个客户端,递归地运行。另外,考虑到UNIX中/dev/tty的实现,它需要在内核中使用特殊的代码来重定向开放调用到适当的设备。相反,在8½中,相应的服务会自动出现:8½将/dev/cons作为其基本功能,没有任何额外的事情要做。当一个程序想从键盘上读取数据时,它就会打开/dev/cons,但它是一个私有文件,而不是一个具有特殊属性的共享文件。同样,本地名称空间使之成为可能;关于其中文件的一致性的约定使之成为自然。
8½有一个独特的功能,它的设计使之成为可能。因为它是作为一个文件服务器实现的,所以它有能力推迟响应特定窗口的读取请求。这种行为是通过键盘上的一个保留键来切换的。切换一次就会暂停客户端对该窗口的读取;再次切换就会恢复正常的读取,即吸收任何已经准备好的文本,一次一行。这允许用户在应用程序看到之前在屏幕上编辑多行输入文本,避免了调用一个单独的编辑器来准备文本,如邮件信息。一个相关的特性是直接从定义显示器上的文本的数据结构中回答读数:文本可以被编辑,直到其最后的换行使准备好的文本行可以被客户端读取。即使如此,在该行被读取之前,客户端将读取的文本也可以被改变。例如,在输入了
% make
rm *
到shell,用户可以在任何时候在最后的换行上退格,直到make完成,从而延缓rm命令的执行,甚至可以在rm之前用鼠标指一下,然后输入另一条要先执行的命令。
在Plan 9中没有ftp命令,相反,一个名为ftpfs的用户级文件服务器会拨号到FTP站点,代表用户登录,并使用FTP协议检查远程目录中的文件。对本地用户来说,它提供了一个文件层次,连接到本地名称空间中的/n/ftp,反映了FTP站点的内容。换句话说,它把FTP协议翻译成9P,以提供Plan 9对FTP站点的访问。实现起来很棘手;ftpfs必须做一些复杂的缓存以提高效率,并使用启发式方法来解码远程目录信息。但结果是值得的:所有的本地文件管理工具,如cp、grep、diff,当然还有ls,都可以用于FTP提供的文件,就像它们是本地文件一样。其他系统如Jade和Prospero也利用了同样的机会[Rao81, Neu92],但由于本地名称空间和实现9P的简单性,这种方法更自然地适合Plan 9而不是其他环境。
一个服务器,exportfs,是一个用户进程,它从自己的名字空间中抽取一部分,通过将9P请求转化为对Plan 9内核的系统调用,使其对其他进程可用。它导出的文件层次可能包含来自多个服务器的文件。Exportfs通常作为一个远程服务器运行,由一个本地程序启动,要么是import,要么是cpu。Import对远程机器进行网络调用,在那里启动exportfs,并将其9P连接附加到本地名称空间。例如、
import helix /net
使Helix的网络接口在本地/net目录中可见。Helix是一个中央服务器,有许多网络接口,所以这允许一台有网络的机器访问Helix的任何网络。经过这样的导入,本地机器可以在连接到Helix的任何网络上进行调用。另一个例子是
import helix /proc
这使得Helix的进程在本地/proc中可见,允许本地调试器检查远程进程。
cpu命令将本地终端连接到一个远程CPU服务器。它的工作方向与import相反:在调用服务器后,它启动一个本地的exportfs,并将其挂载在服务器上一个进程的名字空间中,通常是一个新创建的shell。然后,它重新安排名称空间,使本地设备文件(例如由终端的窗口系统提供的文件)在服务器的/dev目录中可见。因此,运行cpu命令的效果是在一个快速的机器上启动一个shell,一个与文件服务器更紧密联系的shell,其名称空间与本地的类似。所有本地设备文件都是远程可见的,因此远程应用程序可以完全访问本地服务,如位图图形、/dev/cons等等。这与rlogin不同,rlogin在远程系统上不做任何重现本地名称空间的事情,也与文件共享不同,例如NFS,它可以实现一些名称空间的等同性,但不能实现对本地硬件设备、远程文件和远程CPU资源的访问。cpu命令是一个独特的透明机制。例如,在运行cpu命令的窗口中启动一个窗口系统是合理的;在那里创建的所有窗口会自动启动CPU服务器上的进程。
Configurability and administration
Plan 9中组件的统一互连使得Plan 9的安装有可能以许多不同的方式进行配置。一台笔记本电脑可以作为一个独立的Plan 9系统;在另一个极端,我们的设置有中央多处理器CPU服务器和文件服务器以及几十个终端,从小型PC到高端图形工作站。正是这样的大型装置最能代表Plan 9的运作方式。
系统软件是可移植的,同样的操作系统在所有硬件上运行。除了性能之外,比如说SGI工作站上的系统外观与笔记本电脑上的系统外观是一样的。由于计算和文件服务是集中的,而终端没有永久的文件存储,所有的终端在功能上是相同的。这样一来,Plan 9就具有老式分时系统的一个良好特性,即用户可以坐在任何一台机器前,看到同样的系统。在现代工作站社区中,机器往往被人们所拥有,他们通过在本地磁盘上存储私人信息来定制机器。我们拒绝这种使用方式,尽管系统本身也可以这样使用。在我们的小组中,我们有一个实验室,里面有许多可以公开使用的机器–一个终端室–用户可以坐在其中任何一台机器上工作。
中央文件服务器不仅集中了文件,而且还集中了文件的管理和维护。事实上,一台服务器是主服务器,保存着所有的系统文件;其他服务器提供额外的存储空间,或者可用于调试和其他特殊用途,但系统软件驻留在一台机器上。这意味着每个程序都有一份适用于每个架构的二进制文件,所以安装更新和错误修复是很简单的。还有一个单一的用户数据库;不需要同步不同的/etc/passwd文件。另一方面,依赖一个中央服务器确实限制了安装的规模。
集中式文件服务的另一个例子是Plan 9管理网络信息的方式。在中央服务器上有一个目录,/lib/ndb,它包含了管理本地以太网和其他网络的所有必要信息。所有的机器都使用同一个数据库与网络对话;不需要管理一个分布式的命名系统,也不需要保持并行文件的更新。要在本地以太网上安装一台新机器,选择一个名称和IP地址,并将其添加到/lib/ndb中的一个文件中;安装中的所有机器将能够立即与它对话。要开始运行,将机器插入网络,打开它,并使用BOOTP和TFTP来加载内核。其他都是自动的。
最后,自动转储文件系统将所有用户从维护其系统的需要中解放出来,同时在没有磁带、特殊命令或支持人员参与的情况下提供对备份文件的轻松访问。这种服务所带来的生活方式的改善是难以言表的。
Plan 9可以在各种硬件上运行,而不限制如何配置安装。在我们的实验室,我们选择使用中央服务器,因为它们可以摊薄成本和管理。这是一个很好的决定的标志是,我们的廉价终端在大约五年内仍然是舒适的工作场所,比必须提供完整计算环境的工作站要长很多。然而,我们确实对中央机器进行了升级,因此,即使是老式的Plan 9终端,其计算能力也会随着时间的推移而提高。避免定期升级终端所节省的资金转而用在最新、最快的多处理器服务器上。我们估计这样做的成本大约是联网工作站的一半,但却提供了对更强大机器的普遍访问。
C 语言编程 Plan 9的实用程序是用几种语言编写的。一些是shell的脚本,rc[Duff90];少数是用一种新的类似C的并发语言Alef[Wint95]编写的,下面会介绍。不过,绝大多数是用ANSI C[ANSIC]的方言编写的。在这些程序中,大多数是全新的程序,但也有一些是来自我们研究的UNIX系统[UNIX85]的前ANSI C代码。这些程序已被更新为ANSI C语言,并为可移植性和清洁性进行了重写。
Plan 9 C方言有一些小的扩展,在其他地方有描述[Pike95],还有一些主要限制。最重要的限制是,编译器要求所有的函数定义都有ANSI原型,所有的函数调用都出现在函数的原型声明的范围内。作为一个风格上的规则,原型声明被放在一个头文件中,由所有调用该函数的文件包含。每个系统库都有一个相关的头文件,对该库中的所有函数进行声明。例如,标准的Plan 9库被称为libc,所以所有的C源代码文件都包括
另一个限制是,C语言编译器只接受ANSI要求的预处理指令的一个子集。主要的遗漏是#if,因为我们认为它根本没有必要,而且经常被滥用。而且,它的效果可以通过其他方式更好地实现。例如,用于在编译时切换功能的#if可以写成一个普通的if语句,依靠编译时的常量折叠和死代码消除来丢弃目标代码。 在Plan 9中,即使有#ifdef,也很少使用条件编译。系统中唯一依赖架构的#ifdefs是在图形库的低级例程中。相反,我们避免这种依赖性,或者在必要时,将它们隔离在单独的源文件或库中。除了使代码难以阅读之外,#ifdefs还使我们无法知道哪些源代码被编译到二进制文件中,或者受其保护的源代码是否能正常编译或工作。它们使软件的维护变得更加困难。 标准的Plan 9库与ANSI C和POSIX [POSIX]的大部分内容重叠,但在适合Plan 9的目标或实现的情况下会有分歧。当一个函数的语义改变时,我们也会改变其名称。例如,代替UNIX的creat,Plan 9有一个create函数,它需要三个参数,原来的两个加上第三个,像open的第二个参数一样,定义返回的文件描述符是为读、写还是为两者打开。这种设计是由9P实现创建的方式所迫使的,但它也简化了创建初始化临时文件的常见用法。
另一个与ANSI C不同的地方是,Plan 9使用了一个叫做Unicode[ISO10646, Unicode]的16位字符集。虽然我们没有完全实现国际化,但Plan 9在其所有软件中统一处理所有主要语言的表示。为了简化程序之间的文本交换,字符被我们设计的编码打包成一个字节流,称为UTF-8,现在已经被接受为一个标准[FSSUTF]。它有几个吸引人的特性,包括字节顺序的独立性、与ASCII的向后兼容以及易于实现。
要使现有的软件适应一个大的字符集,并采用表示字符的字节数不固定的编码,有很多问题。ANSI C解决了其中的一些问题,但没有解决所有问题。它没有选择一个字符集编码,也没有定义所有必要的I/O库例程。此外,它所定义的函数也有工程问题。由于该标准留下了太多的问题没有解决,我们决定建立我们自己的接口。另一篇论文有详细介绍[Pike93]。
有一小类Plan 9程序不遵循本节讨论的惯例。这些程序是由UNIX社区导入并维护的;tex就是一个有代表性的例子。为了避免每次发布新版本时都要重新转换这些程序,我们建立了一个移植环境,称为ANSI C/POSIX环境,或APE [Tric95]。APE 由独立的包含文件、库和命令组成, 尽可能地符合严格的 ANSI C 和基本的 POSIX 规范。为了移植基于网络的软件,如X Windows,有必要在这些规范中加入一些扩展,如BSD的网络功能。
可移植性和编译 Plan 9可以在不同的处理器架构上移植。在一个单一的计算环节中,使用几种架构是很常见的:也许窗口系统运行在英特尔处理器上,与基于MIPS的CPU服务器相连,文件驻留在SPARC系统上。为了使这种异质性透明化,必须有关于程序间数据交换的约定;为了使软件维护简单明了,必须有关于跨体系结构编译的约定。 为了避免字节顺序问题,在实际情况下,程序之间的数据是以文本形式进行通信的。但有时,数据量大到必须使用二进制格式;这种数据以字节流的形式进行通信,并对多字节值进行预先定义的编码。在极少数情况下,如果一个格式复杂到足以由一个数据结构来定义,那么这个结构永远不会作为一个单元来交流;相反,它被分解成各个字段,被编码为一个有序的字节流,然后由接收者重新组装起来。这些约定影响了从内核或应用程序状态信息到由编译器生成的对象文件中介的数据。 包括内核在内的程序,经常通过文件系统接口来呈现它们的数据,这种访问机制本身就是可移植的。例如,系统时钟由文件/dev/time中的一个十进制数字表示;时间库函数(没有时间系统调用)读取该文件并将其转换为二进制。同样地,内核并没有将应用进程的状态编码为私有内存中的一系列标志和比特,而是在与每个进程相关的/proc文件系统中的名为status的文件中呈现一个文本字符串。Plan 9的ps命令是微不足道的:它在经过一些小的重新格式化后,打印出所需的状态文件的内容;此外,在
import helix /proc
本地ps命令报告Helix的进程状态。
每个支持的架构都有自己的编译器和加载器。C和Alef编译器产生的中间文件是可移植编码的;其内容对目标架构是唯一的,但文件的格式与编译处理器类型无关。当一个给定架构的编译器在另一种类型的处理器上编译,然后用来编译一个程序时,在新架构上产生的中间文件与在本地处理器上产生的中间文件是相同的。从编译器的角度来看,每次编译都是一次交叉编译。
尽管每个架构的加载器只接受由该架构的编译器产生的中间文件,但这些文件可以由在任何类型的处理器上执行的编译器产生。例如,有可能在486上运行MIPS编译器,然后在SPARC上使用MIPS装载器来生成MIPS可执行文件。
由于Plan 9可以在各种架构上运行,即使是在单一的安装中,区分编译器和中间名称可以简化从单一源码树进行的多架构开发。每个架构的编译器和加载器都有唯一的名字;没有cc命令。这些名字是通过将与目标架构相关的代码字母与编译器或加载器的名字连接起来而得到的。例如,字母’8’是Intel x86处理器的代码字母;C编译器被命名为8c,Alef编译器被命名为8al,加载器被称为8l。同样,编译器的中间文件的后缀是.8,而不是.o。
Plan 9的编译程序mk是make的一个亲戚,它从名为\(cputype和\)objtype的环境变量中读取当前和目标架构的名称。默认情况下,当前的处理器是目标,但在调用mk之前,将$objtype设置为另一个架构的名称,会导致交叉构建:
% objtype=sparc mk
构建SPARC架构的程序,而不考虑执行的机器。$objtype的值选择了一个与架构相关的变量定义文件,该文件配置了编译器和加载器的使用。虽然思想简单,但这种技术在实践中效果很好:Plan9中的所有应用程序都是从一个单一的源码树中构建的,可以并行地构建各种架构而不发生冲突。
Parallel programming
Plan 9对并行编程的支持有两个方面。首先,内核提供了一个简单的进程模型和一些精心设计的用于同步和共享的系统调用。第二,一种叫做Alef的新的并行编程语言支持并发编程。尽管用C语言编写并行程序是可能的,但Alef是首选的并行语言。
在新的操作系统中,有一种趋势是实现两类进程:正常的UNIX式进程和轻量级的内核线程。相反,Plan 9提供了一个单一类别的进程,但允许精细控制进程的资源共享,如内存和文件描述符。在Plan 9中,单一类别的进程是一种可行的方法,因为内核有一个高效的系统调用接口和廉价的进程创建和调度。
并行程序有三个基本要求:管理进程间共享的资源,与调度器的接口,以及使用自旋锁的细粒度的进程同步。在Plan 9上,新的进程是用rfork系统调用创建的。Rfork需要一个参数,即一个bit vector,指定父进程的哪些资源应该在子进程中被共享、复制或重新创建。rfork控制的资源包括名称空间、环境、文件描述符表、内存段和notes(Plan 9对UNIX信号的模拟)。其中一个位控制rfork调用是否会创建一个新的进程;如果这个位是关闭的,那么对资源的修改就发生在调用的进程中。例如,一个进程调用rfork(RFNAMEG)来断开其名称空间与父进程的连接。Alef使用了一个细粒度的 fork,其中所有的资源,包括内存,都在父代和子代之间共享,类似于在许多系统中创建一个内核线程。
rfork是正确的模型的一个迹象是它被使用的方式的多样性。除了在库例程fork中的典型使用外,很难找到两个对rfork的调用具有相同的位集;程序用它来创建许多不同形式的共享和资源分配。一个只有两类进程的系统–常规进程和线程–无法处理这种多样性。
有两种方法来共享内存。首先,rfork的一个标志导致父代的所有内存段与子代共享(除了堆栈,无论如何,堆栈都是写时分叉的)。另外,可以使用segattach系统调用附加一个新的内存段;这样的内存段将总是在父代和子代之间共享。
rendezvous系统调用为进程提供了一种同步的方式。Alef用它来实现通信通道、排队锁、多个读写器锁、以及睡眠和唤醒机制。Rendezvous需要两个参数,一个标签和一个值。当一个进程用一个标签调用rendezvous时,它就会睡眠,直到另一个进程提出一个匹配的标签。当一对标签匹配时,两个进程之间会交换值,两个交会调用都会返回。这个基元足以实现全套的同步例程。
最后,自旋锁是由用户级别的架构相关库提供的。大多数处理器提供原子测试和设置指令,可以用来实现锁。一个明显的例外是MIPS R3000,所以SGI Power系列多核处理器在总线上有特殊的锁硬件。用户进程通过使用segattach系统调用将硬件锁的页面映射到他们的地址空间来获得对锁硬件的访问。
在系统调用中的Plan 9进程将被阻塞,不管它的 “重量 “如何。这意味着,当一个程序希望从一个慢速设备上读取数据而不阻塞整个计算时,它必须分叉一个进程来为它做读取工作。解决办法是启动一个卫星进程来进行I/O,并通过共享内存或管道将答案传递给主程序。这听起来很麻烦,但在实践中却很容易和有效;事实上,大多数交互式Plan 9应用程序,甚至是用C语言编写的相对普通的应用程序,如文本编辑器Sam [Pike87],都是作为多进程程序运行的。
Plan9中对并行编程的内核支持是几百行可移植的代码;少数简单的基元使问题在用户层面得到干净的处理。尽管这些基元在C语言中运行良好,但在Alef中它们的表现力特别强。从属I/O进程的创建和管理可以用Alef的几行代码来写,为任意进程之间的数据流复用提供了一致的手段。此外,在语言中而不是在内核中实现它,可以确保所有设备之间的语义一致,并提供一个更通用的复用原语。与UNIX的select系统调用相比:select只适用于一组有限的设备,在内核中规定了一种多路复用的方式,不能跨网络扩展,很难实现,也很难使用。
在Plan9中,并行编程很重要的另一个原因是,多线程的用户级文件服务器是实现服务的首选方式。这种服务器的例子包括编程环境Acme[Pike94]、名称空间导出工具exportfs[PPTTW93]、HTTP守护程序以及网络名称服务器cs和dns[PrWi93]。像Acme这样复杂的应用证明,仔细的操作系统支持可以减少编写多线程应用的难度,而不需要将线程和同步原语移入内核。
Implementation of Name Spaces
用户进程使用三个系统调用构建名称空间:mount、bind和unmount。mount系统调用将一个由文件服务器提供的树附加到当前名称空间。在调用mount之前,客户端必须(通过外部手段)以文件描述符的形式获得与服务器的连接,该文件描述符可以被写入和读取以传输9P消息。该文件描述符代表一个管道或网络连接。 mount调用将一个新的层次结构附加到现有的名字空间上。另一方面,bind系统调用在名字空间的另一点上复制了现有名字空间的某一块。unmount系统调用允许组件被移除。 使用bind或mount,多个目录可以在名称空间的一个点上堆叠。用Plan 9的术语来说,这是一个联合目录,其行为类似于组成目录的串联。绑定和挂载的标志参数指定了联盟中新目录的位置,允许在联盟的前部或后部添加新的元素,或者完全替换它。当在一个联合目录中进行文件查找时,联合目录的每一个组成部分都被依次搜索,并采取第一个匹配;同样,当联合目录被读取时,每个组成目录的内容都被依次读取。联合目录是Plan 9名称空间中最广泛使用的组织特征之一。例如,目录/bin是由/\(cputype/bin(程序二进制文件)、/rc/bin(shell脚本)以及可能由用户提供的更多目录组成的。这种结构使得shell中的\)PATH变量成为不必要的。
One question raised by union directories is which element of the union receives a newly created file. After several designs, we decided on the following. By default, directories in unions do not accept new files, although the create system call applied to an existing file succeeds normally. When a directory is added to the union, a flag to bind or mount enables create permission (a property of the name space) in that directory. When a file is being created with a new name in a union, it is created in the first directory of the union with create permission; if that creation fails, the entire create fails. This scheme enables the common use of placing a private directory anywhere in a union of public ones, while allowing creation only in the private directory.
联盟目录提出的一个问题是联盟中的哪个元素接收新创建的文件(One question raised by union directories is which element of the union receives a newly created file.)。经过几次设计,我们决定采用以下方法。默认情况下,联盟中的目录不接受新文件,尽管应用于现有文件的创建系统调用会正常成功。当一个目录被添加到联盟中时,一个绑定或挂载的标志在该目录中启用了创建权限(名字空间的一个属性)。当一个文件在联合体中以一个新的名字被创建时,它被创建在联合体的第一个目录中,并具有创建权限;如果创建失败,整个创建就会失败。这个方案使人们能够在公共目录的联盟中的任何地方放置一个私有目录,同时只允许在私有目录中创建。
按照惯例,内核设备文件系统被绑定到/dev目录中,但是为了引导名称空间的建立过程,有必要有一个符号,允许直接访问没有现有名称空间的设备。设备驱动所服务的树的根目录可以使用语法#c来访问,其中c是一个识别设备类型的唯一字符(通常是一个字母)。简单的设备驱动程序服务于一个包含几个文件的单层目录。例如,每个串行端口由一个数据和一个控制文件表示:
% bind -a ’#t’ /dev
% cd /dev
% ls -l eia*
- rw-rw-rw- t 0 bootes bootes 0 Feb 24 21:14 eia1
- rw-rw-rw- t 0 bootes bootes 0 Feb 24 21:14 eia1ctl
- rw-rw-rw- t 0 bootes bootes 0 Feb 24 21:14 eia2
- rw-rw-rw- t 0 bootes bootes 0 Feb 24 21:14 eia2ctl
bind程序是对bind系统调用的封装;它的-a标志将新目录定位在联盟的末端。数据文件eia1和eia2可以被读写以通过串行线进行通信。与其在这些文件上使用特殊操作来控制设备,不如将命令写入文件eia1ctl和eia2ctl来控制相应的设备;例如,将文本字符串b1200写入/dev/eia1ctl将该线的速度设置为1200波特。与UNIX的ioctl系统调用相比:在Plan 9中,设备由文本信息控制,不存在字节顺序问题,有明确的读写语义。使用shell脚本来配置或调试设备是很常见的。
正是9P协议的普遍使用,将Plan 9的组件连接在一起,形成一个分布式系统。Plan 9没有为每个服务(如rlogin、FTP、TFTP和X窗口)发明一个独特的协议,而是以对文件对象的操作来实现服务,然后使用一个单一的、有据可查的协议在计算机之间交换信息。与NFS不同,9P将文件视为字节序列而不是块。与NFS不同,9P也是有状态的:客户执行远程过程调用以建立指向远程文件服务器中对象的指针。这些指针被称为文件标识符或FID。所有对文件的操作都提供一个fid来识别远程文件系统中的一个对象。
9P协议定义了17条信息,提供了验证用户、在文件系统层次结构中导航fids、复制fids、执行I/O、改变文件属性以及创建和删除文件的方法。它的完整规范在《程序员手册》[9man]的第5节中。下面是访问由服务器提供的名称层次结构的程序。一个文件服务器连接是通过管道或网络连接建立的。一个初始会话消息在客户和服务器之间进行双边认证。然后,一个附加消息将客户端建议的fid连接到服务器文件树的根。附加信息包括执行附加的用户的身份;此后,所有从根fid衍生出来的fid都有与该用户相关的权限。多个用户可以共享连接,但每个人都必须执行附加信息以建立他或她的身份。
walk 消息在文件系统层次结构中的一个层次上移动一个fid。clone消息接收一个已建立的fid并产生一个副本,该副本指向与原文件相同的文件。它的目的是在不丢失目录上的fid的情况下,能够走到目录中的一个文件。open消息将一个fid锁定在层次结构中的一个特定文件上,检查访问权限,并为fid的I/O做准备。读和写消息允许在文件的任意偏移处进行I/O;传输的最大尺寸由协议定义。clunk消息表示客户端对一个fid没有进一步的使用。remove消息的行为与clunk类似,但会导致与该fid相关的文件被删除,并且服务器上的任何相关资源都会被取消分配。
9P有两种形式:在管道或网络连接上发送的RPC消息和内核内的过程性接口。由于内核设备驱动程序是可以直接寻址的,因此不需要通过消息来与它们通信;相反,每个9P事务都是通过直接的过程调用来实现的。对于每个fid,内核在一个叫做通道的数据结构中保持一个本地表示,所以所有由内核执行的对文件的操作都涉及到与该fid相连的通道。最简单的例子是一个用户进程的文件描述符,它是一个通道数组的索引。内核中的一个表提供了一个入口点列表,与每个设备的9P信息一一对应。一个系统调用,如来自用户的读取,通过该表转化为一个或多个过程调用,以通道中存储的类型字符为索引:procread,eiaread,等等。每个调用至少需要一个通道作为参数。一个特殊的内核驱动,称为mount驱动,将过程调用转换为消息,也就是说,它将本地过程调用转换为远程调用。实际上,这个特殊的驱动成为远程文件服务器所提供的文件的本地代理。本地调用中的通道指针被转换为传输消息中的相关fid。
挂载驱动是系统采用的唯一RPC机制。所提供的文件的语义,而不是对它们进行的操作,创造了一个特殊的服务,如cpu命令。挂载驱动对与文件服务器共享通信通道的客户之间的协议信息进行解复用。对于每个传出的RPC消息,挂载驱动分配了一个缓冲区,用一个小的唯一的整数来标记,称为标签。对RPC的回复也被贴上了同样的标签,挂载驱动使用这个标签来匹配回复和请求。
内核对名字空间的表示被称为mount table,它存储了一个通道之间的绑定列表。挂载表的每个条目包含一对通道:一个从通道和一个到通道。每当行走成功地将一个通道移动到名字空间中的一个新位置时,挂载表就会被查阅,看是否有一个 “从 “通道与新的名字相匹配;如果有的话,”到 “通道就会被克隆并替换成原来的。联合目录是通过将 “to “通道转换为一个通道列表来实现的:一个成功走到联合目录的通道会返回一个 “to “通道,该通道形成一个通道列表的头部,每个通道代表联合目录的一个组成部分。如果在联合目录的第一个目录中找不到文件,就会跟踪该列表,克隆下一个组件,并在该目录上尝试walk。
Plan 9中的每个文件都由一组整数唯一标识:通道的类型(用作函数调用表的索引),服务器或设备编号,以区别于其他相同类型的服务器(由驱动程序在本地决定),以及由两个32位数字组成的qid,称为路径和版本。路径是设备驱动程序或文件服务器在创建文件时分配的唯一文件号。版本号在文件被修改时被更新;正如下一节所描述的,它可以被用来保持客户端和服务器之间的缓存一致性。
类型和设备号类似于UNIX的主要和次要设备号;qid类似于i-number。设备和类型将通道连接到设备驱动,而qid则是识别该设备中的文件。如果从走动中恢复的文件与挂载表中的一个条目具有相同的类型、设备和qid路径,它们就是同一个文件,并从挂载表中进行相应的替换。这就是名称空间的实现方式。
文件缓存 9P协议没有明确支持在客户端缓存文件。中央文件服务器的大内存作为其所有客户端的共享缓存,这减少了网络中所有机器所需的内存总量。尽管如此,还是有充分的理由在客户端缓存文件,例如与文件服务器的连接速度很慢。 每当文件被修改时,qid的版本字段就会被改变,这使得做一些弱一致性的缓存形式成为可能。最重要的是客户端对可执行文件的文本和数据段进行缓存。当一个进程执行一个程序时,该文件被重新打开,qid的版本与缓存中的版本进行比较;如果它们匹配,则使用本地副本。同样的方法也可以用来建立一个本地缓存文件服务器。这个用户级的服务器插在与远程服务器的9P连接上,监控流量,将数据复制到本地磁盘。当它看到对已知数据的读取时,它直接回答,而写入的数据则立即传递–缓存是写过的,以保持中央副本的最新状态。这对终端上的进程是透明的,不需要对9P做任何改动;它在通过串行线路连接的家用机器上运行良好。类似的方法也可以应用于在未使用的本地内存中建立一个普通的客户端缓存,但在Plan 9中没有这样做。
网络和通信设备 网络接口是常驻内核的文件系统,类似于前面描述的EIA设备。呼叫的建立和关闭是通过向与设备相关的控制文件写入文本字符串实现的;信息的发送和接收是通过读写数据文件实现的。设备的结构和语义对所有的网络都是通用的,因此,除了文件名的替换之外,使用以太网上的TCP进行调用的程序与使用Datakit上的URP进行调用的程序相同[Fra80]。
This example illustrates the structure of the TCP device:
% ls -lp /net/tcp
d-r-xr-xr-x I 0 bootes bootes 0 Feb 23 20:20 0
d-r-xr-xr-x I 0 bootes bootes 0 Feb 23 20:20 1
- rw-rw-rw- I 0 bootes bootes 0 Feb 23 20:20 clone
% ls -lp /net/tcp/0
- rw-rw—- I 0 rob bootes 0 Feb 23 20:20 ctl
- rw-rw—- I 0 rob bootes 0 Feb 23 20:20 data
- rw-rw—- I 0 rob bootes 0 Feb 23 20:20 listen
- r–r–r– I 0 bootes bootes 0 Feb 23 20:20 local
- r–r–r– I 0 bootes bootes 0 Feb 23 20:20 remote
- r–r–r– I 0 bootes bootes 0 Feb 23 20:20 status
%
最上面的目录,/net/tcp,包含一个克隆文件和每个连接的目录,编号为0到n。每个连接目录对应一个TCP/IP连接。打开克隆文件保留一个未使用的连接并返回其控制文件。读取控制文件返回文本连接号,因此用户进程可以构建新分配的连接目录的全名。本地、远程和状态文件是诊断性的;例如,远程包含了远程端的地址(对于TCP,IP地址和端口号)。
调用是通过编写一个以网络特定地址为参数的连接消息来启动的;例如,要打开一个Telnet会话(端口23)到IP地址为135.104.9.52的远程机器,其字符串为:
connect 135.104.9.52!23
对控制文件的写入会被阻止,直到连接建立起来;如果目的地无法到达,写入会返回一个错误。一旦连接建立,telnet应用程序就会读写数据文件,与远程Telnet守护进程对话。在另一端,Telnet守护进程将开始写下
announce 23
到它的控制文件,以表明它愿意接受对这个端口的呼叫。这样的守护程序在Plan9中被称为监听器。 网络设备的统一结构不能隐藏不同网络的寻址和通信的所有细节。例如,Datakit使用的是文本的、分层的地址,与IP的32位地址不同,所以给一个控制文件的应用程序仍然必须知道它代表什么网络。与其让每个应用程序知道每个网络的地址,Plan 9把这些细节隐藏在一个叫做cs的连接服务器中。Cs是一个安装在已知位置的文件系统。它提供了一个单一的控制文件,应用程序用它来发现如何连接到一个主机。应用程序写下它想建立的连接的符号地址和服务名称,并读回要打开的克隆文件的名称和要提交给它的地址。如果机器之间有多个网络,cs会提出一个可能的网络和地址列表,依次进行尝试;它使用启发式方法来决定顺序。例如,它首先显示最高带宽的选择。 一个叫拨号的库函数与cs对话以建立连接。一个使用拨号的应用程序不需要改变,甚至不需要重新编译,就能适应新的网络;cs的接口隐藏了细节。 Plan9中网络的统一结构使得导入命令成为构建网关的全部需要。
Kernel structure for networks
用于建立Plan 9通信通道的内核管道被称为流[Rit84][Presotto]。一个流是一个连接物理或伪设备和用户进程的双向通道。用户进程在流的一端插入和删除数据;代表设备行事的内核进程在另一端操作。一个流包括一个处理模块的线性列表。每个模块都有一个上游(面向进程)和下游(面向设备)的put例程。调用流两端的模块的put例程,将数据插入流中。每个模块都会调用后面的模块来向上或向下发送数据。像UNIX流[Rit84]一样,Plan 9的流可以动态配置。
The IL Protocol
9P协议必须在一个可靠的传输协议之上运行,并有限定的消息。9P没有从传输错误中恢复的机制,而且系统假定从通信通道中的每一次读取都会返回一个单一的9P消息;它不会解析数据流以发现消息的边界。管道和一些网络协议已经具有这些特性,但标准的IP协议却没有。TCP不给消息划界,而UDP[RFC768]不提供可靠的按顺序交付。 我们设计了一个新的协议,叫做IL(Internet Link),用于在IP上传输9P消息。它是一个基于连接的协议,在机器之间提供可靠的顺序信息传输。由于一个进程只能有一个未完成的9P请求,所以在IL中不需要流量控制。与TCP一样,IL也有自适应超时功能:它将确认和重传时间与网络速度相匹配。这使得该协议在互联网和本地以太网上都能表现良好。另外,IL不做盲目重传,以避免增加繁忙网络的拥堵。全部细节见另一篇论文[PrWi95]。 在Plan9中,IL的实现比TCP要小,要快。IL是我们的主要互联网传输协议。
Overview of authentication
认证确定了访问资源的用户的身份。请求资源的用户被称为客户端,授予资源访问权的用户被称为服务器。这通常是在9P附加消息的支持下完成的。一个用户可能在一个认证交换中是客户,在另一个认证交换中是服务器。服务器总是代表一些用户行事,要么是正常的客户,要么是一些管理实体,所以认证被定义为用户之间的认证,而不是机器。 每个Plan 9用户都有一个相关的DES[NBS77]认证密钥;用户的身份是通过加密和解密被称为挑战的特殊信息的能力来验证的。由于知道一个用户的密钥就可以访问该用户的资源,所以Plan 9认证协议从不传输包含明文密钥的信息。 认证是双边的:在认证交换结束时,每一方都确信对方的身份。每台机器都以内存中的DES密钥开始交换。在CPU和文件服务器的情况下,服务器的密钥、用户名和域名从永久存储中读取,通常是非易失性RAM。在终端的情况下,密钥是由用户在启动时输入的密码得出的。一台特殊的机器,即认证服务器,为其管理域中的所有用户维护一个密钥数据库,并参与认证协议。 认证协议如下:在交换挑战后,一方与认证服务器联系,创建用每一方的秘密密钥加密的权限授予票,并包含一个新的对话密钥。每一方都会解密自己的票据,并使用对话密钥来加密另一方的挑战。 这种结构有点像Kerberos [MBSS87],但避免了其对同步时钟的依赖。同样与Kerberos不同的是,Plan 9认证支持 “speech for “关系[LABW91],使一个用户拥有另一个用户的权限;这就是CPU服务器代表其客户运行进程的方式。 Plan 9的认证结构建立了安全的服务,而不是依赖于防火墙。防火墙需要为每一个穿墙的服务编写特殊的代码,而Plan 9的方法允许在一个地方对所有的服务进行认证-9P。例如,cpu命令在互联网上安全地工作。
Authenticating external connections
常规的Plan 9认证协议不适合基于文本的服务,如Telnet或FTP。在这种情况下,Plan 9用户用称为认证器的手持式DES计算器进行认证。认证器为用户持有一个密钥,与用户的正常认证密钥不同。用户使用一个4位数的PIN码 “登录 “到认证器上。一个正确的PIN码可以使认证器与服务器进行挑战/回应交换。由于正确的挑战/回应交换仅一次有效,而且密钥从未在网络上发送,因此该程序不容易受到重放攻击,但与Telnet和FTP等协议兼容。
Special users
Plan 9没有超级用户。每台服务器负责维护自己的安全,通常只允许从控制台访问,控制台有密码保护。例如,文件服务器有一个独特的管理用户,叫做adm,具有特殊的权限,只适用于在服务器的物理控制台输入的命令。这些权限涉及服务器的日常维护,如添加新用户和配置磁盘和网络。这些权限不包括修改、检查或改变任何文件权限的能力。如果一个文件是由一个用户阅读保护的,只有该用户可以授予其他人访问权。
CPU服务器有一个相当的用户名,允许对该服务器上的资源进行管理访问,如用户进程的控制文件。这种权限是必要的,例如,为了杀死流氓进程,但不超出该服务器的范围。另一方面,通过受保护的非易失性RAM中的密钥,管理用户的身份被证明给认证服务器。这使得CPU服务器可以对远程用户进行认证,包括对服务器本身的访问以及CPU服务器代表他们进行代理的时候。 最后,一个叫做none的特殊用户没有密码,并且总是被允许连接;任何人都可以声称自己是none。None的权限受到限制;例如,它不允许检查转储文件,只能读取世界可读的文件。 无的背后的想法类似于FTP服务中的匿名用户。在Plan 9上,访客FTP服务器被进一步限制在一个特殊的受限名称空间内。它切断了访客用户与系统程序的联系,如/bin的内容,但通过将本地文件明确地绑定到空间中,使其对访客可用。一个受限的名字空间比通常的导出临时目录树的技术更安全;其结果是在不被信任的用户周围形成了一种笼子。
The cpu command and proxied authentication
当为一个用户(比如说彼得)调用CPU服务器时,其意图是彼得希望以自己的权限运行进程。为了实现这一属性,CPU服务器在收到调用时做了以下工作。首先,监听器分叉出一个进程来处理这个呼叫。这个进程改变为用户 None,以避免在它被破坏时泄露权限。然后,它执行认证协议,以验证呼叫用户真的是Peter,并向Peter证明机器本身是值得信赖的。最后,它使用认证协议重新连接到所有相关的文件服务器,以确定自己是彼得。在这种情况下,CPU服务器是文件服务器的客户端,代表Peter执行认证交换的客户端部分。只有当CPU服务器的管理用户名被允许代表Peter发言时,认证服务器才会给进程票据来完成这个任务。 关系的speaks[LABW91]被保存在认证服务器上的一个表中。为了简化对不同认证域中的用户计算的管理,它还包含不同域中的用户名之间的映射,例如说一个域中的用户rtm与另一个域中的用户rtmorris是同一个人。
File Permissions
将服务构建为文件系统的好处之一是,所有权和权限问题的解决方案会自然而然地出现。在UNIX中,每个文件或目录都有单独的读、写和执行/搜索的权限,这些权限分别属于文件的所有者、文件的组以及其他任何人。组的概念是不寻常的:任何用户名都有可能是一个组名。一个组只是一个用户和组内其他用户的列表。惯例做出了区分:大多数人的用户名没有组成员,而组有一长串附加的名字。例如,传统上sys组有所有的系统程序员,而系统文件是由sys组访问的。考虑一下存储在服务器上的用户数据库的以下两行:
pjw:pjw:
sys::pjw,ken,philw,presotto
第一个是建立用户pjw作为一个普通用户。第二个是将用户sys建立为一个组,并列出四个用户是该组的成员。空的冒号分隔的字段是空间,用于指定一个用户作为组长。如果一个组有一个组长,该用户对该组有特殊的权限,例如可以自由地改变该组中文件的组权限。如果没有指定领导者,该组的每个成员都被认为是平等的,就像每个人都是领导者一样。在我们的例子中,只有pjw可以向他的组添加成员,但所有sys的成员都是该组的平等伙伴。 普通文件由创建它们的用户拥有。组的名称是从存放新文件的目录中继承的。设备文件被特殊对待:内核可以安排适合访问该文件的用户的文件所有权和权限。 进程文件就是一个很好的例子,它由进程的所有者拥有并受读保护。如果所有者想让其他人访问进程的内存,例如让程序的作者调试一个损坏的图像,应用于进程文件的标准chmod命令就可以完成这项工作。 文件权限的另一个不寻常的应用是转储文件系统,它不仅由与原始数据相同的文件服务器提供服务,而且由相同的用户数据库代表。因此,dump中的文件被赋予与常规文件系统中的文件相同的保护;如果一个文件由pjw拥有并受阅读保护,一旦它在dump文件系统中,它仍然由pjw拥有并受阅读保护。另外,由于dump文件系统是不可改变的,文件不能被改变;它永远是受读保护的。缺点是,如果文件是可读的,但应该是受读保护的,那么它将永远可读,而且用户名也很难再使用。
Performance
作为衡量Plan 9内核性能的一个简单措施,我们比较了在Plan 9上和在SGI的IRIX Release 5.3上进行一些简单操作的时间,SGI Challenge M上运行着100MHz的MIPS R4400和1百万字节的二级缓存。测试程序是用Alef编写的,用相同的编译器编译,并在相同的硬件上运行,所以唯一的变量是操作系统和库。 该程序测试了进行上下文切换的时间(Plan 9上的rendezvous,IRIX上的blockproc);琐碎的系统调用(rfork(0)和nap(0));以及轻量级的fork(rfork(RFPROC)和sproc(PR_SFDS|PR_SADDR))。它还测量了在管道上从一个进程向另一个进程发送一个字节的时间以及两个进程之间在管道上的吞吐量。结果见表1。
尽管 “Plan9 “的时间并不惊人,但它们表明,该内核与商业系统相比是有竞争力的。
Discussion
Plan9有一个相对传统的内核;系统的新颖性在于内核以外的部分以及它们的互动方式。在构建Plan 9时,我们把系统的所有方面都考虑在内,在解决方案最适合的地方解决问题。有时,解决方案跨越了许多组件。一个例子是异质指令架构的问题,它由编译器(不同的代码字符,可移植的目标代码)、环境(\(cputype和\)objtype)、名称空间(绑定在/bin中)和其他组件来解决。有时,许多问题可以在一个地方得到解决。最好的例子是9P,它集中了命名、访问和认证。9P确实是系统的核心;可以说,Plan 9内核主要是一个9P复用器。 Plan 9对文件和命名的关注是其表现力的核心。特别是在分布式计算中,事物的命名方式对系统有着深刻的影响[Nee89]。本地名称空间和全局约定的结合,使网络资源相互连接,避免了维护全局统一名称空间的困难,而像文件一样命名一切,使系统易于理解,甚至对新手来说。考虑一下dump文件系统,对于熟悉分层文件系统的人来说,它的使用是微不足道的。在更深的层次上,将所有的资源建立在一个统一的接口之上,使互操作性变得容易。一旦一个资源输出了一个9P接口,它就可以与系统的任何其他部分透明地结合起来,建立不寻常的应用;细节是隐藏的。这听起来可能是面向对象的,但也有区别。首先,9P定义了一套固定的 “方法”;它不是一个可扩展的协议。更重要的是,文件被很好地定义和理解,并预装了熟悉的访问、保护、命名和网络方法。对象,尽管其通用性,并没有定义这些属性。通过将 “对象 “简化为 “文件”,Plan 9获得了一些免费的技术。 尽管如此,还是有可能把基于文件的计算的想法推得太远。将系统中的每一个资源转换成文件系统是一种隐喻,而隐喻是可以被滥用的。克制的一个好例子是/proc,它只是一个进程的视图,而不是一个代表。为了运行进程,通常的fork和exec调用仍然是必要的,而不是做一些像
cp /bin/date /proc/clone/mem
这种例子的问题在于,它们要求服务器做不在其控制范围内的事情。为这样的命令赋予意义的能力并不意味着意义会从回答它所产生的9P请求的结构中自然落下。作为一个相关的例子,Plan 9并没有把机器的网络名称放在文件名空间中。网络接口提供了一个非常不同的命名模式,因为在这种文件上使用打开、创建、读取和写入,不会提供一个合适的地方来编码任意网络的调用设置的所有细节。这并不意味着网络接口不能像文件一样,只是它必须有一个更严格的结构定义。 下一次我们会采取什么不同的做法?实施中的一些元素是不令人满意的。在内核中使用流来实现网络接口,可以将协议动态地连接在一起,例如将同一个TTY驱动附加到TCP、URP和IL连接上,但是Plan 9没有使用这种可配置性。(然而,在研究UNIX系统中,它被利用了,流就是为这个系统发明的)。用静态I/O队列代替流将简化代码并使其更快。 尽管Plan 9的主要内核可以在许多机器上移植,但文件服务器是单独实现的。这造成了几个问题:必须编写两次的驱动程序,必须修复两次的错误,以及文件系统代码的可移植性较弱。解决方法很简单:文件服务器内核应该作为常规操作系统的一个变体来维护,没有用户进程,并由特殊的编译内核进程来实现文件服务。对文件系统的另一个改进是改变内部结构。WORM点唱机是硬件中最不可靠的部分,但由于它保存着文件系统的元数据,所以它必须存在,以便为文件提供服务。系统可以被重组,使WORM只是一个备份设备,而文件系统本身驻留在磁性磁盘上。这将不需要改变外部接口。 尽管Plan 9有每个进程的名字空间,但除了直接继承外,它没有机制将一个进程的名字空间的描述给另一个进程。例如,cpu命令在一般情况下不能重现终端的名字空间;它只能重新解释用户的登录配置文件,并对诸如要加载的二进制目录的名字进行替换。这就错过了运行cpu之前的任何本地修改。相反,它应该可以捕获终端的名称空间,并将其描述传送给远程进程。 尽管存在这些问题,”Plan9 “运作良好。它已经成熟地成为支持我们研究的系统,而不是研究本身的主题。实验性的新工作包括开发更快的网络接口,在客户端内核中进行文件缓存,封装和导出名称空间,以及在服务器崩溃后重新建立客户端状态的能力。现在的注意力集中在使用该系统来建立分布式应用。 Plan 9成功的原因之一是我们在日常工作中使用它,而不仅仅是作为一个研究工具。积极的使用迫使我们解决出现的缺点,并调整系统以解决我们的问题。通过这个过程,Plan 9已经成为一个舒适的、富有成效的编程环境,同时也是进一步进行系统研究的工具。
References
[9man] Plan 9 Programmer’s Manual, Volume 1, AT&T Bell Laboratories, Murray Hill, NJ, 1995.
[ANSIC] American National Standard for Information Systems - Programming Language C, American National Standards Institute, Inc., New York, 1990.
[Duff90] Tom Duff, ‘‘Rc - A Shell for Plan 9 and UNIX systems’’, Proc. of the Summer 1990 UKUUG Conf., London, July, 1990, pp. 21-33, reprinted, in a different form, in this volume.
[Fra80] A.G. Fraser, ‘‘Datakit - A Modular Network for Synchronous and Asynchronous Traffic’’, Proc. Int. Conf. on Commun., June 1980, Boston, MA.
[FSSUTF] File System Safe UCS Transformation Format (FSS-UTF), X/Open Preliminary Specification, 1993. ISO designation is ISO/IEC JTC1/SC2/WG2 N 1036, dated 1994-08-01.
[ISO10646] ISO/IEC DIS 10646-1:1993 Information technology - Universal Multiple-Octet Coded Character Set (UCS) — Part 1: Architecture and Basic Multilingual Plane.
[Kill84] T.J. Killian, ‘‘Processes as Files’’, USENIX Summer 1984 Conf. Proc., June 1984, Salt Lake City, UT.
[LABW91] Butler Lampson, Martín Abadi, Michael Burrows, and Edward Wobber, ‘‘Authentication in Distributed Systems: Theory and Practice’’, Proc. 13th ACM Symp. on Op. Sys. Princ., Asilomar, 1991, pp. 165-182.
[MBSS87] S. P. Miller, B. C. Neumann, J. I. Schiller, and J. H. Saltzer, ‘‘Kerberos Authentication and Authorization System’’, Massachusetts Institute of Technology, 1987.
[NBS77] National Bureau of Standards (U.S.), Federal Information Processing Standard 46, National Technical Information Service, Springfield, VA, 1977.
[Nee89] R. Needham, ‘‘Names’’, in Distributed systems, S. Mullender, ed., Addison Wesley, 1989
[NeHe82] R.M. Needham and A.J. Herbert, The Cambridge Distributed Computing System, Addison-Wesley, London, 1982
[Neu92] B. Clifford Neuman, ‘‘The Prospero File System’’, USENIX File Systems Workshop Proc., Ann Arbor, 1992, pp. 13-28.
[OCDNW88] John Ousterhout, Andrew Cherenson, Fred Douglis, Mike Nelson, and Brent Welch, ‘‘The Sprite Network Operating System’’, IEEE Computer, 21(2), 23-38, Feb. 1988.
[Pike87] Rob Pike, ‘‘The Text Editor sam’’, Software - Practice and Experience, Nov 1987, 17(11), pp. 813-845; reprinted in this volume.
[Pike91] Rob Pike, ‘‘8½, the Plan 9 Window System’’, USENIX Summer Conf. Proc., Nashville, June, 1991, pp. 257-265, reprinted in this volume.
[Pike93] Rob Pike and Ken Thompson, ‘‘Hello World or Καλημέρα κόσμε or こんにちは 世界’’, USENIX Winter Conf. Proc., San Diego, 1993, pp. 43-50, reprinted in this volume.
[Pike94] Rob Pike, ‘‘Acme: A User Interface for Programmers’’, USENIX Proc. of the Winter 1994 Conf., San Francisco, CA,
[Pike95] Rob Pike, ‘‘How to Use the Plan 9 C Compiler’’, Plan 9 Programmer’s Manual, Volume 2, AT&T Bell Laboratories, Murray Hill, NJ, 1995.
[POSIX] Information Technology—Portable Operating System Interface (POSIX) Part 1: System Application Program Interface (API) [C Language], IEEE, New York, 1990.
[PPTTW93] Rob Pike, Dave Presotto, Ken Thompson, Howard Trickey, and Phil Winterbottom, ‘‘The Use of Name Spaces in Plan 9’’, Op. Sys. Rev., Vol. 27, No. 2, April 1993, pp. 72-76, reprinted in this volume.
[Presotto] Dave Presotto, ‘‘Multiprocessor Streams for Plan 9’’, UKUUG Summer 1990 Conf. Proc., July 1990, pp. 11-19.
[PrWi93] Dave Presotto and Phil Winterbottom, ‘‘The Organization of Networks in Plan 9’’, USENIX Proc. of the Winter 1993 Conf., San Diego, CA, pp. 43-50, reprinted in this volume.
[PrWi95] Dave Presotto and Phil Winterbottom, ‘‘The IL Protocol’’, Plan 9 Programmer’s Manual, Volume 2, AT&T Bell Laboratories, Murray Hill, NJ, 1995.
[RFC768] J. Postel, RFC768, User Datagram Protocol,DARPA Internet Program Protocol Specification, August 1980.
[RFC793] RFC793, Transmission Control Protocol,DARPA Internet Program Protocol Specification, September 1981.
[Rao91] Herman Chung-Hwa Rao, The Jade File System, (Ph. D. Dissertation), Dept. of Comp. Sci, University of Arizona, TR 91-18.
[Rit84] D.M. Ritchie, ‘‘A Stream Input-Output System’’, *AT&T Bell Laboratories Technical Journal,*63(8), October, 1984.
[Tric95] Howard Trickey, ‘‘APE — The ANSI/POSIX Environment’’, Plan 9 Programmer’s Manual, Volume 2, AT&T Bell Laboratories, Murray Hill, NJ, 1995.
[Unicode] The Unicode Standard, Worldwide Character Encoding, Version 1.0, Volume 1, The Unicode Consortium, Addison Wesley, New York, 1991.
[UNIX85] UNIX Time-Sharing System Programmer’s Manual, Research Version, Eighth Edition, Volume 1. AT&T Bell Laboratories, Murray Hill, NJ, 1985.
[Welc94] Brent Welch, ‘‘A Comparison of Three Distributed File System Architectures: Vnode, Sprite, and Plan 9’’, Computing Systems, 7(2), pp. 175-199, Spring, 1994.
[Wint95] Phil Winterbottom, ‘‘Alef Language Reference Manual’’, Plan 9 Programmer’s Manual, Volume 2, AT&T Bell Laboratories, Murray Hill, NJ, 1995.