Pipes and inter-process communication
Connect processes with Unix pipes — creating pipes with pipe(), redirecting stdout/stdin with dup2(), and the design of Unix pipelines.
- Create a pipe with the pipe() system call
- Use dup2 to redirect a child's stdin or stdout to a pipe
- Build a parent-to-child and child-to-parent data pipeline
- Explain why closing unused pipe ends is essential
A pipe is a unidirectional byte stream connecting two file descriptors — one for reading, one for writing. Pipes are the fundamental IPC mechanism on Unix: when you type ls | grep foo in a shell, the shell creates a pipe, forks two children, redirects ls's stdout to the pipe's write end, redirects grep's stdin to the pipe's read end, and execs both commands.
Creating a pipe
#include <unistd.h>
int pipefd[2];
if (pipe(pipefd) < 0) { perror("pipe"); return 1; }
/* pipefd[0] is the read end */
/* pipefd[1] is the write end */Data written to pipefd[1] can be read from pipefd[0]. The pipe has a kernel buffer (typically 64 KB on Linux). Writing blocks when the buffer is full; reading blocks when the buffer is empty.
Parent writes, child reads
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(void) {
int pipefd[2];
pipe(pipefd);
pid_t pid = fork();
if (pid == 0) {
/* Child: read from pipe */
close(pipefd[1]); /* close unused write end */
char buf[128];
ssize_t n = read(pipefd[0], buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Child received: %s\n", buf);
}
close(pipefd[0]);
_exit(0);
} else {
/* Parent: write to pipe */
close(pipefd[0]); /* close unused read end */
const char *msg = "Hello from parent";
write(pipefd[1], msg, strlen(msg));
close(pipefd[1]); /* closing write end signals EOF to reader */
waitpid(pid, NULL, 0);
}
return 0;
}Closing unused ends is mandatory. If the parent keeps pipefd[0] open, the child reading from pipefd[0] will never get EOF — the read will block forever after all data is consumed, because there is still a process with an open write end (the parent, even though it wrote nothing).
Redirecting stdin/stdout with dup2
To run a subprocess and capture its output, redirect the child's stdout to the pipe's write end using dup2:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void) {
int pipefd[2];
pipe(pipefd);
pid_t pid = fork();
if (pid == 0) {
/* Child: redirect stdout to write end of pipe */
close(pipefd[0]); /* close read end */
dup2(pipefd[1], STDOUT_FILENO); /* pipefd[1] becomes stdout */
close(pipefd[1]); /* close original write end (now duplicated to 1) */
execlp("date", "date", NULL); /* output goes to pipe */
perror("execlp");
_exit(1);
} else {
/* Parent: read from read end */
close(pipefd[1]); /* close write end */
char buf[256];
ssize_t n = read(pipefd[0], buf, sizeof(buf) - 1);
close(pipefd[0]);
waitpid(pid, NULL, 0);
if (n > 0) {
buf[n] = '\0';
printf("date output: %s", buf);
}
}
return 0;
}dup2(oldfd, newfd) makes newfd a copy of oldfd. After dup2(pipefd[1], STDOUT_FILENO), file descriptor 1 (stdout) points to the pipe's write end. The child's execlp("date", ...) writes its output to stdout, which is now the pipe.
Named pipes (FIFOs)
For communication between unrelated processes, use a named pipe:
mkfifo /tmp/mypipeIn C:
#include <sys/types.h>
#include <sys/stat.h>
mkfifo("/tmp/mypipe", 0666);
FILE *fp = fopen("/tmp/mypipe", "r"); /* or "w" */Named pipes appear in the filesystem and can be opened by any process, not just parent-child pairs.
Other IPC mechanisms
For completeness, Unix provides several other IPC mechanisms beyond pipes:
| Mechanism | Use case |
|---|---|
| Pipes | Parent-child data streaming |
| Named pipes (FIFO) | Any two processes |
| Unix domain sockets | Bidirectional, full-duplex IPC |
Shared memory (mmap) | Large data, needs synchronisation |
Message queues (mq_open) | Structured messages |
| Semaphores | Counter-based synchronisation |
Pipes are the right tool for most command-line tools and simple process communication. Unix domain sockets are the right tool when you need bidirectional communication or multiple clients.
Where to go next
Next: writing portable C — the C standards, _POSIX_C_SOURCE, and the practices that make C programs work across platforms and compilers.