Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Plan 9 and 9P

The strangest of the pre-web architectures, and the one most worth taking seriously even though it sits oddly inside the book’s argument, is Plan 9 from Bell Labs. Plan 9 was not a hypermedia system, not an internet protocol in competition with the web, and not a federated discussion network. It was an operating system. It was, however, an operating system that proposed an answer to the question “how should networked computing look” that was structurally different from what Unix had proposed, what Windows would propose, what Mac OS would propose, and what the web on top of Unix-Windows-Mac OS would propose. The answer was that the network should be the operating system. Every resource on every machine on the network should be addressable as a file. Every process should have its own private view of the file system. Every operation should be a file operation. And the protocol that connected the parts — 9P — should be the only protocol you needed for almost anything. Plan 9 lost as a system. It won as a vocabulary; pieces of its design have been absorbed, mostly without credit, into almost every system that has come since. This chapter is about what Plan 9 actually was, what it proposed, what survived, and what did not.

Where it came from

Plan 9 was the work of the same group that had built Unix at Bell Labs through the 1970s and into the 1980s. By the late 1980s the Computing Sciences Research Center at Bell Labs — the part of Bell Labs that had produced Unix — had concluded that Unix, as it had been deployed and extended by the world outside Bell Labs, had grown into something different from what they had been trying to build. Unix had become a per-machine operating system, with networking added on as a peripheral concern. Distributed computing meant cobbling together TCP/IP services on top of local machines, with each machine retaining its own user accounts, its own file system, its own process model, and its own conventions. The promise of a uniform distributed system, with one user identity across the network, one filesystem across the network, and one programming model across the network, had not been kept by Unix and its descendants. The Bell Labs group decided to try again.

The principal designers of Plan 9 were Rob Pike, Ken Thompson, Dave Presotto, and Phil Winterbottom, with substantial contributions from Howard Trickey, Tom Duff, Doug McIlroy, and others. The development began around 1986. The first internal release was around 1989; the first public release came in 1992 under the name “Plan 9 from Bell Labs,” a reference to the Ed Wood film Plan 9 from Outer Space that became the official explanation of the name. Three more editions followed: 1995, 2000, and 2002 (the “Fourth Edition”). The system was released initially under Lucent Technologies’ research license; in 2002 the source code was released under the more permissive Lucent Public License, and in 2014 (post-Lucent, by then Alcatel-Lucent) it was relicensed under the GPLv2. The system is still maintained, in two active forks: 9front, which is the more active community fork, and plan9port, a port of the user-space programs to other Unix systems that has kept some of Plan 9’s vocabulary alive on Linux and macOS.

The central design idea

Plan 9’s central design idea was an extension of the Unix “everything is a file” doctrine. Unix had treated files, devices, and a few other resources as accessible through the same file-system interface; a program reading data did not need to know whether the data was coming from a disk file, a terminal, or a pipe, because all of them looked like file descriptors. Plan 9 pushed this idea further than Unix had been willing to. In Plan 9, the network was a file. Processes were files. The display was a file. The mouse was a file. Memory was a file. Audio devices were files. Every resource the operating system managed was exposed through the file system, and every operation on every resource was a file operation: open, read, write, close.

The consequence was that almost no operation in Plan 9 required a special-purpose system call. Where Unix needed dozens of system calls for various things (socket, bind, listen, accept, ioctl, mmap, and a long list of others), Plan 9 needed essentially the file operations and a few more. A program that wanted to make a TCP connection did not call a socket function; it wrote to a file in the /net hierarchy. A program that wanted to query the kernel about a process did not call a system call; it read a file in /proc. A program that wanted to draw on the screen did not call a graphics library function; it wrote to a file in the window system’s namespace.

This sounds austere and slightly insane. It is austere; it is not insane. The reason it works is that the file system was extended to be expressive enough to support the operations the resources needed. A file in /net was not a passive storage location; it was an active endpoint, with reads and writes that triggered network operations. A file in /proc was not a snapshot; it was a live view, with reads that returned current state. The protocol that connected programs to resources, 9P, was rich enough to express the semantics each resource needed within a uniform file-operations interface.

