Pipes and FIFOs What is a Pipe? How does IPC work?

 

๐Ÿ“ฆ Pipes and FIFOs
Chapter 44 โ€” Part 1: What is a Pipe? How does IPC work?
๐Ÿ“– TLPI Ch.44
๐Ÿ”ง pipe() syscall
๐Ÿง Linux IPC
๐ŸŽฏ Section 44.1

What Will You Learn?

This tutorial covers the very first IPC (Inter-Process Communication) mechanism in UNIX history โ€” Pipes. Pipes have been in UNIX since the early 1970s (Third Edition UNIX). You will learn:

  • What a pipe is and how it works internally
  • Key properties of pipes: byte stream, unidirectional, PIPE_BUF atomicity, capacity
  • How the shell uses pipes (the ls | wc -l mechanism)
  • Difference between pipes and FIFOs

๐Ÿท๏ธ Key Terms

pipe() IPC byte stream PIPE_BUF atomic write unidirectional filedes[0] filedes[1] FIFO named pipe fork() read end / write end

1. What is a Pipe?

A pipe is a kernel-maintained buffer that lets two processes communicate. Data written by one process can be read by another. Think of it like a water pipe โ€” water (data) flows in one direction only.

Every Linux user has seen pipes in the shell:

$ ls | wc -l

Here the shell:

  1. Creates a pipe (two file descriptors: read end + write end)
  2. Forks two child processes: one runs ls, one runs wc
  3. Connects ls‘s stdout to the write end of the pipe (fd 1)
  4. Connects wc‘s stdin to the read end of the pipe (fd 0)

The two processes don’t even know a pipe exists. They just write to stdout and read from stdin as usual. The shell sets everything up.

๐Ÿ“Š How a Shell Pipe Works (ls | wc -l)
๐Ÿ“‚
ls
stdout (fd 1)
WRITE END โ†’ โ†’ โ†’
[ PIPE BUFFER ]
โ†’ โ†’ โ†’ READ END
kernel memory
๐Ÿ”ข
wc
stdin (fd 0)
Data flows in ONE direction only: ls writes โ†’ pipe โ†’ wc reads
๐Ÿ“ Where did “pipe” come from?
The name comes from physical plumbing. Just like water flows through a pipe from one place to another, data flows from one process to another โ€” in one direction only.

2. Key Properties of Pipes

2a. A Pipe is a Byte Stream

A pipe has no concept of messages or boundaries. It is just a continuous stream of bytes. This means:

  • Writer writes 100 bytes โ†’ Reader can read them as 10 reads of 10 bytes each, or 1 read of 100 bytes. Both work.
  • There are no “message separators” built in.
  • You cannot use lseek() on a pipe โ€” no random access.
  • Bytes come out in the exact order they were written (FIFO order).
๐Ÿ’ก Analogy: Think of a pipe like a garden hose. You pour water in one end โ€” it comes out the other end in the same order. You can’t skip around.

2b. Pipes are Unidirectional

Data can only flow in one direction. One end is for writing (fd[1]), the other is for reading (fd[0]). You cannot write to the read end or read from the write end.

fd[1]
Write End
โœ๏ธ write()
โ†’โ†’โ†’โ†’โ†’
PIPE
kernel buffer
65536 bytes (Linux)
โ†’โ†’โ†’โ†’โ†’
fd[0]
Read End
๐Ÿ‘๏ธ read()
๐Ÿ“ Note on Bidirectional Pipes: Some old UNIX systems (System V Release 4) had bidirectional pipes, but these are not standard. On Linux, if you need bidirectional communication, use socketpair() to create UNIX domain socket pairs instead.

2c. Reading Behavior

Two important rules when reading from a pipe:

  • Empty pipe: If you try to read from an empty pipe, read() blocks until some data is written.
  • Write end closed: If all processes that have the write end open have closed it, then read() returns 0 (EOF) after all remaining data is consumed.

2d. Writes up to PIPE_BUF are Atomic

If multiple processes write to the same pipe, the kernel guarantees that writes of up to PIPE_BUF bytes will not be interleaved with other writes. This is called atomic write.

