IPC — Inter-Process Communication
IPC is how two processes on the same machine (or sometimes across machines) communicate. Unlike network APIs, IPC mechanisms are OS-level primitives — faster, lower overhead, but limited to the same host (usually).
Why IPC?
Process A and Process B need to share data or coordinate.
They can't share memory directly (OS enforces isolation).
IPC provides controlled channels between them.
Use cases:
- Microservices on same host communicating via Unix sockets
- Browser sending commands to a background worker
- Database server and its client library
- Shell pipes: cat file.txt | grep "error" | wc -l
- Electron: renderer process ↔ main process
IPC Mechanisms Overview
mindmap
root((IPC Mechanisms))
Pipes
Anonymous Pipes
Parent-Child only
Unidirectional
Shell pipes
Named Pipes FIFO
Any processes
Unidirectional
Filesystem path
Sockets
Unix Domain Sockets
Same host only
File system path
Faster than TCP
Nginx PostgreSQL Redis
Network Sockets TCP/UDP
Cross-host capable
Standard networking
Shared Memory
Fastest IPC
Both processes map same memory
Requires synchronization
Semaphores Mutexes
Message Queues
POSIX System V
Asynchronous
Message types
No shared state
Signals
Notifications only
No data payload
SIGTERM SIGKILL SIGINT
Memory-Mapped Files
mmap syscall
File backed shared memory
Large data sharing
Pipes
Anonymous Pipes (Shell |)
# Classic Unix pipeline
cat /var/log/nginx/access.log | grep "ERROR" | awk '{print $1}' | sort | uniq -c
# Each | creates a pipe:
# cat → pipe1 → grep → pipe2 → awk → pipe3 → sort → pipe4 → uniq
Process A ──[write end]──▶ [kernel buffer] ──▶[read end]── Process B
(pipe)
- Unidirectional
- Parent creates pipe before fork(), child inherits file descriptors
- Anonymous: no name in filesystem
- Automatic cleanup when both ends close
Named Pipes (FIFOs)
# Create a named pipe
mkfifo /tmp/my-pipe
# Process A writes to it
echo "Hello from A" > /tmp/my-pipe &
# Process B reads from it
cat /tmp/my-pipe
# Output: Hello from A
# Cleanup
rm /tmp/my-pipe
- Appears as a file in the filesystem
- Any two processes can use it (not just parent-child)
- Still unidirectional
- Blocks on open() until both ends are open
Unix Domain Sockets
The preferred IPC for high-performance same-host communication. Looks like a network socket but never leaves the kernel — much faster than TCP.
# Server (Python)
import socket, os
SOCKET_PATH = "/tmp/my-app.sock"
if os.path.exists(SOCKET_PATH):
os.remove(SOCKET_PATH)
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.bind(SOCKET_PATH)
server.listen(1)
conn, _ = server.accept()
data = conn.recv(1024)
conn.send(b"Hello from server!")
conn.close()
# Client
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.connect("/tmp/my-app.sock")
client.send(b"Hello!")
response = client.recv(1024)
Who Uses Unix Sockets?
| Software | Socket Path |
|---|---|
| Nginx → PHP-FPM | /var/run/php/php8.1-fpm.sock |
| PostgreSQL | /var/run/postgresql/.s.PGSQL.5432 |
| Redis (local) | /var/run/redis/redis.sock |
| Docker daemon | /var/run/docker.sock |
| MySQL | /var/run/mysqld/mysqld.sock |
Unix Socket vs TCP localhost:
TCP localhost: goes through full network stack (loopback)
Unix socket: stays in kernel, no TCP overhead
Speed: Unix socket is ~30-50% faster for same-host IPC
Shared Memory
The fastest IPC — both processes map the same physical memory pages. No copying, just read/write.
// Process A: Create and write
int shm_fd = shm_open("/my-shm", O_CREAT | O_RDWR, 0666);
ftruncate(shm_fd, 1024); // Set size
void* ptr = mmap(0, 1024, PROT_WRITE, MAP_SHARED, shm_fd, 0);
sprintf(ptr, "Hello from Process A!");
// Process B: Open and read
int shm_fd = shm_open("/my-shm", O_RDONLY, 0666);
void* ptr = mmap(0, 1024, PROT_READ, MAP_SHARED, shm_fd, 0);
printf("%s\n", (char*)ptr); // "Hello from Process A!"
// Cleanup (Process A)
munmap(ptr, 1024);
shm_unlink("/my-shm");
The Synchronization Problem
Process A writes: "HELLO"
Process B reads: "HEL??" ← race condition!
Both read and write simultaneously without locking = data corruption.
Solution: Semaphores or Mutexes
sem_wait(sem); // Lock
// write/read shared memory
sem_post(sem); // Unlock
Message Queues
Processes exchange discrete messages — sender doesn't wait for receiver (async). Messages persist in the kernel queue.
// POSIX Message Queue
// Send
mqd_t mq = mq_open("/my-queue", O_CREAT | O_WRONLY, 0644, &attr);
mq_send(mq, "job:process-image:42", 20, 1); // message, size, priority
// Receive
mqd_t mq = mq_open("/my-queue", O_RDONLY, 0644, NULL);
mq_receive(mq, buffer, sizeof(buffer), &priority);
Key properties:
✅ Asynchronous — sender doesn't block waiting for receiver
✅ Messages are discrete units (not a stream)
✅ Priority ordering supported
✅ Messages persist if receiver is slow
❌ Size limits per message
❌ Total queue size limit (kernel managed)
Signals
Asynchronous notifications to a process. No data payload — just a signal number.
# Common signals
kill -SIGTERM 1234 # Graceful shutdown (can be caught)
kill -SIGKILL 1234 # Force kill (cannot be caught!)
kill -SIGHUP 1234 # Reload config (nginx uses this)
kill -SIGUSR1 1234 # User-defined signal 1
// Handle a signal in C
#include <signal.h>
void handle_sigterm(int sig) {
printf("Received SIGTERM, shutting down gracefully...\n");
cleanup();
exit(0);
}
signal(SIGTERM, handle_sigterm); // Register handler
Common Signals Reference
| Signal | Number | Default | Catchable | Use |
|---|---|---|---|---|
SIGTERM |
15 | Terminate | ✅ | Graceful shutdown |
SIGKILL |
9 | Terminate | ❌ | Force kill |
SIGINT |
2 | Terminate | ✅ | Ctrl+C |
SIGHUP |
1 | Terminate | ✅ | Reload config |
SIGCHLD |
17 | Ignore | ✅ | Child process exited |
SIGUSR1/2 |
10/12 | Terminate | ✅ | App-specific |
IPC Comparison
┌─────────────────┬───────────┬─────────┬──────────┬────────────┬──────────────────────────┐
│ Mechanism │ Speed │ Async │ Persist │ Direction │ Best For │
├─────────────────┼───────────┼─────────┼──────────┼────────────┼──────────────────────────┤
│ Pipes │ Fast │ No │ No │ One-way │ Shell commands, parent- │
│ │ │ │ │ │ child communication │
│ Named Pipes │ Fast │ No │ No │ One-way │ Unrelated processes │
│ Unix Sockets │ Very fast │ Yes │ No │ Both ways │ Local services (nginx, │
│ │ │ │ │ │ postgres, docker) │
│ Shared Memory │ Fastest │ No │ No │ Both ways │ Large data, high-freq │
│ │ │ │ │ │ reads (with sync) │
│ Message Queues │ Fast │ Yes │ Yes │ Both ways │ Task queues, decoupling │
│ Signals │ Instant │ Yes │ No │ One-way │ Notifications, control │
└─────────────────┴───────────┴─────────┴──────────┴────────────┴──────────────────────────┘
Real-World Patterns
Web Server + App Server (Unix Socket)
Browser
│
▼ TCP (port 80/443)
Nginx
│
▼ Unix Socket (/run/app.sock) ← faster than TCP localhost
Node.js / PHP-FPM / Gunicorn
│
▼ DB connection pool
PostgreSQL (Unix socket locally)
Worker Process Pattern (Message Queue)
API Server Worker Pool
│ │
│── POST /jobs ──▶ kernel ──▶ │
│ (enqueue msg to queue) │ dequeue → process → ack
│ │
│◀──── async result (webhook or polling)
Interview tips: - Unix sockets > TCP for same-host communication — explain why (no TCP stack overhead) - Shared memory is fastest but needs explicit synchronization (race conditions!) - Signals can't carry data — they're just event notifications - Pipes are unidirectional — for bidirectional, use two pipes or a socket