IPC Overview
{:.gc-basic}
Basic
Inter-Process Communication (IPC) allows separate processes to exchange data or synchronise. Linux provides multiple IPC mechanisms, each with different performance and usage characteristics.
| Mechanism | Direction | Persistence | Kernel buffered | Best for |
|---|---|---|---|---|
| Pipe (anonymous) | Unidirectional | Process lifetime | Yes | Parent↔child |
| Named pipe (FIFO) | Unidirectional | Filesystem | Yes | Unrelated processes |
| POSIX shared memory | Bidirectional | Until unlinked | No (direct) | High-speed data sharing |
| POSIX message queue | Bidirectional | Until unlinked | Yes | Structured messages |
| UNIX domain socket | Bidirectional | Process lifetime | Yes | General IPC, fd passing |
Anonymous Pipes
{:.gc-basic}
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
int main(void) {
int pipefd[2]; // pipefd[0] = read end, pipefd[1] = write end
if (pipe(pipefd) == -1) { perror("pipe"); exit(1); }
pid_t pid = fork();
if (pid == 0) {
// --- CHILD: write to pipe ---
close(pipefd[0]); // close unused read end
const char* msg = "Hello from child";
write(pipefd[1], msg, strlen(msg));
close(pipefd[1]);
exit(0);
} else {
// --- PARENT: read from pipe ---
close(pipefd[1]); // close unused write end
char buf[128] = {0};
ssize_t n = read(pipefd[0], buf, sizeof(buf) - 1);
printf("Parent received: %s\n", buf);
close(pipefd[0]);
waitpid(pid, NULL, 0);
}
return 0;
}
Pipe as stdin/stdout for a Child Process
// Redirect child's stdout to our pipe (like shell pipe)
int pipefd[2];
pipe(pipefd);
pid_t pid = fork();
if (pid == 0) {
close(pipefd[0]); // child doesn't read
dup2(pipefd[1], STDOUT_FILENO); // stdout → write end of pipe
close(pipefd[1]);
execlp("ls", "ls", "-la", NULL); // output goes to pipe
exit(1);
} else {
close(pipefd[1]); // parent doesn't write
char buf[4096];
ssize_t n = read(pipefd[0], buf, sizeof(buf));
// process ls output
close(pipefd[0]);
waitpid(pid, NULL, 0);
}
Pipe Properties
- Capacity: typically 64KB (Linux default); write blocks when full
- Atomic writes: writes up to
PIPE_BUF(4096+ bytes) are atomic - EOF: read returns 0 when all write ends are closed
- SIGPIPE: writing to a pipe with no readers sends
SIGPIPE
Named Pipes (FIFOs)
{:.gc-mid}
Intermediate
A named pipe (FIFO) appears as a filesystem entry — unrelated processes can use it.
# Create from shell
mkfifo /tmp/myfifo
ls -la /tmp/myfifo
# prw-r--r-- 1 user user 0 Mar 7 12:00 /tmp/myfifo
#include <sys/stat.h>
#include <fcntl.h>
// Create programmatically
mkfifo("/tmp/sensor_fifo", 0644);
// Writer process
int fd = open("/tmp/sensor_fifo", O_WRONLY); // blocks until reader opens
write(fd, &sensor_value, sizeof(float));
close(fd);
// Reader process
int fd = open("/tmp/sensor_fifo", O_RDONLY); // blocks until writer opens
float val;
read(fd, &val, sizeof(float));
close(fd);
POSIX Shared Memory
{:.gc-adv}
Advanced
Shared memory is the fastest IPC — processes read/write directly to the same physical memory pages. Requires explicit synchronisation (mutex/semaphore) to prevent races.
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#define SHM_NAME "/sensor_shm"
#define SEM_NAME "/sensor_sem"
// Shared data structure
typedef struct {
float temperature;
float humidity;
uint32_t sequence;
int updated;
} SensorData;
// --- PRODUCER (writer) ---
int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0644);
ftruncate(shm_fd, sizeof(SensorData));
SensorData* shm = mmap(NULL, sizeof(SensorData),
PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
sem_t* sem = sem_open(SEM_NAME, O_CREAT, 0644, 1);
// Update shared data with semaphore protection
sem_wait(sem);
shm->temperature = 23.5f;
shm->humidity = 65.0f;
shm->sequence++;
shm->updated = 1;
sem_post(sem);
munmap(shm, sizeof(SensorData));
close(shm_fd);
// --- CONSUMER (reader) ---
int shm_fd = shm_open(SHM_NAME, O_RDONLY, 0);
SensorData* shm = mmap(NULL, sizeof(SensorData),
PROT_READ, MAP_SHARED, shm_fd, 0);
sem_t* sem = sem_open(SEM_NAME, 0);
sem_wait(sem);
float t = shm->temperature;
float h = shm->humidity;
sem_post(sem);
printf("Temp=%.1f°C Humidity=%.1f%%\n", t, h);
// Cleanup (done by one process, usually the producer/creator)
munmap(shm, sizeof(SensorData));
close(shm_fd);
shm_unlink(SHM_NAME); // remove from /dev/shm
sem_close(sem);
sem_unlink(SEM_NAME);
Link with -lrt and -lpthread.
POSIX Message Queues
{:.gc-mid}
Message queues deliver typed, prioritised messages between processes. Each message has a priority — higher-priority messages are received first.
#include <mqueue.h>
#define MQ_NAME "/sensor_queue"
// --- SENDER ---
struct mq_attr attr = {
.mq_maxmsg = 10, // max 10 messages in queue
.mq_msgsize = 128, // max 128 bytes per message
};
mqd_t mq = mq_open(MQ_NAME, O_CREAT | O_WRONLY, 0644, &attr);
typedef struct { int id; float value; } SensorMsg;
SensorMsg msg = { .id = 1, .value = 23.5f };
mq_send(mq, (char*)&msg, sizeof(msg), 5); // priority 5
mq_close(mq);
// --- RECEIVER ---
mqd_t mq = mq_open(MQ_NAME, O_RDONLY);
SensorMsg msg;
unsigned int prio;
ssize_t n = mq_receive(mq, (char*)&msg, sizeof(msg), &prio);
printf("ID=%d Val=%.1f prio=%u\n", msg.id, msg.value, prio);
mq_close(mq);
mq_unlink(MQ_NAME);
// Async notification (signal or thread when message arrives)
struct sigevent sev = {
.sigev_notify = SIGEV_SIGNAL,
.sigev_signo = SIGUSR1,
};
mq_notify(mq, &sev);
IPC Performance Comparison
Shared memory: ~1GB/s (direct memory access, no syscall overhead)
UNIX socket: ~300MB/s (syscall, kernel copy)
POSIX mq: ~200MB/s (syscall, kernel copy)
Pipe: ~200MB/s (syscall, kernel copy)
TCP loopback: ~100MB/s (full network stack)
For high-frequency, large data (video frames, radar data): shared memory + semaphore. For control messages, commands: message queue or UNIX socket.
Interview Q&A
{:.gc-iq}
Interview Q&A
Q1 — Basic: What is the difference between a pipe and a FIFO?
An anonymous pipe is created with
pipe()and exists only in the kernel — it has no filesystem entry and can only be used between related processes (parent and children that inherited the file descriptors). A FIFO (named pipe) is created withmkfifo()and appears as a filesystem entry — any two processes on the same system can open it by name, regardless of process relationship. Both provide unidirectional, kernel-buffered byte streams with the same kernel implementation.
Q2 — Intermediate: Why is shared memory the fastest IPC mechanism?
With shared memory, the kernel maps the same physical memory pages into two (or more) processes’ virtual address spaces. Writes by one process are immediately visible to others reading the same memory — there is no data copy and no syscall involved in the actual data transfer. Other IPC mechanisms (pipes, sockets, message queues) require at least one kernel copy: data must be written to a kernel buffer by the sender and copied to the receiver’s buffer. For multi-megabyte payloads (images, sensor buffers), this difference is significant.
Q3 — Intermediate: How do you synchronise access to shared memory between processes?
Unlike mutexes (which only work within one process by default), you need inter-process synchronisation. Options: (1) POSIX named semaphore (
sem_open) — easy to use; (2)pthread_mutexwithPTHREAD_PROCESS_SHAREDattribute — lower overhead; must be stored in the shared memory itself; (3)pthread_cond_twithPTHREAD_PROCESS_SHARED— for condition synchronisation; (4) Atomic operations — for simple cases (single producer, single consumer). Always initialise the lock in the shared region before mapping in the consumer.
Q4 — Advanced: What happens when a write end of a pipe is closed while a reader is blocked?
When all write ends of a pipe are closed and the reader calls
read(), it returns 0 (EOF). If the reader is blocked inread(), it will be unblocked and receive 0. Conversely, when all read ends are closed and a process writes to the pipe, it receivesSIGPIPE(default action: terminate). IfSIGPIPEis ignored or handled,write()returns -1 witherrno == EPIPE. Always close the unused end of a pipe in each process, and always handle broken pipe conditions in production code.
References
{:.gc-ref}
References
| Resource | Link |
|---|---|
man 2 pipe |
Pipe system call |
man 7 fifo |
Named pipe overview |
man 7 shm_overview |
POSIX shared memory overview |
man 7 mq_overview |
POSIX message queue overview |
| TLPI Chapters 44–48 | IPC mechanisms in depth |