The benefit was that any program that could read and write files could use any resource the operating system exposed. This included resources on remote machines. If a remote machine exposed its /net hierarchy on its 9P file server, a program on a local machine could mount the remote /net into its own namespace and make network connections from the remote machine as easily as from the local one. The same was true of /proc: you could read the process list of a remote machine by mounting its /proc into your namespace and reading the files. The same was true of the display: you could mount a remote machine’s window system into your namespace and draw on its screen as if it were yours.

Per-process namespaces

The second of Plan 9’s design commitments was that each process should have its own private view of the file system. Unix processes inherit a shared global file system; what /usr/local/bin means is the same for every process on the machine. Plan 9 processes inherit a private namespace that can be different from any other process’s. A process can mount things into its namespace at arbitrary paths, can unmount them, can replace one mount with another, can union two directories into one virtual directory, and can do all of this without affecting any other process. The shell starts a new process with a copy of the parent’s namespace, but the child can modify its own namespace independently.

This is a profound generalization of the Unix chroot mechanism. Chroot lets a process change the apparent root of the file system; Plan 9’s namespaces let a process change anything about the file system, with arbitrary granularity. The consequences for system design are substantial. A network service running as a user could be confined to a namespace containing only the files it needed, with no access to the rest of the system. A workspace for a particular task could be a namespace combining the relevant files from many sources, with the task’s tools seeing only the relevant ones. A user logged in to a terminal could have a namespace combining files from the terminal’s local disk, the user’s home machine, and any cluster of remote machines the user wanted to draw from, all stitched together into a single coherent view at the user’s preferred paths.

The current popularity of containers — Linux namespaces, Docker, Kubernetes — is, structurally, a recovery of what Plan 9 had done with namespaces in 1992. The Linux kernel’s namespace facilities, developed in the 2000s, made it possible to give each process group a private view of various aspects of the system: mounts, processes, network, hostnames. Docker, released in 2013, packaged these facilities into a developer-friendly tool. Kubernetes, released in 2014, scaled the namespace abstraction to clusters. Each step has been a partial recovery of what Plan 9 had been doing. The recoveries have arrived in industrial form, with massive adoption, twenty-plus years after the original design.

The recoveries are not, however, complete. Plan 9’s namespaces were uniform: every resource was a file, every namespace was a tree of files, and the operations on namespaces were the same regardless of what was inside. Linux’s namespaces are siloed: the mount namespace handles file system mounts, the network namespace handles networking, the PID namespace handles processes, and the various namespaces do not compose into a unified abstraction. The result is that working with Linux namespaces means working with each silo’s specific API, while working with Plan 9 namespaces meant working with files. The uniformity that was Plan 9’s central commitment has not been recovered.

9P

The protocol that connected the parts was 9P. 9P is a small wire protocol for file system operations: a client sends requests to a server, the server responds, and the operations are the standard ones — attach (open a connection), walk (navigate to a path), open (open a file for reading or writing), read, write, clunk (close), and a few others. 9P is small enough that a complete implementation fits in a few thousand lines of code. The protocol was redesigned in 2000 as 9P2000 (also called “Styx” in the Inferno operating system) to clean up some early-version messiness, and the 2000 version is what continues to be used.

The simplicity of 9P is a feature, in the same way that the simplicity of HTTP is a feature: a simple protocol can be implemented in many languages on many platforms by many developers, and a wide variety of clients and servers can interoperate. 9P has been implemented in C, Go, Rust, Python, Ruby, and many other languages, both as client and as server, with many of the implementations being short enough to read in an afternoon. The protocol’s elegance is one of the reasons it has had quiet influence well beyond Plan 9’s user base.

The other reason is that 9P solves a problem nobody else has solved as cleanly. The problem is: how do you expose a service to remote clients in a way that lets them use the service as if it were local? HTTP solves this by mapping operations to URLs and returning serialized data; the client has to parse the data and interpret it according to the application protocol. RPC frameworks solve it by defining application-specific procedures that clients invoke; each application gets its own protocol. 9P solves it by exposing the service as a file system that clients mount and interact with using ordinary file operations. The client does not need to know anything about the service except how to read and write files. A new service can be added to a network and clients can use it without any new code.

This pattern is the deep insight Plan 9 contributed. The web’s REST architecture, articulated by Roy Fielding in his 2000 PhD dissertation and treated more fully in chapter fifteen, is in some respects a less-rigorous restatement of the same idea: resources are addressable, operations on resources are a small fixed set, and clients can interact with new services using the standard operations. REST is HTTP-based, with all of HTTP’s specific design choices; 9P is file-system-based, with the file system’s specific design choices. The underlying architectural insight is the same.

