What is IPC?
When you run two programs at the same time — say a web server and a database — they are separate processes. By default, each process lives in its own isolated memory space. They cannot read each other’s variables. But in real systems, these processes need to talk to each other.
Interprocess Communication (IPC) is the set of mechanisms the Linux kernel provides to let processes exchange data, coordinate actions, and signal each other. Chapter 43 of TLPI is a bird’s-eye view of all these mechanisms before diving deep in later chapters.
Think of IPC like tools in a toolbox. A hammer, a screwdriver, and a wrench all exist for a reason. Similarly, pipes, sockets, shared memory, semaphores all solve slightly different problems. Understanding the big picture first helps you pick the right tool.
UNIX gives us a rich variety of IPC mechanisms. They fall into three broad categories:
| Category | Sub-Type | Mechanism / Facility | Notes |
|---|---|---|---|
| Communication | Data Transfer | Pipe, FIFO | Byte stream — undelimited bytes |
| System V MQ, POSIX MQ, Datagram Socket | Message — delimited packets | ||
| Pseudoterminal | Specialized use (Chapter 64) | ||
| Shared Memory | System V SHM, POSIX SHM, mmap() | All processes see same RAM pages | |
| Synchronization | — | Semaphore (SysV/POSIX), File Lock (flock/fcntl) | Coordinate access, prevent race conditions |
| Signal | — | kill(), sigqueue(), Realtime signals | Async notification; rarely carries data |
Why Do So Many Similar Facilities Exist?
You will notice pipes, FIFOs, sockets all look similar. There are two historical reasons:
FIFOs came from System V UNIX. Stream sockets came from BSD UNIX. Both were later merged into Linux. So we have both.
POSIX IPC (message queues, semaphores, shared memory) was built to fix design problems in older System V IPC. Newer = cleaner API.
Signals are primarily used for notifications (SIGTERM = terminate, SIGINT = Ctrl+C). But they can sometimes carry information:
- The signal number itself is information (e.g., SIGUSR1 vs SIGUSR2)
- Realtime signals (SIGRTMIN to SIGRTMAX) can carry an integer or pointer as payload via
sigqueue() - They are asynchronous — delivery interrupts the receiving process at any time
Coding Example 1 – Sending a Signal Between Processes
/*
* signal_ipc.c
* Demonstrates using kill() to send a signal as a basic IPC notification.
* Process A (parent) sends SIGUSR1 to Process B (child).
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void sigusr1_handler(int sig) {
/* This runs in the child when signal is received */
printf("[Child] Received SIGUSR1 from parent! Signal number = %d\n", sig);
}
int main(void) {
pid_t child_pid;
child_pid = fork();
if (child_pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (child_pid == 0) {
/* --- CHILD PROCESS --- */
/* Register handler for SIGUSR1 */
signal(SIGUSR1, sigusr1_handler);
printf("[Child] PID=%d, waiting for signal...\n", getpid());
/* pause() sleeps until any signal is received */
pause();
printf("[Child] Resuming after signal. Exiting.\n");
exit(EXIT_SUCCESS);
} else {
/* --- PARENT PROCESS --- */
sleep(1); /* Give child time to set up handler */
printf("[Parent] Sending SIGUSR1 to child PID=%d\n", child_pid);
kill(child_pid, SIGUSR1);
/* Wait for child to finish */
wait(NULL);
printf("[Parent] Child finished. Done.\n");
}
return 0;
}
/* Compile: gcc signal_ipc.c -o signal_ipc
Run: ./signal_ipc
Output:
[Child] PID=12345, waiting for signal...
[Parent] Sending SIGUSR1 to child PID=12345
[Child] Received SIGUSR1 from parent! Signal number = 10
[Child] Resuming after signal. Exiting.
[Parent] Child finished. Done.
*/
Coding Example 2 – Realtime Signal Carrying Data (sigqueue)
/*
* rt_signal_ipc.c
* Realtime signals can carry an integer payload using sigqueue().
* This is IPC: the parent sends a value to the child via signal.
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void rt_handler(int sig, siginfo_t *info, void *ucontext) {
printf("[Child] Got realtime signal %d with value = %d\n",
sig, info->si_value.sival_int);
}
int main(void) {
pid_t child_pid = fork();
if (child_pid == 0) {
/* CHILD: use sigaction with SA_SIGINFO to receive payload */
struct sigaction sa;
sa.sa_sigaction = rt_handler;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sigaction(SIGRTMIN, &sa, NULL);
printf("[Child] Waiting for realtime signal...\n");
pause();
exit(EXIT_SUCCESS);
} else {
sleep(1);
/* PARENT: send SIGRTMIN with integer value 42 */
union sigval sv;
sv.sival_int = 42;
printf("[Parent] Sending SIGRTMIN with value 42 to PID=%d\n", child_pid);
sigqueue(child_pid, SIGRTMIN, sv);
wait(NULL);
}
return 0;
}
/* Compile: gcc rt_signal_ipc.c -o rt_signal_ipc
Run: ./rt_signal_ipc
Output:
[Child] Waiting for realtime signal...
[Parent] Sending SIGRTMIN with value 42 to PID=...
[Child] Got realtime signal 34 with value = 42
*/
