Breaking the Copy Barrier
Every IPC mechanism we have discussed so far — pipes, sockets — involves at least one kernel-mediated data copy. Process A writes data into a kernel buffer; the kernel copies it into Process B's buffer. For most applications, this is fine. But for workloads that move large volumes of data at high frequency — video processing, high-frequency trading, scientific simulation, inter-process message queues — even a single copy is too expensive.
Shared Memory eliminates the copy entirely. The OS takes a region of physical RAM and maps it into the virtual address space of two (or more) processes simultaneously. Each process sees the shared region at a different virtual address, but both virtual addresses point to the same physical memory pages. When Process A writes to its virtual address 0xA000, Process B instantly sees the change at its virtual address 0xB000 — no system call, no copy, no kernel involvement in the data transfer itself.
Physical RAM:
┌─────────────────────────────┐
│ Page Frame 0x1234 (4KB) │ ← Shared physical page
└─────────────────────────────┘
↑ ↑
Process A's page table Process B's page table
Virtual addr: 0xA000 Virtual addr: 0xB000
On Linux, you create shared memory segments using either the POSIX API (shm_open + mmap) or the older System V API (shmget + shmat). The POSIX API is preferred for new code, and its segments show up as ordinary-looking files under /dev/shm — a tmpfs-backed directory rather than real disk storage.
Many high-performance systems use shared memory as their primary data bus. The LMAX Disruptor (a high-performance inter-thread queue used in financial systems) is built on this principle. Redis can use shared memory for its fork-based persistence. Chrome uses shared memory to pass rendered frames between the renderer process and the browser process.
The Danger Zone: Race Conditions
Shared memory is the fastest IPC mechanism, but it is also the most dangerous. With pipes and sockets, the kernel serializes access — only one process writes at a time, and the data arrives in order. With shared memory, the kernel steps back entirely. If Process A writes while Process B reads the same memory location, you get a race condition: Process B may read a partially-written value, a mix of old and new data, or garbage.
You must use synchronization primitives to coordinate access:
- Mutex (Mutual Exclusion Lock): Only one process holds the lock at a time. Others block until it is released. Use for protecting a critical section.
- Semaphore: A counter that processes increment and decrement. Use for signaling (e.g., "N items are ready to consume").
- Futex (Fast Userspace Mutex): Linux's low-level primitive. Avoids a system call in the uncontended case — the lock/unlock is a userspace atomic operation. Only falls into the kernel when there is actual contention.
pthreadsmutexes are implemented on top of futexes.
The pattern is always: lock → read/write shared memory → unlock. Forgetting the lock, even once, causes data corruption that is extremely difficult to reproduce and debug.