Free — no signup required

Shared Memory — The Zero-Copy Fast Path

3 min read

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. pthreads mutexes 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.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free