Linux System Programming · advanced · ~12 min

Pipes

Connect two processes with a unidirectional byte stream.

Overview

A pipe is a one-directional kernel buffer with two file descriptors: one to write into, one to read from. pipe(fds) fills fds[0] with the read end and fds[1] with the write end. Combined with fork and dup2, pipes implement shell pipelines like ls | grep foo.

Why it matters

Pipes are how Unix programs cooperate. Every | you type in a shell creates a pipe between the two processes. They are also how parent and child processes exchange data within a single program (the parent reads the child's stdout, for example).

Core concepts

Atomic up to PIPE_BUF. Small writes (<= 4 KB on Linux) are guaranteed atomic — useful for log collectors. EOF on read. When the write end is fully closed, read returns 0. Blocking by default. Reading from an empty pipe blocks until data arrives or the write end closes; writing to a full pipe blocks until space frees. Close unused ends. Each fork inherits both ends; close the ones you don't use, or readers never see EOF.

Syntax notes

int fds[2];
pipe(fds);                  // fds[0] = read end, fds[1] = write end
if (fork() == 0) {           // child
    dup2(fds[1], 1);          // stdout -> pipe write end
    close(fds[0]); close(fds[1]);
    execlp("ls", "ls", NULL);
}
// parent:
close(fds[1]);               // don't keep the write end alive
char buf[4096];
read(fds[0], buf, sizeof buf);

Lesson

int pipe(int p[2]) creates a pair of fds: p[0] to read, p[1] to write. Typically: fork, dup the appropriate end onto stdin/stdout, close the unused end, then exec.

Shell pipelines a | b are exactly this dance.

Code examples

int p[2]; pipe(p);
if (fork() == 0) {        // child
    dup2(p[0], 0); close(p[0]); close(p[1]);
    execvp("wc", (char*[]){"wc","-l", NULL});
    _exit(127);
}
close(p[0]);
write(p[1], data, n); close(p[1]);
wait(NULL);

Common mistakes

  • Forgetting to close the unused end of the pipe — readers block waiting for EOF that never comes.

Debugging tips

If your reader never sees EOF, you forgot to close the write end in the parent (or in both sides — close everywhere you don't actually use). strace -f -e trace=pipe,read,write,close shows every pipe operation. Deadlock between two processes? Usually one is waiting to write while the other is waiting to write the other direction — use two pipes.

Memory safety

Pipes are file descriptors — they suffer the same leak class as fds. Always close both ends after use. Reading into a buffer of size N reads at most N bytes; check the return for short reads (less than you asked for is normal on pipes).

Real-world uses

Shell pipelines (grep foo big.log | wc -l), inter-process communication, capturing a subprocess's stdout, building command pipelines programmatically.

Practice tasks

  1. Make a pipe, fork, have the child write 'hello' and the parent read+print it. 2. Reproduce ls | wc -l with two forks and two execs joined by one pipe. 3. Test what happens if you forget to close the write end — note the read blocks forever.

Summary

A pipe is a one-way kernel buffer. Pair with fork + dup2 to chain processes. Close the ends you don't use, watch for short reads, and you can build any Unix-style pipeline from C.

Practice with these exercises