UTF-8

A side product of Plan 9’s development is one of the few unambiguous wins to come out of the project: UTF-8, the variable-width encoding of Unicode that has become the default text encoding for almost everything on the internet. UTF-8 was designed by Rob Pike and Ken Thompson on a placemat at a New Jersey diner in September 1992. Plan 9 needed an encoding that could represent Unicode characters in byte streams without breaking compatibility with ASCII-only software. The constraints — ASCII compatibility, self-synchronization, no embedded null bytes, byte-stream-friendly — led to the design that Pike and Thompson worked out over dinner. UTF-8 was first implemented in Plan 9, used as the default encoding from the start, and submitted as an internet standard in RFC 2044 (1996) and refined in subsequent RFCs.

UTF-8 is now the default text encoding for HTML, for JSON, for source code in most modern languages, for filenames on most modern file systems, for protocols across most of the modern internet. It is the most successful standard ever to come out of Plan 9. It is also a piece of the Plan 9 design philosophy — make the simple cases simple, make the complex cases possible, prefer self-describing formats — that has been absorbed without the rest of the design.

Why Plan 9 did not win as a system

Plan 9 was used at Bell Labs, was deployed at a handful of other research institutions, and acquired a small community of dedicated outside users. It did not, however, become a mass-market operating system, and was never really intended to. Bell Labs had been broken up and restructured several times by the mid-1990s; the funding model that had supported the Computing Sciences Research Center was no longer producing the kind of patient long-term research support that had let Unix grow through the 1970s. Plan 9 was developed in a research lab that was, by the mid-1990s, increasingly under pressure to produce commercial results. The system was released, but Bell Labs (by then Lucent) did not put serious marketing behind it. Most computer users in the 1990s wanted Windows or a Unix workstation, not an experimental research operating system.

There were also real technical obstacles to adoption. Plan 9’s “everything is a file” doctrine, while elegant, was incompatible with the existing Unix software ecosystem. Programs that had been written to use sockets, ioctl, and the rest of the Unix system-call repertoire did not work on Plan 9; they had to be rewritten to use file operations. The Plan 9 team rewrote much of the standard Unix toolkit for their system, including new versions of the shell (rc), the editor (sam, then later acme), the window system (8½, then rio), and many of the standard utilities, but the result was a system that could not run Unix software directly. For users with substantial existing Unix software investments, this was prohibitive.

The system also chose, deliberately, several non-Unix conventions that made transition harder. The Plan 9 build system, the Plan 9 file format conventions, the Plan 9 command names — many of these were different from Unix in small ways. The cumulative effect was that adopting Plan 9 required adopting a different software ecosystem, and the ecosystem was much smaller than Unix’s. Plan 9 was, in this sense, the high-purity option, and the high-purity option is rarely the winning option in software ecosystems.

What got absorbed and what didn’t

Plan 9’s quiet influence on subsequent computing has been substantial. Several specific contributions have been adopted, usually without credit:

UTF-8, as discussed above, has become the universal text encoding.

Per-process namespaces have been recovered in Linux’s namespace mechanisms, Docker, and Kubernetes, in siloed form that does not match Plan 9’s uniformity but is structurally derived from the same insight.

The /proc filesystem, exposing kernel and process state as readable files, was adopted into Linux and most other Unix systems through the 1990s. Plan 9 had it first, in the most thoroughgoing form; Linux has a subset that has nevertheless been useful enough to become a standard way of inspecting system state.

The FUSE (Filesystem in Userspace) framework, developed in the early 2000s, lets user-space programs implement file systems that the kernel can mount. FUSE is, in effect, a way to expose arbitrary user-space services as file systems, which is what Plan 9 had been doing natively. FUSE-mounted file systems for SSH, for cloud storage, for archived data, for custom applications, are now common on Linux and macOS.

The Go programming language, designed by Pike and Thompson at Google starting in 2007, carries forward many Plan 9 design sensibilities — the small standard library, the emphasis on composition over inheritance, the goroutine concurrency model, the emphasis on plain text and concise syntax. Go is the most direct programming-language descendant of Plan 9, and several Plan 9 alumni (Pike, Thompson, Russ Cox, Dave Presotto) have been involved in its development.

