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):
| Mechanism | One-way latency | Bulk bandwidth | Sync needed? |
|---|---|---|---|
| pipe (anonymous) | ~1–2 µs | ~2–5 GB/s | no (kernel does) |
| Unix socket (stream) | ~2–4 µs | ~3–6 GB/s | no |
| TCP loopback | ~5–30 µs RTT | ~1–3 GB/s | no |
| POSIX message queue | ~1–3 µs | ~1–2 GB/s | no |
| shared memory + futex | ~0.2–1 µs | 10–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
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.