编程珠玑番外篇-K. Plan 9 的故事(修订版)
这样的例子在历史中屡见不鲜。Jobs 和 Apple 分手后开创的 NeXT 公司的操作系统和硬件设备,创新点很多,市场反响却不大。而 NeXT 系统在软件和硬件设计上的创新,以及工业设计的思想,最终成为了现在 iOS 系统、Mac 系统软硬件设计的基石。同样是 Apple, 当年出品的 Hypercard 软件,首创了超文本格式和交互式页面。虽然 HyperCard 的这些创新在当时并不显得太出众,最终被苹果终止开发。但到了 Internet 出现之后,Tim Berners-Lee 受到这种“超文本 (hypertext)”格式的启发,将这些思想平移到联网的计算机上,由此出现了万维网。Ward Cunningham 更是受这一张一张的“数字卡片”受到,发展出了世界上第一个 wiki 系统 WikiWikiWeb。所有的这些例子,都说明了一项当时未必得到大多数人认可的创新可能会在意想不到的地方凤凰重生。 本节我们介绍的 Plan 9 操作系统,也是这样的一个例子。
FTPFS 虚拟文件系统
大部分读者应该都用过 FTP 在两台机器之间传送文件。FTP 是一种简单成熟的传输协议。试想现在我们需要修改一个 FTP 服务器上的文件,我们无法直接用本机上的编辑器打开这个远程的文件编辑,而需要先下载这个文件,修改后再上传。这种编辑远程文件的方法显然比编辑本机的文件麻烦多了。 我们退后一步仔细想一下这个不方便,会发现,文件是同样的一个文件,只是因为文件不在本地,我们需要借助于 FTP 协议访问,所以我们就不能直接编辑它了(事实上有些功能强大的编辑器如 VIM 仍然可以编辑,但普通的编辑器则不能)。在一切都是文件的假设下,我们可以不加区别的访问软盘,硬盘和闪存盘上的文件,但这里,因为中间多了一个网络协议,这种“一切都是文件”的方便特性就消失了。 因为这个需求很常见,很多 FTP 客户端,如 Windows 下的 LeapFTP, Mac 下的 Cyberduck,都做了一个贴心的功能,在你想要编辑远程文件的时候,自动将其下载成一个临时文件,等你修改结束后又自动上传。但是这依赖于 FTP 客户端,并不是每个客户端都提供了这类支持。
FTPFS 就是针对上面提出的这种不方便而出现的一种技术。通过利用一个叫做 FUSE (Filesystem in Userspace) 的技术,FTPFS 允许用户将远程的 FTP 文件系统挂载到本地的文件系统上,使得用户可以像操作本地文件一样操作远程文件1,包括查看,编辑,删除和重命名等等。而实际网络传输协议的细节,则对用户隐藏了。实际上,FUSE 技术可以用来实现很多“虚拟”的文件系统,而不仅限于将 FTP 文件系统挂载到本地文件系统上这一种。比如,使用 HTTP 协议的文件系统,SSH 服务器上的文件,Flicker 上的图片,维基百科上的文章,都可以通过 FUSE,抽象成一种虚拟的挂载在本地文件系统上的文件系统。这些虚拟的文件系统,隐藏了协议的细节,将各种不同类型的协议支持下的资源抽象成一个文件系统,也可以挂载到本地的文件系统上。
虚拟文件系统能把资源无差别的抽象成文件系统。这种做法消除了网络协议的不同造成的访问障碍,方便了用户对各种不同资源的访问。这种 “消除协议差异,一切资源都是文件”的思想,实际上来源于 Plan 9。毫不令人惊讶的是,在 Plan 9 中,干脆就没有 FTP 这个命令,所有对 FTP 的操作都是采用挂载 ftpfs 挂载文件系统的方式实现的。
一切都是文件(这次是真的)
上面我们提到了虚拟文件系统可以把资源无差别地抽象成一个文件系统,而这个思想是来源于 Plan 9 操作系统的。且慢,早在 UNIX Programming Environment 中, Brian W. Kernighan 就提出了 “UNIX 中,一切都是文件” 的设计哲学。事实上,UNIX 中的确很多对象是文件:进程是文件,设备是文件,命名管道也是文件。但是,也有很多不是文件,尤其是由其他非 Bell 实验室加入 UNIX 的组件。举例来说,计算机网络设备和服务不是文件(UNIX 的网络支持部分最先由 UC Berkerly 开发),图形界面中的对象也不是文件(UNIX 的图形界面支持最初由 MIT 的 X 工作组开发)。“一切都是文件” 这个口号因为 UNIX 的发展和新模块的加入而不再贴切。
UNIX 出现的时候,支持的设备都很简单,都可以抽象成文件交由内核统一管理,由内核提供 read/write 等系统调用访问设备。随着硬件的发展,一些新的硬件需要有超越系统调用范围的控制方式(例如我们可以控制光盘驱动器弹出托盘,而这个操作在传统磁盘驱动器上是不存在,也不能简单的抽象为 read/write 甚至 unmount 操作),或者为效率着想,需要用户空间程序直接和设备通信(如网卡,高速硬盘)。因为这些需求,和为未来扩展性考虑,Bell 实验室在 UNIX 第七版中,也不得不引入 ioctl 等具有无穷扩展性的系统调用机制,配合设备驱动程序,支持对设备的控制。这些做法,绕开了原先统一的 read/write 设备访问方式。也就是说,设备再也不能简单地抽象为文件了。
随着 UNIX 发展而失却的“一切都是文件”的纯粹哲学,正是 Plan 9 想要恢复的。在 Plan 9 中,通过实现一个叫做 9P 的文件协议,用户可以自由的把任何资源或服务抽象成本地的一个“虚拟的”文件或者目录,而对这些文件的操作,会通过 9P 协议,自动映射到对原来资源或者服务的操作。 这样,访问资源的各种细节就被隐藏了。在对付那些需要 ioctl 或者其他控制机制的设备或者应用程序时,Plan 9 提倡将程序的控制部分抽象成一个支持 read/write 的 ctl 文件,而非使用专门的 ioctl 系统调用。这样,其他程序就可以通过读写 ctl 文件与被控制的程序通信。从对资源和对控制的抽象不难看出来,Plan 9 把 UNIX 中“一切都是文件”的思想做了进一步的升华。在 Plan 9 里面,真的是一切都是文件了──设备是文件,窗口管理器是文件,Email 程序是文件(实际上所有程序都是文件),网络是文件(实际上所有服务包括 DNS 都是文件),等等。
Plan 9 的创新
要说 Plan 9 的特性,就不能不先介绍一下它的几个创造者。和 UNIX 一样, Plan 9也是从 Bell 实验室计算机科学研究中心开发的。其项目主要负责人是 Rob Pike (现在在 Google 工作,负责 Go 编程语言),当时在 Bell 实验室的很多人,包括 UNIX 的两位创始人,Ken Thompson 和 Dennis Ritchie ,以及 Brain Kernighan、Doug Mcllroy (UNIX 管道的提出者)都参与了这个项目的开发。从某种意义上来说,Plan 9 有点充当 UNIX 继承人的味道。事实上 Rob Pike 最初,也的确是想构建一个更加“现代的 UNIX”。除了坚持 UNIX 中已经成功了的“一切都是文件”,“KISS”等原则外,Plan 9 在原有 UNIX 的设计理念上做了新突破,其中最值得一提的,就是“分布式操作系统” 的理念。
Plan 9 这个分布式操作系统的出现和当时计算机发展的趋势是密不可分的。我们都知道, UNIX 是一种分时操作系统,用户分享机器资源。UNIX 操作系统则负责在各任务(或者说进程)之间调度。因此,UNIX 是一个中心化的操作系统。CPU、内存、IO 以及所有的任务的调度都是集中被 UNIX 管理的。上世纪 80 年代中期,更加便宜的微型计算机开始普及。这些微型计算机各自有着磁盘、CPU、内存和 IO 设备。Plan 9 的指导思想,就是把微机组织起来,方便的实现资源共享。
Plan 9 里,能共享的资源包括文件系统、图形界面、IO 设备、以及 CPU 和内存等计算资源。这些资源之间千差万别,我们固然可以针对每种资源设计一个协议,如文件分享用 NFS,图形界面用 X 协议,打印机用 CUPS 协议等等,不过这种做法在 Plan 9 的设计者看来是不够优雅的。他们采用的,是在上文我们已经提到过的“一切都是文件”的方法[cite:Plan 9, a distributed system]。我们可以用两个很有启发性的例子来说明。
例一、替换 CPU
假想一下我们有一台日常使用但性能不佳的笔记本,和一台不在本地但性能强劲的服务器。 我们当然能够使用远程计算机的强劲的 CPU 运行一些计算量特大的程序。这不是什么难事,因为几乎所有操作系统都支持登陆到远程的机器。然而,麻烦的是,如果在远程运行程序需要读写本地的文件,或者访问挂载在本地笔记本上的打印机,扬声器麦克风之类设备,我们除了在本地和远程之间把文件传来传去之外,并没有什么好方法。特别的,如果我们想借用另一台计算机上强劲的 CPU 做音频和视频解码,来播放一个放在本机光盘驱动器里的电影文件的话,我们是不可能指望远程计算机既能读本地的光驱,又能把音频投递到本机的扬声器上的。
Plan 9 中,有一个简单的 cpu 命令,能够让用户自然地使用一个其他机器上的 CPU 运行程序,且仍然能够访问本地的所有文件和设备。也就是说,我们可以用远程计算机上强劲的 CPU 做图像处理,媒体解码等任务,并且可以直接把声音播放到本地的扬声器。cpu 命令给人的感觉,是除了给机器换个了 cpu 外,其他一切都和原来一样。这个看似 “神奇” 的功能,其实在 Plan 9 里实现起来一点都不复杂: cpu 指令首先连接服务器上,然后将本地的所有资源和文件系统,包括窗口管理器,光盘驱动器,扬声器等设备(别忘了他们都是文件),一股脑儿挂载到服务器上,成为服务器上的资源。这样,在服务器上运行的程序,就可以“自然地”使用本地的键盘鼠标和显示器完成交互,还可以访问你本地的显示器扬声器等设备。
cpu 命令真的就是名副其实的换掉了本地计算机的 cpu (其实还有内存)而保留其他一切设备。Plan 9 的这个 cpu 命令,带有强烈的分布式操作系统的特征,而我们平时接触的操作系统都不是分布式操作系统,因此 cpu 这个命令至今在现代主流操作系统上没有完全等价物。
例二、进程间控制和通信
进程间通信可以提高使用计算机的效率(详细请参见 Page XX:开发人员为何应该使用 Mac OS X)。UNIX 下的管道就是一个经典的进程间通信的例子。在图形界面程序和集成化的程序出现后, 应用程序不断的把多种功能集成到一起,进程间通信反而变得相对困难了。比如说,即使有个给汉字加拼音的程序,除了来回复制粘贴,我们还是不能方便地从文字编辑程序中选取一段自动加上拼音。而 UNIX 下的编辑器可以借助管道很简单完成这样的操作。这个问题的本质困难,用操作系统的眼光来看,在于进程这个对象,没有在运行时暴露出应有的通信和控制接口。
Plan 9 的一切都是文件的思想从一个新的角度,解决了程序间的数据共享问题。Plan 9 倡导应用程序在运行时都把自己的内部状态抽象成一个文件系统。举例来说,一个邮件客户端程序不光支持图形界面下查看邮件,用户还能够直接通过
cat /mail/fs/inbox/1/subject
cat /mail/fs/inbox/1/body
来查看收件箱(inbox)中第一封邮件的主题和内容。这种设计,使得应用程序不再成为进程间通信的障碍,从而拷贝粘贴也变得没有必要。比如说,我们可以直接把草稿箱里邮件的内容通过管道送给其他拼写检查器。邮件客户端提供的拼写检查器再差也没关系了。这种把应用程序中的对象暴露出来的想法,和 Mac OS X 中的应用程序暴露一个Applescript 字典的设计思想异曲同工。
同样的道理,Plan 9 也从一切都是文件的思想出发,解决了了程序控制问题。 Plan 9 鼓励每个应用程序和设备在抽象成文件的时候,都暴露出一个抽象的 ctl 文件。这样,其他应用程序可以通过向 ctl 文件写命令的方式,运行时控制应用程序。举例来说,Plan 9 的窗口管理器 Rio,提供了 ctl 文件,我们可以通过读取和写入 ctl 文件,实现一些 Rio 本身不支持的如平铺所有窗口的操作。同样,通过对邮件程序的 ctl 操作,我们可以实现邮件的发送和接受的控制等等。Mac OS X 下的 Applescript 也可以完成类似的功能。遗憾的是,除了 Plan 9 和 Mac OS X,其他操作系统对这类进程间控制和通信的支持都不够完整。
实践者指南
对 Plan 9 感兴趣,想要更多了解 Plan 9 的读者,可以到 http://plan9.bell-labs.com/plan9/ 下载 Live CD, 并在多台联网的机器或虚拟机中安装该系统。喜欢 Plan 9 里的一些命令,而不想折腾系统的读者,可以到 http://swtch.com/plan9port/ 下载可以在 Linux,Mac OS X 等主流操作系统上运行的 Plan 9 的移植程序。Rob Pike 的网站 cat-v.org 有最完整的 Plan 9 的资料,以及对 UNIX 设计哲学的反思。