Inter-Process Communication (IPC) Architecture
Operating systems enforce strict address space isolation between processes to prevent accidental corruption and malicious tampering. Inter-Process Communication (IPC) provides the architectural mechanisms through which decoupled processes exchange data, synchronize state, and pass hardware resources across these isolation boundaries.
+-------------------------------------------------------------+
| The IPC Memory Copy Spectrum |
| |
| 1. Double-Copy (Pipes, FIFOs, UDS) |
| User A Buffer ---> Kernel Buffer ---> User B Buffer |
| |
| 2. Single-Copy (Android Binder, Mach OOL) |
| User A Buffer -----------> User B Mapped Address Space |
| |
| 3. Zero-Copy (Shared Memory: shm_open, memfd_create) |
| User A Pointer <=== Shared RAM Frame ===> User B Pointer|
+-------------------------------------------------------------+
The IPC Tradeoff Space: Isolation vs Throughput
Selecting an IPC mechanism involves balancing four architectural properties:
- Memory Copy Overhead: Number of times payload data traverses CPU caches and memory buses.
- Context Switching Overhead: Whether data transmission requires waking a thread via kernel scheduler privilege transitions.
- Synchronization Burden: Whether the kernel arbitrates concurrent access (serialized queues) or delegates concurrency management to userspace atomic locks.
- Capability & Security Passing: Ability to transfer credentials, cryptographic tokens, or open file descriptors across process boundaries.
IPC Data Transfer Mechanisms & Memory Boundaries
1. Double-Copy IPC: Pipes & Message Queues
The classic Unix communication primitive. Created via pipe() or pipe2(), an anonymous pipe allocates a circular memory buffer (typically 64 KiB on Linux) managed inside kernel space:
- Sender: Issues
write(pipe_fd[1], buf, len). The CPU copies data from the sender's user-space buffer into the kernel's circular buffer (Copy 1). - Receiver: Issues
read(pipe_fd[0], buf, len). The CPU copies data from the kernel buffer into the receiver's user-space buffer (Copy 2). - Characteristics:
- Unidirectional, serialized byte stream with built-in backpressure (sender blocks when the circular buffer fills; receiver blocks when empty).
- Simple, leak-proof lifecycle: buffer automatically deallocates when all file descriptors close.
- High CPU cache thrashing for multi-megabyte payloads due to double memory copying.
2. Single-Copy IPC: Android Binder & Mach Out-of-Line
To achieve high-frequency IPC without double-copy overhead, specialized kernels implement single-copy mechanisms:
- Android Binder:
- The receiver process maps a read-only memory region (
1 MB - 8 KBtransaction buffer) from the/dev/binderdriver into its address space during initialization (mmap()). - When a sender transmits a Parcel, the Binder kernel driver copies the payload directly from the sender's user memory into the receiver's pre-mapped buffer in a single
copy_from_useroperation.
- The receiver process maps a read-only memory region (
- Mach Out-of-Line (OOL) Memory:
- For large Mach messages on macOS/iOS, the kernel does not copy message bodies. Instead, it alters page table mappings, marking the pages Copy-on-Write (CoW) in the receiver's address space.
3. Zero-Copy IPC: POSIX Shared Memory & memfd_create
Zero-copy IPC maps the identical physical RAM page frames into the virtual address spaces of two or more distinct processes:
Process A Virtual Address Space Process B Virtual Address Space
+-------------------------------+ +-------------------------------+
| Address 0x7FFF0000 (PTE A) | | Address 0x7AAA0000 (PTE B) |
+-------------------------------+ +-------------------------------+
\ /
\ /
v v
+-----------------------------------+
| Shared Physical RAM Frame |
| (Page Frame Number) |
+-----------------------------------+
- Mechanisms:
shm_open()+mmap(): POSIX shared memory backed by/dev/shm(tmpfs).memfd_create(): Anonymous in-memory file descriptor with file sealing (F_ADD_SEALS,F_SEAL_SHRINK,F_SEAL_WRITE), preventing untrusted peers from truncating or altering memory layouts during concurrent parsing.
- Tradeoffs:
- Maximum Throughput: Data transmission speed is limited only by CPU memory bus bandwidth; zero kernel transitions during reads and writes.
- Concurrency Responsibility: The kernel does not arbitrate access. Applications must implement their own synchronization primitives across processes.
Capability & Descriptor Passing via SCM_RIGHTS
In Unix-like systems, file descriptors (int fd) are indices into a private, per-process file descriptor table. Passing integer 3 over a regular pipe to another process points to whatever file process B happens to have open at index 3, not process A's open file.
Unix Domain Sockets (AF_UNIX) resolve this using SCM_RIGHTS ancillary control messages (sendmsg() and recvmsg()):
Process A (Privileged Root) Process B (Unprivileged Sandbox)
Opens /etc/shadow -> fd = 4 Restricted sandbox (no root)
| ^
v |
sendmsg(sock_fd, &msg) recvmsg(sock_fd, &msg)
(ancillary data: SCM_RIGHTS = fd 4) |
| |
+-----------> Linux Kernel VFS -------------------+
- Resolves fd 4 to struct file*
- Increments file reference count (f_count++)
- Allocates lowest available slot in Process B table
- Injects new fd into Process B (e.g. fd = 7)
This capability transfer is the cornerstone of modern sandboxing (Chromium multi-process browser, Wayland compositors passing graphics buffers, and container runtime shims).
Kernel IPC Paradigms Across Operating Systems
| Architecture | Linux / Unix | Windows NT | macOS (XNU) | Android |
|---|---|---|---|---|
| Primary IPC | Unix Domain Sockets / Pipes | Advanced Local Procedure Call (ALPC) | Mach Ports / XPC | Binder |
| Object Addressing | Filesystem path or abstract socket namespace | Object Manager directory namespace | Port Rights (Capability numbers) | 32-bit Handle / Service Manager tokens |
| Descriptor Passing | SCM_RIGHTS via sendmsg |
DuplicateHandle() |
Mach Port Transfer (MACH_MSG_PORT_DESCRIPTOR) |
Binder BINDER_TYPE_FD object translation |
| Performance Model | Double-copy (sockets/pipes) or zero-copy (shm) |
Single-copy message or Section Object sharing | Port message queue (in-line or OOL virtual copy) | Single-copy shared kernel buffer |
| Crash Detection | POLLHUP / SIGPIPE on closed socket |
Disconnected port notification | Port Death Notification (MACH_NOTIFY_DEAD_NAME) |
Death Recipients (Kernel linked notification) |
1. macOS Mach Ports & XPC
Mach microkernel architecture routes all IPC through Mach Ports—unidirectional kernel-protected message queues:
- Port Rights: A process must hold explicit capabilities:
- Receive Right: Only one task can hold the receive right for a given port.
- Send Right: Multiple tasks can hold send rights to dispatch messages into the queue.
- Send-Once Right: Consumed immediately after transmitting one reply message.
- XPC Services: Apple's modern high-level IPC framework built on Mach ports with
launchdon-demand service activation, automatic serialization, and Sandboxing entitlement checks.
2. Windows NT: Advanced Local Procedure Call (ALPC)
Windows NT uses ALPC for high-speed local RPC communication between user-mode processes and system subsystems (e.g. lsass.exe, csrss.exe):
- Small Messages (< 64 KB): Copied through internal kernel ALPC message queues.
- Large Payloads: ALPC maps a shared Section Object (shared memory view) between caller and callee, passing metadata handles in the message header to avoid data copying.
3. Android: Binder Driver
Android rejected POSIX IPC in favor of the Binder driver (/dev/binder):
- Provides object-oriented remote procedure call interfaces (AIDL).
- Performs automatic process identity tracking (UID/PID injected securely by kernel, immune to spoofing).
- Implements Priority Inheritance: If an unprivileged background thread makes an IPC call to a critical system service thread, the receiving thread temporarily inherits the client's priority (or vice versa) to eliminate latency bottlenecks.
Cross-Process Synchronization: The futex Revolution
Early IPC synchronization required a kernel system call (semop(), read()) for every lock acquisition and release, costing hundreds of CPU cycles even when zero lock contention existed.
Ulrich Drepper and Ingo Molnar introduced the futex (Fast Userspace Mutex) in Linux 2.5.70:
Lock Acquisition (Fast Path - Zero Contention):
User code executes atomic Compare-And-Swap (atomic_cmpxchg):
- If lock == 0: Set lock = 1.
- Done! ZERO system calls, execution proceeds at memory speed (~2 ns).
Lock Contention (Slow Path - Collision):
- atomic_cmpxchg fails (lock already 1).
- Process executes futex(uaddr, FUTEX_WAIT, val, timeout).
- CPU enters Ring 0; kernel puts thread on wait-queue and halts execution.
Lock Release:
- Releasing thread atomizes lock to 0.
- If waiters exist: futex(uaddr, FUTEX_WAKE, 1) wakes the sleeping thread.
Modern cross-process synchronization on other operating systems incorporates identical split-path logic:
- Windows:
WaitOnAddress()/ SRW (Slim Reader/Writer) locks. - macOS:
os_unfair_lock/ Mach semaphore wait routines.