System PIPE_BUF Value
Linux 4096 bytes
FreeBSD 6.0 512 bytes
Solaris 8 5120 bytes
Tru64 5.1 4096 bytes
POSIX minimum 512 bytes (_POSIX_PIPE_BUF)
๐Ÿ’ก What does atomic mean here?
If Process A writes 100 bytes and Process B writes 200 bytes to the same pipe, and both writes are < PIPE_BUF (4096), then the reader will see either A’s 100 bytes or B’s 200 bytes โ€” never a mix of A’s and B’s data. Without atomicity, bytes from A and B could get shuffled together, corrupting data.

If you write more than PIPE_BUF bytes:

  • The kernel may split the write into smaller pieces
  • Other writers can interleave their data between your pieces
  • This is fine for a single writer, but dangerous for multiple writers

2e. Pipe Capacity (Kernel Buffer Size)

A pipe is a buffer in kernel memory. It has a finite size:

  • Linux < 2.6.11: capacity = system page size (4096 bytes on x86)
  • Linux โ‰ฅ 2.6.11: capacity = 65,536 bytes (64 KB)

If the pipe is full, write() blocks until the reader removes some data.

From Linux 2.6.35, you can change pipe capacity using:

/* Set pipe capacity to at least 'size' bytes */
fcntl(fd, F_SETPIPE_SZ, size);

/* Get current pipe capacity */
int actual_size = fcntl(fd, F_GETPIPE_SZ);
๐Ÿ“ Max pipe size: By default, unprivileged processes can set pipe size up to /proc/sys/fs/pipe-max-size (default: 1,048,576 = 1 MB). Root/CAP_SYS_RESOURCE can override this.

3. Coding Example 1 โ€” Demonstrating Pipe Byte Stream Behavior

