Inter-Process Communication — Pipes, Sockets & Shared Memory

Each process has its own virtual address space. Process A cannot read Process B's variables directly — the OS enforces that boundary. But processes constantly need to cooperate: a shell connects commands with pipes, a web server spawns workers that share a cache, a daemon notifies clients of events. That cooperation requires Inter-Process Communication (IPC).

UNIX and Linux provide a rich set of IPC mechanisms. Each has different trade-offs in speed, complexity, and use case. This article covers all of them.

Why Processes Can't Share Memory Directly

When the OS creates a process, it gets its own page table. Two processes with a variable at virtual address 0x7fff1234 are looking at different physical frames. Writing to one does not affect the other.

This isolation is a feature — it's why a crashing process doesn't take down the whole system. IPC mechanisms are the deliberate, controlled exceptions to that rule.

Pipes — Anonymous, Unidirectional

A pipe is the simplest IPC mechanism. It's a kernel-managed buffer (typically 64 KB on Linux) with a write end and a read end. Data flows in one direction: write end → buffer → read end.

Process A(writer)Process B(reader)Kernel pipe bufferdata flows left to right →write()read()

Key properties:

  • Unidirectional — for bidirectional comms you need two pipes
  • Anonymous — no name in the filesystem; only inherited by related processes (parent/child)
  • Blocking — reading from an empty pipe blocks; writing to a full pipe blocks
  • Automatic EOF — when all write ends are closed, read returns 0

Shell Pipes

When you type ls | grep foo, the shell:

  1. Creates a pipe
  2. Forks two children
  3. Connects ls's stdout to the pipe's write end (dup2)
  4. Connects grep's stdin to the pipe's read end
  5. Execs ls and grep in the two children

Python: subprocess.PIPE

python
Loading...
python
Loading...
python
Loading...

Named Pipes (FIFOs)

Anonymous pipes only work between related processes (parent/child). A named pipe (FIFO) is a pipe with a filename in the filesystem. Any two processes — completely unrelated — can open it by path.

bash
Loading...
python
Loading...

FIFOs appear in ls -l with a p type flag. They behave like pipes once opened — kernel-buffered, unidirectional, blocking. The file path is just a rendezvous point; no data is stored on disk.

Message Queues

Pipes pass a raw byte stream — the receiver has no idea where one message ends and the next begins. Message queues deliver discrete, typed messages. The receiver gets one complete message per receive call.

POSIX message queues (mq_open, mq_send, mq_receive) are available on Linux. Python's multiprocessing.Queue wraps this with a friendlier API.

python
Loading...

multiprocessing.Queue serialises Python objects with pickle, puts them into a pipe, and handles the framing for you. For high-throughput work between many workers, see multiprocessing.JoinableQueue which supports task_done() / join() for drain-and-wait patterns.

Shared Memory — The Fastest IPC

All IPC mechanisms so far copy data: write it into a kernel buffer, then copy it out to the reader. Shared memory eliminates the copies entirely. A region of physical RAM is mapped into the virtual address space of two or more processes. One process writes to a virtual address; the other process can read it immediately — zero kernel involvement after setup.

Process Avirtual: 0x7f00_0000Process Bvirtual: 0x7f80_0000Physical RAM pagesame physical frame

This makes shared memory the fastest IPC mechanism — throughput limited only by memory bandwidth. The catch: nothing prevents both processes from writing simultaneously. You must use semaphores or mutexes to coordinate access.

Python: multiprocessing.shared_memory (3.8+)

python
Loading...

Python: mmap for shared memory

python
Loading...

Signals — Asynchronous Notifications

Signals are the UNIX mechanism for asynchronous notification. They are not for transferring data — a signal is just a number that says "something happened."

SignalNumberDefault actionMeaning
SIGTERM15TerminatePolite shutdown request
SIGKILL9Terminate (uncatchable)Immediate kill
SIGINT2TerminateCtrl+C from terminal
SIGCHLD17IgnoreChild process state changed
SIGALRM14TerminateTimer expired
SIGUSR110TerminateUser-defined signal 1
SIGUSR212TerminateUser-defined signal 2
SIGHUP1TerminateTerminal closed (often: reload config)
SIGSEGV11Core dumpSegmentation fault

SIGKILL and SIGSTOP cannot be caught or ignored — the kernel handles them directly.

python
Loading...

Run this, then from another terminal: kill -USR1 <pid> or kill -TERM <pid>.

Signals have important limitations: you can't queue multiple signals of the same type (a second SIGUSR1 before the first is handled may be lost), and signal handlers run in a restricted context (not all functions are async-signal-safe).

Unix Domain Sockets vs TCP Sockets for Local IPC

Sockets are the most flexible IPC mechanism — they support bidirectional, full-duplex, stream or datagram communication. For local IPC you have two options:

Unix domain sockets (AF_UNIX)

  • Address is a filesystem path (/tmp/app.sock)
  • Data stays in kernel — no TCP/IP stack overhead
  • ~2× faster than loopback TCP for high-throughput local IPC
  • Supports passing file descriptors between processes (SCM_RIGHTS)

TCP loopback (AF_INET to 127.0.0.1)

  • Goes through the TCP/IP stack
  • Easy to extend to remote machines later
  • Works across containers / VMs; Unix sockets do not cross filesystem boundaries easily
python
Loading...

Comparison — All IPC Mechanisms

MechanismSpeedComplexityDirectionPersistenceBest for
Anonymous pipeMediumLowOne-wayNoneParent ↔ child data stream
Named pipe (FIFO)MediumLowOne-wayPath existsUnrelated processes, simple
Message queueMediumMediumBoth waysUntil deletedDiscrete jobs, task queues
Shared memoryVery fastHigh (needs sync)Both waysUntil unlinkedHigh-throughput, large data
SignalMinimalLowOne-wayNoneAsync events, notifications
Unix domain socketFastMediumFull-duplexUntil unlinkedGeneral local IPC
TCP loopbackModerateMediumFull-duplexNoneCross-host extensible IPC

Choosing the Right Mechanism

  • One command to another in a shell script → pipe
  • Daemon that receives jobs from multiple clients → Unix domain socket or message queue
  • High-throughput data sharing between workers → shared memory + semaphore
  • Notify a running process to reload its config → SIGUSR1
  • Microservices on the same host → Unix domain socket (faster) or TCP (portable)
  • Worker pool that processes tasksmultiprocessing.Queue

IPC is not a single tool — it's a family of tools. The right choice depends on whether you need data transfer or notification, how much data, whether you need synchronisation, and whether the processes are related or completely independent.