Distributed shared memory failed; the abstraction that won comes from programming languages, not the OS.
48.5 Remote Procedure Call (RPC)
A remote procedure call remote procedure call RPC: the dominant abstraction for building distributed systems, borrowed from programming languages. It makes executing a routine on a remote machine look almost like an ordinary local function call โ the client calls a stub, and the RPC system hides the messaging, argument marshaling, and timeout/retry needed to run the code remotely and return the result. defined in ch. 48 โ open in glossary (RPC) has one goal: make running code on a remote machine as simple as calling a local function. The server exports some routines; the client calls them; the RPC system handles all the messaging magic in between. That magic has two pieces: a stub generator and a run-time library.
The stub generator
The stub generator stub generator The RPC 'protocol compiler' that takes an interface definition and generates the glue code: a client stub (which marshals the arguments into a message, sends it, waits, and unmarshals the reply) and a server stub (which unmarshals the request, calls the real function, and marshals the reply). Automating this removes a class of hand-written packing bugs and can optimize the code. defined in ch. 48 โ open in glossary (or protocol compiler) takes an interface โ the calls the server wants to export โ and generates the glue code so nobody has to write fragile message-packing by hand:
The heart of a stub is marshaling marshaling Packing a function's arguments (or results) into a single contiguous message buffer so they can be sent across the network โ also called serialization. The inverse operation at the far end, extracting the values back out of the message, is called unmarshaling or deserialization. defined in ch. 48 โ open in glossary โ packing the function id and arguments into one contiguous message buffer (a.k.a. serialization); the far side unmarshals (deserializes) them back out:
Put the pieces in motion and a whole remote call looks like this:
The client has no idea a network is involved.
Two wrinkles the stub compiler must handle: complex arguments โ a raw pointer
(as in write(fd, buf, size)) means nothing on another machine, so RPC uses
well-known types (e.g. a buffer_t of a given size) or annotations to know which
bytes to serialize โ and server concurrency: a server that handles one
request at a time wastes resources whenever a call blocks, so real servers use a
thread pool, dispatching each request to a worker (paying the usual cost of
locks and synchronization).
The run-time library
The run-time library does the heavy lifting โ performance and reliability. A few of its jobs:
- Naming. The client must find the service โ commonly by hostname/IP + port number (and, above that, DNS). Full naming is a deep topic of its own.
- Transport choice. Reliable TCP seems the obvious pick, but itโs usually the wrong one:
| RPC on TCP (reliable) | RPC on UDP (unreliable) | |
|---|---|---|
| acknowledgments | TCP acks the request AND the reply | none from the transport โ confirmation rides on the natural request/response |
| who provides reliability | TCP does | the RPC layer adds its own timeout/retry + sequence numbers |
| semantics | reliable byte stream | exactly-once (no failure) or at-most-once (on failure) |
| verdict | simpler, but 2 extra messages per call | more efficient โ the common choice for RPC |
- Long-running calls. A slow remote call can look like a failure and trigger a needless retry; the run-time can send an explicit ack and let the client periodically ask โstill working?โ.
- Large arguments. Bigger than one packet? The run-time fragments on the sender and reassembles on the receiver.
- Byte order. Machines differ (big- vs little-endian), so RPC defines a canonical wire format โ Sun RPCโs XDR (eXternal Data Representation) โ and converts when a machineโs native order differs.
- Asynchrony. Normally an RPC is synchronous (the client waits). An asynchronous RPC returns immediately so the client can do other work, then later collects the result โ overlapping computation with communication.
Aside: The End-to-End Argument
The end-to-end argument end-to-end argument The design principle that certain functionality โ reliability being the classic example โ can only be implemented completely at the highest level of a system, the application 'at the end,' because lower-level guarantees never cover the whole path. Reliable network delivery, for instance, does not make a file transfer reliable if the bytes can be corrupted in memory or when written to disk; only an end-to-end check does. A corollary is that lower-level machinery can still be worth adding for performance. defined in ch. 48 โ open in glossary says certain functionality can only be fully implemented at the highest level โ the application โat the endโ. Take reliable file transfer: even if the network delivers every byte reliably (timeout/retry, acks, sequence numbers), the bytes could have been corrupted in the senderโs memory before transmission, or mangled when the receiver writes them to disk. Only an end-to-end check โ read the file back on the receiver, checksum it, and compare to the senderโs โ truly guarantees the transfer. The corollary: lower-level machinery can still be worth adding for performance, just not for the ultimate guarantee.48.6 Summary
Distributed systems introduce one dominant new problem โ failure, now a
commonplace event (โwith thousands of machines, failure is happening all the
timeโ). Their heart is communication, and its most common abstraction is
RPC, which hides timeout/retry, acknowledgments, marshaling, and byte-order
details behind something that closely mirrors a local procedure call. The best
way to really get it is to use one: Sunโs rpcgen, or modern takes like Googleโs
gRPC and Apache Thrift. Next, weโll see these ideas made concrete in two
real distributed file systems โ NFS and AFS.
Check yourself: remote procedure call
1.What is the goal of a remote procedure call (RPC) system?
2.What does the stub generator produce from an interface, and why is that useful?
3.What is marshaling (serialization) in an RPC call?
4.Why are many RPC packages built on UDP rather than the reliable TCP?
5.The end-to-end argument: why doesn't a reliable network layer alone make a file transfer reliable?
6.How do machines with different byte orderings (big- vs little-endian) exchange values in an RPC?