Code of the Day
AdvancedSystems programming patterns

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.

CAdvanced11 min read
Recommended first
By the end of this lesson you will be able to:
  • 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/mypipe

In 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:

MechanismUse case
PipesParent-child data streaming
Named pipes (FIFO)Any two processes
Unix domain socketsBidirectional, full-duplex IPC
Shared memory (mmap)Large data, needs synchronisation
Message queues (mq_open)Structured messages
SemaphoresCounter-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.

Finished reading? Mark it complete to track your progress.

On this page