Skip to content
Marek Bykowski edited this page May 15, 2026 · 3 revisions

Linux IPC Cheat Sheet


Pipe

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 implement cmd1 | 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);

FIFO (Named Pipe)

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 06660644)
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");

Internet Socket (TCP)

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

  • sockfd keeps listening; connfd returned by accept() is for the individual client
  • Loop accept() to handle multiple clients sequentially
  • read()/write() on connfd work 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);

Shared Memory (POSIX)

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));

Semaphore (POSIX Named)

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 write counter=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");

Signals

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
  • SIGKILL and SIGSTOP cannot be caught, blocked, or ignored
  • Avoid printf() inside signal handlers — not async-signal-safe; use write() 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);

IPC Comparison

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

Practical Usage Today

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 SIGTERM and SIGHUP
  • FIFOs and POSIX message queues are niche — you will see them but rarely reach for them first

Clone this wiki locally