This example shows that a pipe is a byte stream: the reader can read in smaller chunks than the writer wrote.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int pfd[2];   /* pfd[0] = read end, pfd[1] = write end */
    char buf[10]; /* reader reads only 10 bytes at a time */
    ssize_t n;

    /* Step 1: Create the pipe */
    if (pipe(pfd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    /* Step 2: Write 30 bytes in one shot */
    const char *msg = "Hello from pipe! This is 30B!";
    printf("[writer] Writing %zu bytes to pipe\n", strlen(msg));
    write(pfd[1], msg, strlen(msg));

    /* Step 3: Close write end so reader gets EOF eventually */
    close(pfd[1]);

    /* Step 4: Read 10 bytes at a time - shows byte stream behavior */
    int chunk = 1;
    while ((n = read(pfd[0], buf, sizeof(buf) - 1)) > 0) {
        buf[n] = '\0';
        printf("[reader] chunk %d: read %zd bytes: \"%s\"\n", chunk++, n, buf);
    }

    /* n == 0 means EOF (write end was closed) */
    printf("[reader] EOF reached (write end closed)\n");

    close(pfd[0]);
    return 0;
}

/*
Expected output:
[writer] Writing 29 bytes to pipe
[reader] chunk 1: read 9 bytes: "Hello fro"
[reader] chunk 2: read 9 bytes: "m pipe! T"
[reader] chunk 3: read 9 bytes: "his is 30"
[reader] chunk 4: read 2 bytes: "B!"
[reader] EOF reached (write end closed)
*/

Key observations:

  • Writer wrote 29 bytes in one write() call
  • Reader read them in 10-byte chunks โ€” pipe doesn’t care about message boundaries
  • When write end is closed, read() returns 0 (EOF)

4. Coding Example 2 โ€” Checking PIPE_BUF Value

This example shows how to programmatically get the PIPE_BUF value and the actual pipe capacity on your system.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>   /* for PIPE_BUF */
#include <fcntl.h>    /* for F_GETPIPE_SZ */

int main(void)
{
    int pfd[2];

    /* Show compile-time PIPE_BUF constant */
    printf("PIPE_BUF (compile-time constant) = %d bytes\n", PIPE_BUF);

    /* Create pipe and check runtime capacity */
    if (pipe(pfd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    /* F_GETPIPE_SZ: Linux-specific, get actual allocated pipe size */
    int pipe_sz = fcntl(pfd[0], F_GETPIPE_SZ);
    if (pipe_sz == -1) {
        perror("fcntl F_GETPIPE_SZ");
    } else {
        printf("Actual pipe capacity (F_GETPIPE_SZ) = %d bytes\n", pipe_sz);
    }

    /* Try to set a larger pipe size */
    int desired = 131072; /* 128 KB */
    int result = fcntl(pfd[1], F_SETPIPE_SZ, desired);
    if (result == -1) {
        perror("fcntl F_SETPIPE_SZ");
    } else {
        printf("Requested %d bytes, kernel allocated %d bytes\n",
               desired, fcntl(pfd[0], F_GETPIPE_SZ));
    }

    /* fpathconf: POSIX way to get PIPE_BUF for this fd */
    long pbuf = fpathconf(pfd[0], _PC_PIPE_BUF);
    printf("fpathconf PIPE_BUF for this pipe = %ld bytes\n", pbuf);

    close(pfd[0]);
    close(pfd[1]);
    return 0;
}

/*
Sample output on Linux:
PIPE_BUF (compile-time constant) = 4096 bytes
Actual pipe capacity (F_GETPIPE_SZ) = 65536 bytes
Requested 131072 bytes, kernel allocated 131072 bytes
fpathconf PIPE_BUF for this pipe = 4096 bytes
*/
๐Ÿ“ PIPE_BUF vs Pipe Capacity:

  • PIPE_BUF (4096): The threshold for atomic writes. Writes โ‰ค 4096 bytes are guaranteed not to be interleaved.
  • Pipe capacity (65536): The total buffer size. The pipe blocks writes when full.
  • These are two different things! PIPE_BUF is about atomicity; pipe capacity is about how much data can be buffered.

5. What is a FIFO (Named Pipe)?

A FIFO (First In, First Out) is also called a named pipe. It has all the same properties as a regular pipe, but with one major difference:

Feature Anonymous Pipe FIFO (Named Pipe)
Created with pipe() mkfifo() or mknod()
Visible in filesystem No Yes (has a path like /tmp/myfifo)
Between related processes? Must be related (parent-child) Any two processes on the same system
Lifetime Ends when both fds closed Persists until explicitly deleted
Direction Unidirectional Unidirectional
๐Ÿ’ก Why does “related processes” matter for pipes?
For a regular pipe, both processes need to have inherited the same file descriptors โ€” which only happens when one process is created by fork() from another (parent-child relationship). FIFOs don’t have this restriction โ€” any two unrelated processes can open the same FIFO path.

๐ŸŽฏ Interview Questions โ€” Pipes Overview
Q1. What is a pipe in Linux? How is it different from a file?
A pipe is a kernel-maintained byte-stream buffer for IPC between processes. Unlike a file, a pipe has no name in the filesystem (for anonymous pipes), is not persistent, does not support lseek(), and is unidirectional. Data written flows sequentially and can only be read once.
Q2. What does PIPE_BUF guarantee? What is its value on Linux?
PIPE_BUF guarantees that writes of up to PIPE_BUF bytes will be atomic โ€” not interleaved with writes from other processes. On Linux, PIPE_BUF = 4096 bytes. Writes larger than PIPE_BUF may be split and interleaved.
Q3. What happens when you read from an empty pipe? What if the write end is closed?
Reading from an empty pipe blocks until data is written. If the write end is closed and no data remains, read() returns 0 (EOF).
Q4. What is the default pipe capacity on Linux โ‰ฅ 2.6.11?
65,536 bytes (64 KB). Before Linux 2.6.11 it was the page size (4096 bytes). Starting from Linux 2.6.35, capacity can be changed using fcntl(fd, F_SETPIPE_SZ, size).
Q5. Why can’t you use lseek() on a pipe?
Pipes are sequential byte streams maintained as kernel ring buffers. There is no notion of a file offset or random access position. Data is consumed in FIFO order โ€” once read, it’s gone. This is by design for IPC, not storage.
Q6. What is the difference between a pipe and a FIFO?
A pipe (anonymous pipe) has no filesystem name and can only be used between related processes (parent-child via fork). A FIFO (named pipe) has a pathname in the filesystem, so any two unrelated processes can open it. Both are unidirectional byte streams.
Q7. If Process A writes 8000 bytes to a pipe and Process B also writes 8000 bytes simultaneously, can their data be interleaved?
Yes. Since 8000 > PIPE_BUF (4096), neither write is guaranteed to be atomic. The kernel may split each write and interleave data from A and B. To avoid interleaving with multiple writers, keep each write โ‰ค PIPE_BUF bytes.

Continue Learning

Next: Creating pipes with pipe(), using fork() to connect two processes

โ†’ Part 2: Creating and Using Pipes ๐Ÿ“‹ Chapter Index

Leave a Reply

Your email address will not be published. Required fields are marked *