Distributed Operating Systems and DSM

You have a rack of machines. The dream — old, beautiful, and mostly defeated — is that they should not feel like a rack of machines. They should feel like one big computer: a single pool of CPUs, one memory, one file system, one process space, where you launch a program and the operating system transparently runs it wherever there is room, and it can touch any memory and any file as if everything were local. This is the single-system-image (SSI) distributed operating system, and in the 1980s it looked like the obvious future.

It is not how we build datacenters today. We build them out of a pile of ordinary machines, each running its own boring Linux, glued together by middlewareRPC, message queues, Kubernetes — that runs above the OS, not inside it. This lesson is about that beautiful defeat: the real distributed OSes (Amoeba, Sprite, Plan 9), the seductive-but-doomed idea of distributed shared memory, why the transparent single-system dream lost to clusters, and how — the twist — nearly every idea survived and won somewhere else.

Three attempts at one computer

The classic distributed OSes each attacked the dream from a different direction, and each left a durable idea behind:

Notice the common move: take an OS abstraction you already trust locally — a process, a file — and stretch it transparently across the network. The question is always the same: what breaks when "local" silently becomes "remote"?

Distributed shared memory — the most seductive idea

Of all the stretches, the boldest is distributed shared memory (DSM): make the physical memory of many machines look like one shared address space, so a program on any node can just read and write \texttt{x} and the OS makes it coherent across the network — no explicit messages, no RPC, just load and store. Ivy (Kai Li, 1986) showed how, and the trick is gorgeous: reuse the paging hardware you already have.

Each shared page lives on some owner node. When a node touches a page it doesn't hold, its MMU raises an ordinary page fault — but instead of reading from a local disk, the fault handler fetches the page from the owning node over the network. Reads can be replicated (many read-only copies); a write must invalidate all other copies and take ownership — exactly the invalidation protocol of a cache-coherent multiprocessor, but with the "cache line" being a 4 KB page and the "bus" being an Ethernet. Watch one read fault travel:

Where DSM hurts: false sharing at 4 KB, and the consistency tax

DSM is transparent, which is precisely its curse — the programmer cannot see the network cost hiding under an innocent \texttt{x = x + 1}. Two problems dominate:

Page ownership, simulated

Model a single-writer/multiple-reader DSM page as a sequence of accesses from different nodes. A read by a non-holder fetches a copy; a write invalidates every other copy and seizes ownership. Count the network transfers — and watch two nodes write-fighting turn into a ping-pong.

// Single-writer / multiple-reader DSM for ONE page. // reads add a read-only copy; a write invalidates all other copies and takes the (sole) writable copy. type Access = { node: string; write: boolean }; let owner = "B"; // node B starts as the writable owner let readers = new Set<string>(["B"]); // nodes holding a valid copy let transfers = 0; const trace: Access[] = [ { node: "A", write: false }, // A reads -> fetch copy from owner { node: "C", write: false }, // C reads -> fetch copy { node: "A", write: true }, // A writes -> invalidate B,C ; A becomes owner { node: "B", write: true }, // B writes -> invalidate A ; B becomes owner (ping!) { node: "A", write: true }, // A writes -> invalidate B ; A becomes owner (pong!) ]; for (const a of trace) { if (a.write) { const invalidated = [...readers].filter((n) => n !== a.node); transfers += invalidated.length + (readers.has(a.node) ? 0 : 1); readers = new Set([a.node]); owner = a.node; console.log(`${a.node} WRITE -> invalidate [${invalidated.join(",") || "-"}], ${a.node} now sole owner`); } else { if (!readers.has(a.node)) { transfers++; readers.add(a.node); } console.log(`${a.node} READ -> copies held by {${[...readers].sort().join(",")}}`); } } console.log(`\ntotal network page transfers = ${transfers}`); console.log("note: the A/B write-fight ping-pongs the page every time — that is false-sharing pain.");

Why the dream lost — and where every idea won anyway

Transparent single-system-image OSes and DSM never took over the datacenter. The reasons are sobering and worth stating plainly, because they are a lesson about abstraction itself:

And yet — the ideas are everywhere, just moved up or sideways:

Old distributed-OS ideaWhere it lives now
Plan 9's "everything is a file" + per-process namespacesLinux namespaces & bind mounts — the machinery under containers; \texttt{/proc}, \texttt{/sys}; the 9P protocol still shipping (WSL2, QEMU, Docker)
Sprite process migrationlive VM/container migration; datacenter schedulers rebalancing load
Amoeba processor poolcluster schedulers (Borg, Kubernetes, Mesos): you submit work, not a target machine
Distributed shared memoryRDMA (read remote memory in ~1 µs) and memory disaggregation — DSM's dream on hardware that finally makes it pay

The single-system OS lost the battle and won the war: we didn't build one giant transparent computer, but we plundered its ideas for the cluster we built instead. RDMA in particular is DSM reincarnated — remote memory at microsecond, not millisecond, latency — which is why "the datacenter as a computer" is the theme of this module's final lesson.

Plan 9 came from the very people who wrote Unix, and by almost every measure it is cleaner: one protocol (9P) for all resources, genuinely composable per-process namespaces, no exceptions bolted on the side. It should have won. It didn't — and the reason is one of computing's hardest laws: incumbency beats elegance. By the time Plan 9 matured (early 1990s) Unix already had the applications, the drivers, the users, and the momentum; "better, but incompatible, and you must rewrite everything" is a losing pitch no matter how good the "better" is. So Plan 9 became the most influential OS almost nobody runs: its ideas were quietly strip-mined into everyone else's systems — \texttt{/proc}, UTF-8 (invented for Plan 9, by Thompson and Pike, on a diner placemat), union mounts, namespaces — while the OS itself stayed a research curiosity. Elegance is portable even when the system is not.

The tempting misread of DSM is "great, now I can ignore the network and just use shared variables." That is exactly backwards. DSM does not remove the network cost; it conceals it behind ordinary loads and stores, so you can no longer see where you are paying it. An explicit RPC at least announces "this is a remote call, it is slow and can fail." A DSM \texttt{x = x + 1} looks free but may trigger a 4 KB page fetch, an invalidation storm, or a false-sharing ping-pong — silently, millions of times worse than the same line on one machine. The lesson generalises far beyond DSM: a leaky abstraction that hides a cost gradient (local vs remote, cached vs uncached, memory vs disk) is dangerous precisely because it is convenient. Transparency is not free; someone always pays the latency, and if you can't see it, you can't fix it.