Linux System Programming · advanced · ~12 min
Connect two processes with a unidirectional byte stream.
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.
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).
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.
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);
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.
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);
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.
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).
Shell pipelines (grep foo big.log | wc -l), inter-process communication, capturing a subprocess's stdout, building command pipelines programmatically.
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.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.