Inter-Process Communication (IPC)
Why Inter-Process Communication is Necessary
By design, every process operates within an isolated virtual address space. Process A cannot directly read or write memory belonging to Process B. However, complex systems require processes to collaborate (e.g., Producer-Consumer models). To facilitate this, the operating system kernel provides several mechanisms for Inter-Process Communication (IPC).
Overview of IPC Mechanisms
| Mechanism | Directionality | Kernel Mediation Required? | Primary Use Case |
|---|---|---|---|
| Pipe (Anonymous) | Unidirectional | Yes | Simple data streams between parent and child processes. |
| Named Pipe (FIFO) | Unidirectional | Yes | Stream communication between unrelated processes. |
| Message Queue | Bidirectional | Yes | Exchanging structured, typed messages. |
| Shared Memory | Bidirectional | No (Fastest) | Zero-copy exchange of massive datasets. |
| Semaphore | Synchronization | Yes | Controlling concurrent access to shared resources. |
| Signal | Asynchronous | Yes | Notifying a process that a specific event occurred. |
| Socket | Bidirectional | Yes | Communication across a network (or locally via UDS). |
Pipes
A Pipe is the most fundamental IPC mechanism. Under the hood, it is simply a bounded buffer allocated within kernel memory (typically 64KB). One end is opened for writing, the other for reading.
Parent Process Kernel Buffer Child Process
┌────────┐ ┌──────────────┐ ┌────────┐
│ │──write──▶│ ████████ │──read──▶ │ │
│ fd[1] │ │ Pipe Buffer │ │ fd[0] │
└────────┘ └──────────────┘ └────────┘
Write End Read End
Architectural Characteristics:
- Half-Duplex: Data flows in one direction only. (Bidirectional communication requires two pipes).
- Lineage Restriction: Anonymous pipes can only be used between processes with a common ancestor (typically a parent and a child created via
fork()). - Byte-Stream Oriented: Data is unstructured; there are no message boundaries.
When you use | in a Shell (e.g., ls | grep ".txt"), the Shell is literally invoking the pipe() system call to connect the stdout of the first process to the stdin of the second.
Named Pipes (FIFO)
A Named Pipe exists as a special file within the filesystem namespace. Because it has a path, unrelated processes can use it to communicate.
# Create a Named Pipe
mkfifo /tmp/my_pipe
# Terminal 1: Write into the pipe (Will block until a reader connects)
echo "hello" > /tmp/my_pipe
# Terminal 2: Read from the pipe
cat /tmp/my_pipe # Output: hello
Message Queues
A Message Queue is a linked list of messages maintained by the kernel. Processes can push and pull structured messages to and from the queue.
Process A Message Queue Process B
┌────────┐ ┌─────┬─────┬─────┬────┐ ┌────────┐
│ msgsnd │───▶│msg1 │msg2 │msg3 │ │───▶│ msgrcv │
└────────┘ └─────┴─────┴─────┴────┘ └────────┘
Each message has a 'type' and a payload ('data')
Key Differences from Pipes:
- Messages are typed. The receiver can selectively pull messages of a specific type.
- Messages have strict boundaries. This eliminates the "TCP sticky packet" problem inherent in byte streams.
- The queue's lifecycle is independent of the creating process. It persists in the kernel until explicitly deleted or the system reboots.
Shared Memory
Shared Memory is the absolute fastest IPC mechanism available. It completely bypasses the kernel during data transfer.
Process A Virtual Space Physical Memory Process B Virtual Space
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ │ │ │ │ │
│ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │
│ │ Shared │──┼──────┼▶│ Shared │◀─┼─────┼──│ Shared │ │
│ │ Region │ │ │ │ Page │ │ │ │ Region │ │
│ └────────┘ │ │ └────────┘ │ │ └────────┘ │
│ │ │ │ │ │
└──────────────┘ └──────────────┘ └──────────────┘
Both virtual addresses map to
the exact same physical frame
Why is it the fastest? Pipes and Message Queues require data to be copied from Process A's user space into kernel space, and then copied again from kernel space into Process B's user space (2 copies). Shared memory eliminates the kernel middleman, resulting in 0 copies.
The Catch: Because the kernel is no longer mediating access, the processes are entirely responsible for synchronizing concurrent reads and writes (usually via Semaphores). Failure to do so guarantees data races and corruption.
Semaphores
A Semaphore is essentially an atomic integer counter used to orchestrate access to shared resources. It does not transmit payload data; it transmits state.
Semaphore S = 3 (3 resources available)
Process A: P(S) → S=2, Access Granted
Process B: P(S) → S=1, Access Granted
Process C: P(S) → S=0, Access Granted
Process D: P(S) → S=0, Blocked, entering sleep queue...
Process A: V(S) → S=1, Resource Released → Kernel wakes up Process D
POperation (Wait/Down): IfS > 0, decrementSand proceed. IfS == 0, block the process.VOperation (Signal/Up): IncrementS. If there are blocked processes, wake one of them up.
When the maximum value of a Semaphore is restricted to 1, it functions as a Mutex (Mutual Exclusion lock).
Signals
Signals provide an asynchronous notification mechanism. They act as software interrupts. Like a push notification on a phone—no matter what the process is executing, the signal interrupts the normal flow to invoke a signal handler.
| Signal | POSIX Number | Default Action | Trigger / Description |
|---|---|---|---|
SIGTERM |
15 | Terminate | A polite request to exit. Can be caught and handled gracefully. |
SIGKILL |
9 | Terminate | Forceful assassination by the kernel. Cannot be caught or ignored. |
SIGINT |
2 | Terminate | Triggered by pressing Ctrl+C in the terminal. |
SIGSEGV |
11 | Core Dump | Segmentation Fault (Illegal memory access). |
SIGCHLD |
17 | Ignore | Sent to parent when a child process stops or terminates. |
SIGSTOP |
19 | Stop | Suspends the process. Cannot be caught or ignored. |
SIGCONT |
18 | Continue | Resumes a process suspended by SIGSTOP. |
Sockets
Sockets are the most versatile IPC mechanism. They not only facilitate communication between processes on the same machine but are the foundation of cross-network communication.
- TCP/UDP Sockets: Route traffic through the full network stack (IP/TCP) to reach processes on remote machines.
- Unix Domain Sockets (UDS): Designed strictly for local, intra-machine communication. They bypass the TCP/IP stack (no checksums, no routing headers) making them significantly faster and more secure than utilizing
localhost(loopback).
System Design Audit & Observability
Choosing the wrong IPC mechanism will bottleneck system throughput or introduce impossible-to-debug race conditions.
1. The IPC Throughput Bottleneck
When passing large payloads (e.g., passing a 4K video frame from an AI inference process to an encoding process), using Pipes or Sockets will destroy CPU performance due to memory copy overhead.
- Audit Protocol: If payload size exceeds L3 cache bounds, you must use Shared Memory. However, because shared memory lacks inherent synchronization, you must pair it with a ring buffer architecture and atomic Semaphores to coordinate the read/write pointers.
2. Unix Domain Sockets vs. Loopback TCP
Microservices deployed on the same physical host (or within the same Kubernetes Pod) often communicate via localhost:8080. This wastes kernel cycles wrapping and unwrapping TCP/IP headers for data that never leaves the motherboard.
- Audit Protocol: If two services are guaranteed to be co-located (e.g., an Envoy sidecar and a web app), they should communicate over a Unix Domain Socket (
.sockfile). This yields a measurable reduction in CPU load and latency.
3. Graceful Degradation via SIGTERM
When a container orchestrator (like Kubernetes) scales down a pod, it sends a SIGTERM. If the process doesn't exit within a grace period (default 30s), it sends a SIGKILL.
- Audit Protocol: Ensure your application registers a signal handler for
SIGTERM. Upon receipt, the application must immediately stop accepting new connections, flush pending I/O, complete in-flight transactions, and then exit cleanly. Hard-crashing onSIGKILLguarantees dropped user requests and corrupted state.