Pipes and Named Pipes (FIFOs)
Anonymous pipes
An anonymous pipe (pipe() system call) creates a unidirectional byte stream between a pair of file descriptors:
int pipefd[2];
pipe(pipefd); // pipefd[0] = read end, pipefd[1] = write end
Data written to pipefd[1] can be read from pipefd[0]. The pipe is a kernel buffer, typically 64 KB on Linux. Writes of up to PIPE_BUF bytes (4096 on Linux) are atomic — they will not be interleaved with writes from other processes. Larger writes may be interleaved.
Pipes are only usable between related processes (parent-child, or siblings sharing the parent’s descriptors). The typical pattern:
- Parent calls
pipe(). - Parent calls
fork(). The child inherits a copy of both descriptors. - Parent closes the read end; child closes the write end (or vice versa).
- Parent writes; child reads (or child writes; parent reads).
The shell’s pipe operator
ls | grep foo translates to:
pipe(pipefd);
if (fork() == 0) {
close(pipefd[0]); // close read end
dup2(pipefd[1], STDOUT_FILENO); // redirect stdout to pipe write end
close(pipefd[1]);
execlp("ls", "ls", NULL);
}
// Parent
close(pipefd[1]); // close write end
dup2(pipefd[0], STDIN_FILENO); // redirect stdin to pipe read end
close(pipefd[0]);
execlp("grep", "grep", "foo", NULL);
The dup2() calls redirect the standard streams to the pipe, so the programmes can use printf() / scanf() without knowing they are communicating through a pipe.
Named pipes (FIFOs)
A named pipe (or FIFO) is created with mkfifo() or the mkfifo command. It appears as a special file in the file system:
mkfifo /tmp/myfifo
ls -l /tmp/myfifo # shows type 'p' (pipe)
A named pipe behaves like a pipe, but any process (not just related ones) can open it by name. One process opens the FIFO for writing, another for reading; the kernel connects them. Unlike regular files, the data never touches the disk — it is buffered in the kernel’s pipe buffer, just like an anonymous pipe.
Named pipes support rendezvous semantics: an open() for reading blocks until another process opens the same FIFO for writing (and vice versa), unless O_NONBLOCK is specified.
Pipes vs named pipes vs signals
| Feature | Signals | Pipes | Named pipes |
|---|---|---|---|
| Data transfer | No (signal number only) | Yes — byte stream | Yes — byte stream |
| Synchronisation | Asynchronous; hard to synchronise on | Built-in: read blocks until data, write blocks when full | Same as pipes |
| Process relationship | Any (by PID) | Related only (inherited descriptors) | Any (by path name) |
| Scope | Same UID (usually) | Fork tree | Any process with FS access |
| Persistence | Ephemeral (delivered or merged) | Lifetime of processes holding descriptors | Persistent in FS; survives process lifetime |
| Capacity | Minimal (merged, no queue) | Kernel buffer (64 KB) | Kernel buffer (64 KB) |
Summary
- Pipes are unidirectional byte streams between related processes; created with
pipe()and inherited acrossfork(). - The shell uses pipes +
dup2()to connect commands’ stdin/stdout. - Named pipes (FIFOs) extend pipes to unrelated processes by existing in the file system.
- Unlike files, pipe data is buffered in memory, not on disk.
- Signals are for notification; pipes and FIFOs are for data.