Inter-Process Communication
Processes have separate address spaces (Module 10), so they need kernel-provided mechanisms to cooperate:
Concept
Processes have separate address spaces (Module 10), so they need kernel-provided mechanisms to cooperate:
One-way byte stream between related processes pipe(); shell a | b FIFO (named pipe) Pipe with a name in the file system mkfifo; unrelated processes Message queue Discrete messages with priorities POSIX mq_open, System V msgget Shared memory
Same physical pages mapped into several processes Fastest (no copying) but needs synchronization; shm_open + mmap, or mmap(MAP_SHARED) before fork Synchronization between processes sem_open (named) or in shared memory
Byte streams or datagrams, local (Unix domain) or network Same API for local and remote Memory-mapped files Share a file's contents mmap of a file Higher level
D-Bus, gRPC, SOME/IP (automotive), message brokers Built on the mechanisms above
Message passing (pipes, queues, sockets): the kernel copies data; simple to synchronize, works across machines.
Shared memory: no copying; you must synchronize access yourself.
Why It Matters
Shells, databases (PostgreSQL uses shared memory), browsers (process per tab + IPC), Android (Binder), automotive middleware (SOME/IP, AUTOSAR communication) and microservices are all built on IPC.
Syntax
int fd[2]; pipe(fd); /* fd[0] read end, fd[1] write end */
void *p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
int sv[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, sv);Example
17.1 Pipe, shared memory and a socket pair between parent and child 17.2 A named pipe from the shell
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdatomic.h>
int main(void)
{
/* 1. pipe: a one-way byte stream, parent -> child */
int fd[2];
pipe(fd);
fflush(stdout);
if (fork() == 0) {
close(fd[1]);
char buf[64] = {0};
ssize_t n = read(fd[0], buf, sizeof buf - 1);
printf("pipe : child read %zd bytes: \"%s\"\n", n, buf);
fflush(stdout); /* _exit() does not flush stdio buffers */
_exit(0);
}
close(fd[0]);
write(fd[1], "22 F1 90", 8);
close(fd[1]);
wait(NULL);
/* 2. shared memory: both processes see the same physical pages */
struct { atomic_int ready; int value; } *shm =
mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
shm->ready = 0;
if (fork() == 0) {
shm->value = 12600; /* written by the child ... */
atomic_store(&shm->ready, 1);
_exit(0);
}
wait(NULL);
printf("shm : parent sees value %d (ready=%d) written by the child\n", shm->value, atomic
…$ mkfifo telemetry && ls -l telemetry | cut -c1-10
prw-r--r--
$ ( echo "speed=88" > telemetry & ) ; cat telemetry
speed=88
$ echo "12.6 12.4 12.5" | tr ' ' '\n' | sort -n | head -1
12.4Expected Result
The pipe carried the 8 bytes of a UDS request from parent to child.
The child's write to the shared mapping was visible to the parent — unlike ordinary memory after fork (Module 10), because the mapping is MAP_SHARED.
The socket pair worked in both directions: request and response.
The p at the start of the permissions marks a FIFO; the writer blocked until a reader opened it.
pipe : child read 8 bytes: "22 F1 90"
shm : parent sees value 12600 (ready=1) written by the child
socket: request PING, response ACK:PING$ mkfifo telemetry && ls -l telemetry | cut -c1-10
prw-r--r--
$ ( echo "speed=88" > telemetry & ) ; cat telemetry
speed=88
$ echo "12.6 12.4 12.5" | tr ' ' '\n' | sort -n | head -1
12.4Common Mistakes
Not closing unused pipe ends → the reader never sees end-of-file.
Shared memory without synchronization (races between processes, Module 15).
Assuming one write equals one read on a stream (a stream has no message boundaries; frame your messages).
Forgetting to remove named IPC objects (shm_unlink, mq_unlink, FIFO files).
Best Practices
Use sockets or message queues by default; switch to shared memory for large, high-rate data with clear synchronization (e.g. a ring buffer with atomics).
Frame messages with a length prefix or delimiter.
Set permissions on named IPC objects; validate everything received from another process.
Interview Relevance
High: "list IPC mechanisms and compare them", "pipe vs FIFO?", "why is shared memory fastest?", "message passing vs shared memory?", "how does ls | wc -l work?".
Practice Questions
Implement ls | wc -l in C with pipe, fork, dup2 and exec.
Build a ring buffer in shared memory with a producer and a consumer process.
Send a message with POSIX mq_send and receive it in another process (link with -lrt).
Measure throughput of pipe vs shared memory for 1 GB of data.
Quick Quiz
Q1. (Multiple choice) Which IPC mechanism avoids copying data through the kernel? A) Pipe B) Socket C) Shared memory D) Message queue Answer: C.
Q2. (Predict the output) What does ls -l show as the first character for a FIFO?
Answer: p (verified: prw-r--r--).
Q3. (True/False) A pipe is bidirectional.
Answer: False. Use two pipes or a socket pair for two-way communication.
Q4. (Find the bug) The parent writes to a pipe and waits for the child reading until EOF, but the child hangs forever.
Answer: Some process (often the child itself) still holds the write end open; close all unused write ends so read can return 0.
Coding Challenge
Implement the shell pipeline echo hello world | wc -w in C: create a pipe, fork two children, connect them with dup2, exec the two programs and wait for both.
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
int fd[2];
if (pipe(fd) < 0) { perror("pipe"); return 1; }
fflush(stdout);
if (fork() == 0) { /* left side: stdout -> pipe */
dup2(fd[1], STDOUT_FILENO);
close(fd[0]); close(fd[1]);
execlp("echo", "echo", "hello", "world", (char *)NULL);
_exit(127);
}
if (fork() == 0) { /* right side: stdin <- pipe */
dup2(fd[0], STDIN_FILENO);
close(fd[0]); close(fd[1]);
execlp("wc", "wc", "-w", (char *)NULL);
_exit(127);
}
close(fd[0]); close(fd[1]); /* parent must close both ends or wc never sees EOF */
wait(NULL); wait(NULL);
printf("pipeline finished\n");
return 0;
}