-
Notifications
You must be signed in to change notification settings - Fork 0
ipc
Marek Bykowski edited this page May 15, 2026
·
3 revisions
Flow
pipe() → fork() → write(fd[1]) / read(fd[0]) → close()
Key Syscalls
| Syscall | Description |
|---|---|
pipe(int fd[2]) |
Creates kernel buffer; fd[0] = read end, fd[1] = write end |
read(fd[0], buf, n) |
Copies from kernel buffer into buf; blocks if empty; 0 on EOF |
write(fd[1], buf, n) |
Copies buf into kernel buffer; blocks if full |
close(fd[n]) |
Always close the unused end — read end sees EOF only when all write ends are closed |
dup2(src, dst) |
Makes dst point at whatever src points at; used to redirect stdin/stdout to the pipe |
Notes
- Lives entirely in the kernel — no filesystem entry
- After
fork()both processes inherit both ends; manually close the end you do not use -
dup2(fd[1], STDOUT_FILENO)+exec()is how shells implementcmd1 | cmd2 - Parent forks twice (one per command), closes both ends, then
wait()s — otherwise the reader never sees EOF
int fd[2];
pipe(fd);
if (fork() == 0) { /* child: reader */
close(fd[1]);
read(fd[0], buf, sizeof(buf));
close(fd[0]);
} else { /* parent: writer */
close(fd[0]);
write(fd[1], msg, strlen(msg) + 1);
close(fd[1]);
}dup2 example — simulates ls | wc -l
fd table before dup2: fd table after dup2(fd[1], STDOUT_FILENO):
fd 0 → terminal (stdin) fd 0 → terminal (stdin)
fd 1 → terminal (stdout) fd 1 → pipe write end ← redirected
fd 2 → terminal (stderr) fd 2 → terminal (stderr)
fd 3 → pipe read end fd 3 → pipe read end
fd 4 → pipe write end fd 4 → closed
int fd[2];
pipe(fd);
/* child 1: ls — stdout redirected to pipe write end */
if (fork() == 0) {
dup2(fd[1], STDOUT_FILENO); /* fd 1 now points at pipe write end */
close(fd[0]);
close(fd[1]); /* fd 4 no longer needed — fd 1 covers it */
execlp("ls", "ls", NULL);
}
/* child 2: wc -l — stdin redirected to pipe read end */
if (fork() == 0) {
dup2(fd[0], STDIN_FILENO); /* fd 0 now points at pipe read end */
close(fd[0]);
close(fd[1]);
execlp("wc", "wc", "-l", NULL);
}
/* parent closes both ends — otherwise wc -l never sees EOF */
close(fd[0]);
close(fd[1]);
wait(NULL);
wait(NULL);Flow
mkfifo() → open() → write() / read() → close() → unlink()
Key Syscalls
| Syscall | Description |
|---|---|
mkfifo(path, mode) |
Creates a named pipe as a file in the filesystem (e.g. /tmp/myfifo) |
umask(0) |
Call before mkfifo; subtracts permissions from mode (umask(0022) turns 0666 → 0644) |
open(path, O_WRONLY) |
Blocks until the other end is also opened |
open(path, O_RDONLY) |
Blocks until the other end is also opened |
unlink(path) |
Removes the FIFO file from the filesystem |
Notes
- Same read/write semantics as a pipe but any two unrelated processes can use it by name
-
open()blocks until both ends are open — built-in synchronization - Kernel buffer is the same as a pipe; data is consumed on read
- Writer is responsible for
unlink()cleanup
mkfifo("/tmp/myfifo", 0644);
/* writer process */
int fd = open("/tmp/myfifo", O_WRONLY);
write(fd, msg, strlen(msg) + 1);
close(fd);
/* reader process */
int fd = open("/tmp/myfifo", O_RDONLY);
read(fd, buf, sizeof(buf));
close(fd);
unlink("/tmp/myfifo");Flow
Server: socket() → bind() → listen() → accept() → read/write() → close()
Client: socket() → connect() → write/read() → close()
Key Syscalls
| Syscall | Description |
|---|---|
socket(AF_INET, SOCK_STREAM, 0) |
Creates a TCP socket; returns a file descriptor |
bind(fd, addr, len) |
Assigns IP + port to the socket; INADDR_ANY = all interfaces |
htons(port) |
Converts port to network byte order (big-endian) |
listen(fd, backlog) |
Marks socket passive; backlog = max queued pending connections; does not block |
accept(fd, NULL, NULL) |
Blocks until a client connects; returns a new fd for that connection |
connect(fd, addr, len) |
Client initiates connection; blocks until established |
inet_addr("127.0.0.1") |
Converts IP string to binary network address |
Notes
-
sockfdkeeps listening;connfdreturned byaccept()is for the individual client - Loop
accept()to handle multiple clients sequentially -
read()/write()onconnfdwork the same as on a pipe fd
/* server */
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
listen(sockfd, 5);
int connfd = accept(sockfd, NULL, NULL);
read(connfd, buf, sizeof(buf));
write(connfd, reply, sizeof(reply));
close(connfd);
close(sockfd);
/* client */
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
connect(sockfd, (struct sockaddr *)&addr, sizeof(addr));
write(sockfd, msg, sizeof(msg));
read(sockfd, buf, sizeof(buf));
close(sockfd);Flow
shm_open() → ftruncate() → mmap() → read/write ptr → munmap() → shm_unlink()
Key Syscalls
| Syscall | Description |
|---|---|
shm_open(name, O_CREAT|O_RDWR, mode) |
Creates/opens a shared memory object by name; visible under /dev/shm
|
ftruncate(fd, size) |
Sets the size of the object; must call before mmap — object starts at 0 bytes |
mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0) |
Maps the object into this process's virtual address space; returns a pointer |
munmap(ptr, size) |
Unmaps from this process's address space |
shm_unlink(name) |
Removes the object from the system; one process is responsible for this |
Notes
- Fastest IPC — no kernel buffer; both processes map the same physical memory page
- Two processes find the same object by name (same principle as opening a file by name)
- No built-in synchronization — concurrent writes to the same region require a semaphore
- Link with
-lrt
/* writer */
int fd = shm_open("/myshm", O_CREAT | O_RDWR, 0644);
ftruncate(fd, sizeof(struct shared));
struct shared *p = mmap(NULL, sizeof(struct shared),
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
memcpy(p->data, msg, strlen(msg) + 1);
munmap(p, sizeof(struct shared));
shm_unlink("/myshm");
/* reader */
int fd = shm_open("/myshm", O_RDONLY, 0);
struct shared *p = mmap(NULL, sizeof(struct shared),
PROT_READ, MAP_SHARED, fd, 0);
close(fd);
printf("%s\n", p->data);
munmap(p, sizeof(struct shared));Flow
sem_open() → sem_wait() → [critical section] → sem_post() → sem_close() → sem_unlink()
Key Syscalls
| Syscall | Description |
|---|---|
sem_open(name, O_CREAT, mode, value) |
Creates/opens a named semaphore; value=1 = binary semaphore (mutex) |
sem_wait(sem) |
Decrements semaphore; blocks if 0 — this is the lock
|
sem_post(sem) |
Increments semaphore; unblocks a waiting process — this is the unlock |
sem_close(sem) |
Closes handle in this process; does not remove the semaphore |
sem_unlink(name) |
Removes the semaphore from the system |
Notes
- Binary semaphore (
value=1) acts as a mutex — only one process in the critical section at a time - Classic race without semaphore: both processes read
counter=5, both writecounter=6— one increment lost - Link with
-lrt -lpthread
sem_t *sem = sem_open("/mysem", O_CREAT, 0644, 1);
sem_wait(sem); /* lock */
(*counter)++; /* critical section */
sem_post(sem); /* unlock */
sem_close(sem);
sem_unlink("/mysem");Flow
Receiver: sigaction() → pause()/loop → handler runs on signal arrival
Sender: kill(pid, sig)
Key Syscalls
| Syscall | Description |
|---|---|
sigaction(signum, &sa, NULL) |
Registers a handler for a signal; preferred over signal()
|
sigemptyset(&sa.sa_mask) |
No signals blocked while handler runs |
pause() |
Suspends process until any signal arrives; returns after handler runs |
kill(pid, sig) |
Sends any signal to a process by PID; name is misleading — not just for termination |
Common Signals
| Signal | Value | Description |
|---|---|---|
SIGINT |
2 | Ctrl+C — interrupt from terminal |
SIGTERM |
15 | Polite shutdown request |
SIGKILL |
9 | Forceful kill — cannot be caught or ignored |
SIGUSR1 |
10 | User-defined — free to use for any purpose |
SIGUSR2 |
12 | User-defined — free to use for any purpose |
Notes
- Signals carry no data — notification only
-
SIGKILLandSIGSTOPcannot be caught, blocked, or ignored - Avoid
printf()inside signal handlers — not async-signal-safe; usewrite()instead
/* receiver */
static void handler(int sig) { write(1, "got signal\n", 11); }
struct sigaction sa;
sa.sa_handler = handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGUSR1, &sa, NULL);
while (1) pause();
/* sender */
kill(target_pid, SIGUSR1);| Mechanism | Direction | Filesystem | Speed | Sync needed | Scope |
|---|---|---|---|---|---|
| Pipe | One-way | No | Fast | No | Parent/child only |
| FIFO | One-way | Yes | Fast | No | Any processes |
| Socket (TCP) | Two-way | No | Moderate | No | Network or local |
| Shared Memory | Two-way | No | Fastest | Yes | Any processes |
| Semaphore | No data | No | — | — | Any processes |
| Signal | No data | No | Fast | No | Any processes |
| Mechanism | Used in practice | Real-world examples |
|---|---|---|
| Pipe | Yes — everywhere | Shells (ls | grep), popen(), stdin/stdout chaining between processes |
| FIFO | Occasionally |
systemd service communication, log daemons, simple local IPC where sockets are overkill |
| Socket (TCP) | Heavily | Web servers (nginx, Apache), databases (PostgreSQL, Redis), any networked service; also used locally via AF_UNIX
|
| Socket (Unix domain) | Heavily | Docker daemon, D-Bus (desktop IPC), systemd, PostgreSQL local connections — faster than TCP for local IPC |
| Shared Memory | Yes — performance-critical | Video/audio processing (FFmpeg pipelines), game engines, high-frequency trading, kernel bypass networking (DPDK) |
| Semaphore | Yes — alongside shared memory | Any shared memory usage, thread pools, producer/consumer queues in real-time systems |
| Message Queue | Rarely directly | Mostly replaced by sockets + user-space libraries (ZeroMQ, RabbitMQ, Kafka) which offer more features |
| Signal | Yes — everywhere | Process lifecycle management (SIGTERM on shutdown, SIGHUP to reload config, SIGCHLD to reap children), Ctrl+C handling |
The short version:
- Sockets (especially Unix domain) dominate local IPC in modern Linux — flexible, bidirectional, well-supported
- Pipes remain the backbone of shell scripting and subprocess communication
- Shared memory + semaphores are chosen only when performance is critical and sockets are too slow
-
Signals are unavoidable — every daemon handles at least
SIGTERMandSIGHUP - FIFOs and POSIX message queues are niche — you will see them but rarely reach for them first