Inter-Process Communication (IPC)
In an operating system, processes are isolated by the Virtual Memory Manager to prevent one process from corrupting another. However, complex systems (like Chrome, Nginx, or a Database) require these isolated units to cooperate. IPC is the set of mechanisms provided by the kernel to bridge this isolation.Mastery Level: Senior Systems Engineer
Key Internals: Kernel Ring Buffers, Page Table Aliasing, Signal Frames,
Prerequisites: Virtual Memory, Process Internals
Key Internals: Kernel Ring Buffers, Page Table Aliasing, Signal Frames,
rt_sigreturnPrerequisites: Virtual Memory, Process Internals
0. The Big Picture: Why So Many IPC Mechanisms?
Before diving into each mechanism, understand how they compare:| Mechanism | Data Model | Copy Overhead | Sync Built-in? | Best For |
|---|---|---|---|---|
| Pipe | Byte stream | 1 kernel copy | Yes (blocking) | Parent–child, simple streaming |
| Named Pipe (FIFO) | Byte stream | 1 kernel copy | Yes | Unrelated processes, simple streaming |
| Shared Memory | Raw bytes | Zero copy | No (bring your own) | High-throughput, low-latency bulk data |
| Message Queue | Discrete messages | 1 kernel copy | Yes (blocking, priority) | Structured messages, priority ordering |
| Unix Domain Socket | Stream or datagram | 1 kernel copy | Yes | High-perf local networking, FD passing |
| Signal | Integer only | N/A | N/A | Async events, not data |
| TCP/UDP Socket | Stream/datagram | 1 kernel copy | Yes | Cross-machine, network-transparent |
Decision Flowchart
- Need to pass file descriptors? → Unix Domain Socket (
SCM_RIGHTS). - Need zero-copy bulk transfer? → Shared Memory + your own synchronization.
- Need structured messages with priority? → POSIX Message Queue.
- Parent–child streaming? → Pipe.
- Unrelated processes, streaming? → Named Pipe or Unix Socket.
- Cross-machine? → TCP/UDP Socket.
- Async notification only? → Signal.
Understanding Process Isolation
Before diving into IPC mechanisms, let’s understand why processes need isolation and how the kernel enforces it.Virtual Memory Isolation
Each process operates in its own virtual address space:- Memory safety: Process A cannot corrupt Process B
- Security: Privileged data stays protected
- Stability: Crash in one process doesn’t affect others
- Predictability: Each process sees a consistent address space
1. Pipes: The Kernel Ring Buffer
Pipes are the oldest and most fundamental IPC mechanism in Unix. While they appear as simple file descriptors to user space, their internal implementation reveals sophisticated kernel buffer management.1.1 Pipe Fundamentals
A pipe is a unidirectional communication channel with:- Write end: One process writes data
- Read end: Another process reads data
- FIFO ordering: First In, First Out
- Byte stream: No message boundaries
pipefd[1] open, the read() call will never return 0 (EOF) because the kernel sees there’s still a potential writer.
1.2 Kernel Implementation Deep Dive
The Pipe Buffer Structure
In the Linux kernel, a pipe is implemented using a circular buffer structure (struct pipe_inode_info):
Write Operation Flow
When a process callswrite(pipefd[1], data, size):
1.3 Atomicity and PIPE_BUF
Critical Guarantee: Writes of size ≤PIPE_BUF (4096 bytes on Linux) are atomic.
What does atomic mean?
size <= PIPE_BUF:
1.4 Named Pipes (FIFOs)
Regular pipes only work between related processes (parent-child viafork()). Named pipes (FIFOs) allow unrelated processes to communicate.
S_IFIFO. The inode’s i_pipe field points to the same struct pipe_inode_info as regular pipes.
1.5 Pipe Performance Characteristics
- Throughput Test
- Latency Test
1.6 The Self-Pipe Trick (Advanced Pattern)
Problem: Signal handlers are asynchronous and severely limited in what they can do (async-signal-safe functions only). How do you integrate signals with an event loop? Solution: The self-pipe trick.- Signal handler executes in async context (can’t safely do much)
- Handler writes 1 byte to pipe (write is async-signal-safe)
- Main event loop wakes up from
poll() - Main loop reads signal number and handles it safely
- Signal handling is now integrated with other I/O events
2. Shared Memory: The Zero-Copy Holy Grail
Shared Memory is the fastest IPC mechanism because it completely eliminates kernel involvement in data transfer. Once set up, processes communicate at memory speed.2.1 The Fundamental Concept
Traditional IPC (pipe/socket) data flow:2.2 POSIX Shared Memory Implementation
2.3 The MMU Magic: Page Table Aliasing
How the kernel makes shared memory work:mmap(MAP_SHARED):
mmap(MAP_SHARED) on the same shared memory object:
2.4 System V Shared Memory (Legacy API)
IPC_RMID or system reboot. Use ipcs -m to list and ipcrm -m <shmid> to delete orphaned segments.
2.5 Synchronization: The Critical Challenge
Problem: Shared memory provides NO synchronization. Multiple processes accessing the same memory simultaneously will corrupt data.Race Condition Example
Correct Solution
2.6 Huge Pages for Shared Memory
For large shared memory regions (GB+), using huge pages (2MB or 1GB instead of 4KB) reduces TLB pressure and improves performance.- Regular 4KB pages: 1 GB = 262,144 page table entries
- 2MB huge pages: 1 GB = 512 page table entries
- TLB misses: Reduced by ~99%
3. Message Queues: Structured Communication
Message Queues provide message-oriented communication with built-in synchronization and priority handling.3.1 POSIX Message Queues
3.2 Kernel Implementation
POSIX message queues are implemented in the kernel as a priority-sorted list:3.3 Asynchronous Notification
Message queues support asynchronous notification via signals:3.4 Performance Comparison
- Message Queue vs Pipe
- When to Use Each
4. Signals: Asynchronous Interrupts
Signals are “software interrupts” that allow asynchronous notification of events.4.1 Signal Fundamentals
Standard Signals:| Signal | Number | Default | Description |
|---|---|---|---|
| SIGHUP | 1 | Term | Hangup (terminal closed) |
| SIGINT | 2 | Term | Interrupt (Ctrl+C) |
| SIGQUIT | 3 | Core | Quit (Ctrl+\) |
| SIGILL | 4 | Core | Illegal instruction |
| SIGTRAP | 5 | Core | Trace/breakpoint trap |
| SIGABRT | 6 | Core | Abort signal |
| SIGBUS | 7 | Core | Bus error |
| SIGFPE | 8 | Core | Floating-point exception |
| SIGKILL | 9 | Term | Cannot be caught |
| SIGSEGV | 11 | Core | Segmentation fault |
| SIGPIPE | 13 | Term | Broken pipe |
| SIGALRM | 14 | Term | Timer expired |
| SIGTERM | 15 | Term | Termination signal |
| SIGUSR1 | 10 | Term | User-defined 1 |
| SIGUSR2 | 12 | Term | User-defined 2 |
| SIGCHLD | 17 | Ign | Child stopped/terminated |
| SIGCONT | 18 | Cont | Continue if stopped |
| SIGSTOP | 19 | Stop | Cannot be caught |
4.2 Signal Handler Installation
4.3 The Signal Delivery Mechanism (Deep Dive)
What happens when Process B sends a signal to Process A?kernel/signal.c):
4.4 Async-Signal-Safety: The Critical Constraint
Problem: A signal can interrupt a process anywhere, including inside non-reentrant functions.4.5 Realtime Signals (SIGRTMIN - SIGRTMAX)
Realtime signals (32-64 on Linux) have additional features:- Queued: Multiple instances of the same signal are queued (standard signals are not)
- Ordered: Delivered in priority order (lower signal numbers first)
- Data: Can send an integer or pointer with the signal
4.6 Signal Masking and Blocking
5. Unix Domain Sockets: High-Speed Local IPC
Unix Domain Sockets (UDS) provide socket API semantics for local IPC with superior performance compared to network sockets.5.1 Stream Sockets (SOCK_STREAM)
5.2 Datagram Sockets (SOCK_DGRAM)
5.3 Passing File Descriptors (SCM_RIGHTS)
This is the “superpower” of Unix Domain Sockets - the ability to pass open file descriptors between processes.SCM_RIGHTS:
- Privilege separation: Privileged broker passes FDs to sandboxed workers (Chrome, systemd)
- Zero-copy: Pass a socket FD to another process for load balancing
- Capability-based security: Grant access to specific resources without filesystem permissions
5.4 Credentials Passing (SO_PEERCRED)
5.5 Abstract Namespace Sockets
Linux supports “abstract” Unix sockets that don’t exist in the filesystem:- No filesystem clutter
- No permission/ownership issues
- Automatically cleaned up on close
- No race conditions with unlink()
- Linux-specific (not portable)
- Can’t use filesystem permissions for access control
6. Performance Comparison & Selection Guide
6.1 Throughput Benchmark
- Benchmark Code
- Typical Results
6.2 Selection Decision Tree
7. Advanced Topics
7.1 splice() and vmsplice() - Zero-Copy Pipes
Linux providessplice() and vmsplice() for zero-copy operations:
7.2 memfd and File Sealing
memfd_create() creates anonymous file descriptors that can be shared and sealed:
8. Real-World Architecture Patterns
8.1 Chrome Multi-Process Architecture
8.2 systemd Socket Activation
- Zero-downtime restarts (systemd holds socket)
- On-demand activation
- Privilege separation (systemd binds privileged port, passes FD to unprivileged service)
9. Interview Questions & Answers
Q1: Explain the 'self-pipe trick' and why it's necessary.
Q1: Explain the 'self-pipe trick' and why it's necessary.
Problem: Signal handlers can interrupt a process anywhere, including inside non-reentrant functions like
malloc(). This severely limits what you can do in a signal handler.Solution: The self-pipe trick:- Create a pipe:
pipe(signal_pipe) - In signal handler:
write(signal_pipe[1], &sig, 1)(write is async-signal-safe) - Add
signal_pipe[0]to your event loop (epoll,select,poll) - When pipe becomes readable, main loop reads signal number and handles it safely
Q2: How does the kernel implement shared memory? Explain page table aliasing.
Q2: How does the kernel implement shared memory? Explain page table aliasing.
Q3: What is SCM_RIGHTS and how does it enable privilege separation?
Q3: What is SCM_RIGHTS and how does it enable privilege separation?
SCM_RIGHTS: A Unix Domain Socket control message type that allows passing open file descriptors between processes.Kernel Operation:Example: Chrome browser process (privileged) opens files and passes FDs to renderer processes (sandboxed, no filesystem access).Security benefit: Capability-based security. Worker gets access to specific resource instances, not broad permissions.
- Sender has FD 3 → struct file* (kernel object)
- Sender calls
sendmsg()with SCM_RIGHTS control message containing FD 3 - Kernel increments reference count of the struct file object
- Kernel finds free FD slot in receiver’s FD table (e.g., FD 5)
- Kernel installs same struct file* pointer in receiver’s FD table at slot 5
- Receiver now has FD 5 pointing to the same kernel file object
Q4: Why are writes ≤ PIPE_BUF atomic, and what happens for larger writes?
Q4: Why are writes ≤ PIPE_BUF atomic, and what happens for larger writes?
PIPE_BUF: Linux defines it as 4096 bytes (one page).Atomicity Guarantee: If two processes write ≤ 4096 bytes simultaneously, the kernel guarantees the data won’t interleave.Implementation:For writes > PIPE_BUF:Example:Solution: If you need atomicity for large messages:
- Use message queues (message-oriented)
- Use Unix sockets with framing protocol
- Use shared memory with proper locking
- Break into ≤4096 byte messages with sequence numbers
Q5: Compare System V vs POSIX IPC. When would you use each?
Q5: Compare System V vs POSIX IPC. When would you use each?
System V IPC (shmget, semget, msgget):Pros:
Recommendation: Use POSIX IPC for new projects unless you specifically need System V features.
- Kernel-persistent (survives process death until reboot or manual deletion)
- Well-established, available on all Unix systems
- Atomic operations (semop with multiple sem ops)
- Awkward API (ftok for key generation, numeric IDs)
- No integration with file descriptors (can’t poll/select)
- Requires manual cleanup (orphaned segments persist)
- Global namespace (key collisions possible)
- Clean API (name-based, like files)
- FD-based (can use poll/select on message queues)
- Better integration with modern APIs
- Filesystem-like semantics (/dev/shm)
- Not truly kernel-persistent (typically backed by tmpfs)
- Less widely available on old Unix systems
- Platform differences (macOS limits)
| Requirement | Choice |
|---|---|
| Need to survive process crashes | System V |
| Integration with event loops | POSIX (FD-based) |
| Modern codebase | POSIX |
| Legacy Unix compatibility | System V |
| Need poll/select on message queue | POSIX |
| Complex semaphore operations | System V (semop) |
Q6: Explain the security implications of shared memory and how to mitigate risks.
Q6: Explain the security implications of shared memory and how to mitigate risks.
Q7: How does the kernel deliver signals? Explain the signal frame and rt_sigreturn.
Q7: How does the kernel deliver signals? Explain the signal frame and rt_sigreturn.
Signal Delivery Process:Code:Key Insight: The signal frame is a “snapshot” of the process state that allows the kernel to resume execution exactly where it was interrupted.
Q8: What is the performance difference between pipes, Unix sockets, and shared memory?
Q8: What is the performance difference between pipes, Unix sockets, and shared memory?
10. Debugging IPC
Listing Active IPC Objects
Cleaning Orphaned IPC
Tracing IPC with strace
Multi-Mechanism Lab: Producer–Consumer Three Ways
Implement the same producer–consumer pattern using three different IPC mechanisms to feel the differences firsthand.Variant A: Pipe
Variant B: Shared Memory + Semaphore
Variant C: Unix Domain Socket
What to Observe
- Latency: Shared memory is fastest (no kernel copy).
- Complexity: Pipe is simplest; shared memory requires explicit sync.
- Flexibility: Unix sockets support FD passing and can be converted to network sockets easily.
Summary
IPC Mechanisms:| Mechanism | Speed | Use Case |
|---|---|---|
| Pipes | Fast | Parent-child, byte streams |
| FIFOs | Fast | Unrelated processes, byte streams |
| Shared Memory | Fastest | High throughput, zero-copy |
| Message Queues | Medium | Structured messages, priorities |
| Signals | Slow | Asynchronous notifications |
| Unix Sockets | Fast | FD passing, credentials, flexibility |
- Pipes: Simple but powerful. Remember PIPE_BUF atomicity and closing unused ends.
- Shared Memory: Fastest IPC via page table aliasing. Requires manual synchronization. Security-critical applications must validate all shared data.
- Message Queues: Built-in priority and message boundaries. Overhead makes them slower than pipes for high throughput.
- Signals: Async-signal-safety is critical. Use self-pipe trick for event loops. Modern apps prefer signalfd (Linux).
- Unix Sockets: Versatile. SCM_RIGHTS enables capability-based security. SO_PEERCRED provides kernel-verified credentials.
- Performance: Shared memory > Unix sockets > Pipes > TCP. But complexity also increases in that order.
- Chrome: Unix sockets + SCM_RIGHTS for sandboxing
- X11/Wayland: Shared memory for pixmap transfer
- systemd: Socket activation with FD passing
- Databases: Shared memory for buffer pools
Next: Synchronization & Locks →