The 9P protocol itself has been used in several places outside Plan 9. The Linux kernel’s VirtFS (the v9fs module) implements 9P as a shared-folder protocol for virtualization, used by QEMU/KVM and the WSL on Windows. The protocol’s simplicity makes it a good fit for VM-to-host communication where minimizing protocol overhead matters.

What has not been absorbed is the unification: the proposition that the operating system, the network, and the user environment should be a single coherent system in which every resource is uniformly addressable through one abstraction. Linux is a Unix variant; Windows is a Windows variant; macOS is a Mac variant; the cloud is a layered cake of various technologies. The unified namespace Plan 9 proposed has not arrived. Each layer of the contemporary stack has its own abstractions, its own protocols, its own conventions, and the user must navigate between them.

What Plan 9 has to say to the web

The relevance of Plan 9 to the broader argument of this book is structural. Plan 9 proposed that the network should be transparent: that there should be no meaningful distinction, at the architectural level, between a resource on your local machine and a resource on a remote machine. The web’s distinction is sharp: local resources are accessed through the operating system’s file API, remote resources are accessed through HTTP and a browser. Plan 9’s distinction was thin: both kinds of resources are accessed through the file system, and the protocol underneath is the same. A document on your local disk and a document on a server in Tokyo are, in Plan 9, the same kind of thing — a file you read with the same call. In the web’s world, they are different kinds of things accessed through different mechanisms, with the differences being visible at every layer of the stack.

The web’s choice has scaling and engineering benefits. The local file and the remote document have to behave differently in many cases — caching, error handling, security, performance — and pretending they are the same kind of thing puts the burden of distinction on the application instead of the operating system. The web’s approach lets each application handle the distinction in the way most appropriate for the application. Plan 9’s approach put the burden on the operating system, and the operating system did not always have enough information to do the right thing.

But Plan 9’s choice has costs the web’s choice does not have. The web’s distinction between local and remote has become, over the decades, a wall: files on your computer cannot easily be linked to from the web; web resources cannot easily be embedded in your local desktop environment; the two worlds run in parallel with limited interchange. A Plan 9 system that mounted remote resources into your local namespace did not have this wall; a remote file behaved like a local file, with caching and error handling delegated to the file system. The recoveries in Part V — local-first software, syncing systems, the various attempts to give web users persistent local data — are in some sense recoveries of what Plan 9 had been doing as a matter of course.

What we kept and what we didn’t

What we kept from Plan 9 is a vocabulary: UTF-8, /proc, FUSE, namespaces, the Go language, the 9P protocol’s use in specific niches. The vocabulary has been useful; the elements have been absorbed into systems that have themselves been useful.

What we did not keep is the unification. The proposal that an operating system should be a single coherent design, with one abstraction (the file) doing the work of many, with the network transparent, with each process having its own view, with every resource on every machine addressable through the same call — that proposal has not been recovered at scale. The closest contemporary system, in spirit, is the way Kubernetes structures cluster resources, with everything addressed through declarative APIs that look superficially like file systems. The resemblance is shallow; Kubernetes is not Plan 9, and the Kubernetes APIs are application-specific in a way Plan 9’s file system was not.

Plan 9 is the system in this book where the loss is hardest to feel. Most users have no direct experience of what a unified namespace operating system would feel like, because nobody has shipped one. The loss is theoretical; it is the loss of an architectural alternative that the people building Plan 9 had built and used and found good, and that the rest of us, working in the operating systems we have, do not even know we are not using. The chapter is here partly to say: this alternative existed. The people who built it were among the best operating-system designers in the history of the field. They built a working system, ran it for a decade, and made an architectural argument that has been quietly absorbed in pieces and has never been recovered as a whole. The argument is still there. The pieces are still there. The system is still there, in 9front and in plan9port and in the small communities that keep it alive. The recovery, if it happens, will be a recovery of attention as much as a recovery of code.

The five-chapter survey of internet alternatives ends here. The next part turns to the system that beat them. Part IV is about how the web won — the technical decisions, the commercial inflection, the standards politics, the accidental dominance of JavaScript, and the platform decade. The web’s victory was real and is worth understanding on its own terms, separately from the alternatives it displaced. The four chapters in Part IV try to do that.