The Runtime Theory
Operating Systems

Interprocess Communication Compared: Pipes, Sockets, Shared Memory

Bandwidth, latency, and security tradeoffs of pipes, Unix sockets, TCP loopback, shared memory, signals, and message queues — with real numbers and selection guidance.

The Runtime Theory Team3 min read#ipc#pipes#sockets#shared-memory#signals
On this page

Processes cannot touch each other's memory — that is the point of them. Everything called IPC is engineering around that wall: the kernel copies data on your behalf, or you tear the wall down for one shared region and handle synchronization yourself. The right choice is a tradeoff between bandwidth, latency, and security, and the numbers decide.

The mechanisms and their numbers

Typical measurements on modern x86 Linux (one-way latency for a small message, throughput for bulk data):

MechanismOne-way latencyBulk bandwidthSync needed?
pipe (anonymous)~1–2 µs~2–5 GB/sno (kernel does)
Unix socket (stream)~2–4 µs~3–6 GB/sno
TCP loopback~5–30 µs RTT~1–3 GB/sno
POSIX message queue~1–3 µs~1–2 GB/sno
shared memory + futex~0.2–1 µs10–30 GB/s (memory-bound)yes
signal (RT, with payload)~1–2 µs~n/a (32-bit payload)no

The pattern: every kernel-copy channel costs a couple of microseconds and caps bandwidth near single-digit GB/s. Shared memory skips the kernel entirely and pays in coordination responsibility.

Pipes: the baseline

An anonymous pipe is a kernel ring buffer (64 KB by default) with two fds: one write end, one read end. A write copies user → kernel; a read copies kernel → user; when the buffer is full the writer blocks — that flow control is the whole contract. Latency is ~1–2 µs, throughput GB/s-scale.

Security is the quiet advantage: a pipe has no name, no path, no namespace — it cannot be opened by any process that did not inherit an end. The most restrictive channel in the comparison, and the only one with built-in backpressure. FIFOs (named pipes) trade that anonymity for a filesystem path and its permissions.

Unix sockets: the workhorse

Unix domain sockets are bidirectional, behave like sockets (select/poll/epoll-able, async), and add two superpowers: fd passing (SCM_RIGHTS — hand a socket or file to another process without re-opening) and credentials passing (SO_PASSCRED). Latency ~2–4 µs, bandwidth ~3–6 GB/s. This is why systemd, DBus, and most modern daemon protocols use them: systemd sockets are Unix sockets by default.

Access control is filesystem permissions on the socket path — chmod the socket and you've filtered clients. The abstract namespace (@name) skips the filesystem but also skips permission checks beyond the containing user.

TCP loopback: networking semantics for free

TCP over loopback runs the full stack — checksums, retransmission, congestion control — for a connection that never leaves the machine: 5–30 µs RTT and CPU-heavy. Its one virtue: the code is identical when the peer moves to another host. That's also its trap — a service that "works fine locally" via loopback teaches you nothing about real network behavior, and loopback TCP is subject to routing quirks (e.g., packets leaving the box and returning).

Shared memory: the fast lane with no guardrails

shm_open() + ftruncate() + mmap(), or memfd_create() for a private, sealed region. After setup, reads and writes are ordinary memory access — zero kernel involvement, 10–30 GB/s, sub-microsecond.

The invoice: nobody arbitrates. Torn writes, stale reads, and lost wakeups are now your problem. Standard practice: a futex or atomic flag guards the region, a generation counter detects torn writers, and a monitor process cleans up after crashes (a crashed writer leaves a locked region behind — user-space memory does not die with its owner). False sharing is another hidden tax: two threads on different cores writing adjacent cache lines serialize on the cache line, quietly costing 100×.

Signals and message queues: niches

Signals are an event channel, not a data channel: standard signals carry no payload and coalesce — ten SIGUSR1s while one is pending deliver one. RT signals (sigqueue) carry a 32-bit value and queue, bounded by RLIMIT_SIGPENDING. Use them to poke a poller, not to move data.

POSIX message queues copy a message per mq_send()/mq_receive() syscall, like a pipe with priority ordering and no reader blocking. They rarely beat a pipe on performance and add queue-name/persistent-object management — the main argument is the priority semantics.

The selection matrix

text
Same host, streaming data, want backpressure?      → pipe or Unix socket
Same host, hot path, need throughput?              → shared memory + futex
Peer may move to another host later?               → TCP
Just need "wake up and check"?                     → signal or Unix socket datagram
Trust boundary between processes?                  → pipe (inherited fds only)

Security summary

Pipes are the safest (nothing to discover). Unix sockets inherit file permissions; shared memory is world-readable unless you set mode flags (memfd_create seals it to your process tree); TCP exposes you to the network stack and any local port scanner. And seccomp / SELinux policies routinely gate sockets and shm — an IPC "hang" is sometimes a policy denial, not a bug.