Unix 已经让“一切皆文件”成为一句有用的表述。Plan 9 更具决定性的抽象又向前走了一步:每个进程都可以得到一个各不相同、由文件组装而成的世界,其中的树既可以由内核设备供应,也可以来自用户程序或另一台机器。这套架构从私有命名空间出发,经由 bind 和 mount,跨过 9P 协议边界,最终回到普通的 open、read 或 write。所得环境更像按进程组合的服务,固定文件系统的形态退到了次要位置。
Plan 9 的模块化由此落在一个少见的具体环节——路径解析。程序会打开 /net/tcp 这样的约定路径;至于网络栈位于何处、背后采用哪种实现,都由进程的命名空间决定。服务注册表、专用 RPC 客户端和程序代码里保存的服务位置都退出了这条访问链。位置无关性因此来自路径名解析,本身就是操作系统的一部分。[1][2]
这棵树由进程组装,挂载只是其中一步
Plan 9 进程看到的是一棵以根目录为起点的层次树,来源却可以跨越多块磁盘乃至多台计算机。mount 取得一个连接到文件服务器的双向文件描述符,再把服务器的树挂接到选定路径。bind 把当前命名空间里已经可见的一棵树映到另一处。unmount 则移除这项挂接。服务器既可对应存储设备,也可提供其他类型的文件服务。[1][2]
新挂接的树可以取代该路径原有的目录,也可以排在现有条目之前或之后。多棵树按顺序叠放,会组成一个联合目录(union directory)。查找依次搜索各组成部分;创建文件时,系统选择第一个明确允许接纳新文件的组成部分。于是,Plan 9 可以把特定架构的二进制文件、shell 脚本和用户自己的命令组合到 /bin,shell 只需使用这个目录,搜索顺序留给命名空间处理。[1]
这里最要紧的作用范围是命名空间组。子进程通常与父进程共享同一视图,rfork 则控制包括命名空间在内的资源继续共享,或复制到一个新组。进程调用 rfork(RFNAMEG),便可让自己的命名空间脱离原有共享组,再重新布置其中的挂接。调试器可以把昨天的头文件叠加到 /sys/include;窗口可以得到自己的 /dev/cons;受限任务可以只看到运行所需的程序文件和服务接口。这些变化真实作用于相应的进程家族,无关进程看不到它们。[1][2]
这种粒度细于为整台机器选择一个根目录,也把约定名称和具体身份分开。/dev/cons 表示“当前上下文中的控制台”,指向随上下文而定的设备。/bin/date 表示约定路径上与当前环境匹配的程序,即便实际二进制文件来自特定架构的树。程序使用稳定名称,命名空间为名称赋予本地含义。[1]
9P 让所有服务器共用同一道窄门
私有命名空间若要用于跨服务器组合,还需要一套共同的通信方式,9P 正是这条通道。文件服务器给出一棵层次树;客户端先协商协议版本与最大消息大小,在需要时完成认证,然后发送 Tattach,并带上由客户端选定的 fid,用它标识服务器树的根。Twalk 带上已有 fid、路径分量和另一个由客户端选定的 fid;遍历成功后,后一个 fid 便与目标关联。响应会返回遍历过程中到达的各个对象的 qid。随后,Topen、Tread、Twrite 与 Tclunk 把熟悉的文件操作转换成请求与响应消息。[3]
fid 是客户端选定的会话状态;每次读取都用它指向对象,省去了反复发送路径名。qid 是服务器赋予对象的身份,其中包含 path 部分和版本字段。借助 qid,客户端可以分辨经由不同名称到达的同一对象,也可以识别旧名称被复用后出现的新对象。tag(标签)把并发请求与响应配对,因此服务器可以暂缓一次受阻的读取,先回答其他请求。[3]
这组精简操作构成了共同通道。内核设备可以用本地调用回应这组操作;用户空间程序可以合成一棵树,经由管道提供服务;远程文件服务器则可以通过网络传输响应同一组操作。9P 数据包由挂载驱动构造,大多数应用只发起普通文件系统调用。路径解析一旦跨入由文件服务器提供的树,驱动便在交界处完成转换。[1][3]
这套架构主张,各类资源都能呈现实用的文件语义;“所有资源都以文件形式存储”超出了这项主张。读取 /proc/<pid>/status 时,系统即时合成文本。向进程的 ctl 文件写入 stop 或 kill,会改变内核状态。/net/tcp/clone 分配一个连接;把连接命令写入它的 ctl 文件,会推进该连接的状态;它的 data 文件负责传送字节。窗口系统还可以分别为每个客户端提供一棵私有的鼠标、屏幕与控制台树。[2]
这也划出了那句著名比喻的适用范围。文件接口与 9P 把一套访问操作纳入统一规范,让服务之间的命名、保护与工具复用保持一致;控制消息的含义则由每项服务自行规定。使用 /net/tcp 的程序仍要理解网络服务的文件布局和状态机。Plan 9 省去一类专用集成代码,应用仍需掌握领域协议。
cpu 将执行移往远端,同时保留终端环境
当计算跨越机器时,这套设计最为清楚。Plan 9 的 cpu 命令会在 CPU 服务器上启动一个 shell。用户进入远端后,面对的是重新构造的适用命名空间;终端本地环境的一部分随后导出给远程进程。shell 可以使用靠近 CPU 服务器的文件,它所启动的应用依然能在惯用路径上看到终端的控制台与图形设备。[1][2]
反向操作同样能说明问题。import helix /net 可以把另一台机器的网络接口放到调用者的 /net。使用这一路径的软件仍按原有方式工作,代码保持原样。贝尔实验室论文给出的远程调试示例中,导入远端 /proc 后,熟悉的进程工具便可检查另一台机器;调试器根据目标二进制文件识别其架构,判断依据独立于终端架构。[2]
依赖在应用访问前已经完成重新绑定,这正是透明性的来源;网络距离依然存在。进程仍受服务器权限、可用性、时序与语义约束,应用代码则省去了与位置相关的连接逻辑。
故障面随挂接点转移
共同接口的简洁会遮住三条界线。
第一,可见性与权限分属两层。私有命名空间可以隐藏资源,或只暴露一棵范围经过限定的合成树,因此适合作为隔离手段。挂接一棵树时,系统仍要处理用户身份、用 afid 完成的可选认证,以及文件服务器执行的权限检查。名称重排只改变可见性;对不可信对端的认证仍由连接过程负责。服务器权限过宽时,整齐的挂载路径也保留着同样的风险。[3][4]
第二,简单的请求/响应式 I/O 对距离敏感。2005 年的 Linux 实现研究发现,结果随工作负载而变:在小文件 PostMark 测试中,9P2000 表现良好;面对大型静态文件,NFS 则从回写和较宽松的读取一致性中获益。[4] 后来一篇 RIT 学位论文把顺序传输视为未缓冲 9P 交互的一项具体弱点,并试验用带外数据流减少往返次数。[6] 两项结果的适用范围都限于各自的工作负载,普遍性的快慢判断超出了这些证据的范围。协议设计的简洁之外,工作负载形态、消息大小、缓存方式与网络延迟仍共同决定实际表现。
第三,缓存会改变约定。Linux 当前的 v9fs 客户端支持 Unix 套接字、TCP、virtio、RDMA 与 USB gadget 链路等传输方式,也提供多种缓存模式。文档警告,在 cache=loose 模式下,客户端可以跳过对缓存值的服务器校验,因此该模式只适用于独占挂载,而且底层树的所有变化都须经由该客户端。[5] 路径外观看来完全普通,一致性却已经成为一项运维选择。
审查任何沿用 Plan 9 思路的系统时,都要回答以下问题:
- 谁可以挂接服务,授权决定在哪里执行?
- 哪个进程组会得到这项挂接,后代进程能否重新布置或导出它?
- 服务器为 fid、受阻请求和已打开的控制文件保存哪些状态?
- 实际工作负载会产生多少次往返,哪种缓存策略会改变写后读预期?
- 替换服务器除了沿用惯用路径名称,是否也保持其背后的语义?
延续至今的思想:在执行前完成组合
Plan 9 最终留在 Unix 主流继承路线之外。2021 年,诺基亚贝尔实验室将软件版权转交 Plan 9 Foundation;当时这套系统的影响力已经超出实际装机规模,这项举动体现了对它的守护与延续。[7] 它留下的架构思路仍能在原操作系统之外看到。Linux 内核至今仍包含 v9fs,并记录了 9P 在远程、本地、虚拟机和嵌入式设备传输方式上的挂载方法。[5] 2005 年的 USEN 研究还明确把 9P 与 Linux 私有命名空间结合起来,用作试验分布式资源的基础。[4]
比“让每个 API 都成为文件”范围更窄、也更实用的经验是:在执行前组合进程的依赖,为这些依赖赋予稳定的本地名称,并让服务边界清晰可察。 Plan 9 用命名空间、9P 树和普通文件操作完成这些工作。当精简接口确实适合相应资源,而且运维人员能够逐一分析各挂接点的权限、状态、延迟与故障时,这套模型便能奏效。
封面照片中的贝尔实验室团队坐在一间堆满终端、线缆、手册和不同机器的房间里。面对这种物理异质性,Plan 9 的大胆回应,是接受这些机器彼此不同,再让每个进程组装出它所需要的那一台。
来源
- Rob Pike、Dave Presotto、Sean Dorward、Bob Flandrena、Ken Thompson、Howard Trickey 与 Phil Winterbottom,《Plan 9 from Bell Labs》——介绍私有命名空间、联合目录、9P、文件服务与
cpu的系统概览。 - Rob Pike、Dave Presotto、Ken Thompson、Howard Trickey 与 Phil Winterbottom,《The Use of Name Spaces in Plan 9》——涵盖
mount、bind、rfork、/proc、/net、import 与远程调试示例的核心设计论文。 - 《Plan 9 Programmer's Manual》,“Introduction to the Plan 9 File Protocol, 9P”——介绍消息帧、标签、fid、qid、认证、遍历、I/O 与权限。
- Eric Van Hensbergen 与 Ron Minnich,《Grave Robbers from Outer Space: Using 9P2000 Under Linux》,USENIX Annual Technical Conference,2005 年——一项独立实现研究,涵盖协议分析、命名空间讨论与工作负载基准测试。
- Linux 内核文档,“v9fs: Plan 9 Resource Sharing for Linux”——介绍当前传输方式、挂载示例、缓存模式与一致性警告。
- John Floren,《FTP-like streams for the 9P file protocol》,罗切斯特理工学院学位论文,2010 年——独立考察连续传输延迟与一种流式扩展。
- 诺基亚贝尔实验室,“Plan 9 from Bell Labs in Cyberspace!”,2021 年 3 月 23 日——介绍开发历史、版权转交,并提供贝尔实验室团队照片。