Pipes - Most minimal form of inter process communication

Published: (January 10, 2026 at 02:28 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

When you need to transfer data between two processes, direct access is not allowed by the operating system. This isolation protects one process from interfering with another (e.g., preventing a malicious program from reading your payment information). To share information safely, processes agree on a communication method. This article focuses on pipes, which are a form of inter‑process communication (IPC). Pipes are not network programming, although they are often a stepping stone toward learning sockets.

Using pipes in the terminal

Pipes can connect the output of one command to the input of another:

$ ls /bin | grep system | more

The shell creates an anonymous pipe, connects the standard output of ls to the standard input of grep, and then connects grep to more.

Anonymous pipes in programs

In programs, anonymous pipes can be used only between processes that share a common ancestor (typically a parent and its child created with fork()). The pipe’s file descriptors exist only in the memory space of the process that created them, so unrelated processes cannot access them.

Example program (C)

#include 
#include 
#include 
#include 
#include 
#include 

int main(void)
{
    int pipefd[2];
    pid_t child_pid;
    const char *payload = "Hello pipes";
    char child_buffer;
    char child_payload[12] = {0};

    if (pipe(pipefd) == -1)
        err(EXIT_FAILURE, "pipe");

    child_pid = fork();
    if (child_pid == -1)
        err(EXIT_FAILURE, "fork");

    if (child_pid == 0) {               // Child process
        close(pipefd[1]);               // Close write end
        printf("Reading payload from parent...\n");
        while (read(pipefd[0], &child_buffer, 1) > 0) {
            strncat(child_payload, &child_buffer, 1);
        }
        printf("%s\n", child_payload);
        close(pipefd[0]);
        exit(EXIT_SUCCESS);
    } else {                            // Parent process
        close(pipefd[0]);               // Close read end
        printf("Writing payload to child process...\n");
        if (write(pipefd[1], payload, strlen(payload)) == -1)
            err(EXIT_FAILURE, "write");
        if (close(pipefd[1]) == -1)
            err(EXIT_FAILURE, "close");
        if (wait(NULL) == -1)            // Wait for child to exit
            err(EXIT_FAILURE, "wait");
        exit(EXIT_SUCCESS);
    }
}

How it works

  1. pipe(pipefd) creates a pipe with two file descriptors:

    • pipefd[0] – read end
    • pipefd[1] – write end
  2. fork() creates a child process. Both processes inherit the pipe descriptors.

  3. The parent closes the read end (pipefd[0]) and writes the string "Hello pipes" to the write end (pipefd[1]).

  4. The child closes the write end (pipefd[1]) and reads from the read end (pipefd[0]) one byte at a time, assembling the message.

  5. Each process closes the descriptor it does not use; failing to do so can cause deadlocks because the reading side would never see an end‑of‑file condition.

  6. The parent waits for the child to finish before exiting.

Building and running

$ gcc -o main main.c
$ ./main

The program prints:

Writing payload to child process...
Reading payload from parent...
Hello pipes

Feel free to discuss or ask questions about the implementation.

Back to Blog

Related posts

Read more »