What Are Pipes?

 

๐Ÿ“ก Pipes and FIFOs
Chapter 44 โ€” TLPI | Linux System Programming
Part 1 of 9
Pipe Introduction
Topic
IPC Fundamentals
Level
Intermediate

What Are Pipes?

A pipe is one of the oldest and most fundamental Inter-Process Communication (IPC) mechanisms in Unix/Linux. It allows two related processes to communicate โ€” one process writes data into the pipe, and the other reads from it.

Think of a pipe like a real water pipe: water flows in one end and comes out the other. Similarly, data written into one end of a pipe flows out the other end. Pipes are unidirectional โ€” data flows only in one direction.

You use pipes every day in the shell โ€” the | character creates a pipe between two commands, e.g., ls | grep .c.

Key Concepts in This File

pipe() File Descriptors Byte Stream Unidirectional IPC Kernel Buffer read()/write() Related Processes

๐Ÿ”ง How a Pipe Works โ€” Internals

When you call pipe(), the kernel creates an internal buffer (typically 65536 bytes on Linux) and gives you two file descriptors:

Pipe Internal Structure

โœ๏ธ
Process A
write end
fd[1]

โ†’

Kernel Buffer
64 KB (default)
FIFO order (queue)

โ†’

๐Ÿ“–
Process B
read end
fd[0]
fd[0] = Read end (index 0 = “zero โ†’ read”)
fd[1] = Write end (index 1 = “one โ†’ write”)
๐Ÿ“ Memory tip: fd[0] is for reading (0 = stdin = reading), fd[1] is for writing (1 = stdout = writing).

๐Ÿ“‹ Properties of Pipes
Property Description
Unidirectional Data flows in one direction only (write end โ†’ read end)
Byte stream No message boundaries โ€” continuous stream of bytes like a file
Kernel buffered Data stored in kernel memory, not on disk
Anonymous No name in filesystem โ€” only related processes can share via inheritance
FIFO order Bytes are read in the same order they were written
Blocking by default read() blocks if empty; write() blocks if full
Related processes only Only processes sharing a common ancestor can use anonymous pipes

โš™๏ธ The pipe() System Call
#include <unistd.h>

int pipe(int filedes[2]);
/* Returns 0 on success, -1 on error */

filedes[0] โ€” file descriptor open for reading from the pipe
filedes[1] โ€” file descriptor open for writing to the pipe

Data written to filedes[1] can be read from filedes[0].

Example 1: Basic pipe() usage โ€” write and read in same process

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

int main(void)
{
    int fd[2];
    char write_msg[] = "Hello from EmbeddedPathashala!";
    char read_buf[100];
    ssize_t bytes_read;

    /* Create the pipe */
    if (pipe(fd) == -1) {
        perror("pipe");
        return 1;
    }

    printf("pipe created: fd[0]=%d (read), fd[1]=%d (write)\n", fd[0], fd[1]);

    /* Write to the write end */
    write(fd[1], write_msg, strlen(write_msg));
    printf("Written: \"%s\"\n", write_msg);

    /* Read from the read end */
    bytes_read = read(fd[0], read_buf, sizeof(read_buf) - 1);
    read_buf[bytes_read] = '\0';
    printf("Read back: \"%s\"\n", read_buf);

    close(fd[0]);
    close(fd[1]);
    return 0;
}
Output:
pipe created: fd[0]=3 (read), fd[1]=4 (write)
Written: "Hello from EmbeddedPathashala!"
Read back: "Hello from EmbeddedPathashala!"

Example 2: Check pipe() creates two valid file descriptors

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

void print_fd_flags(int fd, const char *label)
{
    int flags = fcntl(fd, F_GETFL);
    printf("%-20s fd=%d  flags=0x%x (%s)\n",
           label, fd, flags,
           (flags & O_WRONLY) ? "WRITE-ONLY" :
           (flags & O_RDONLY) == O_RDONLY ? "READ-ONLY" : "READ-WRITE");
}

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

    if (pipe(fd) == -1) {
        perror("pipe");
        return 1;
    }

    print_fd_flags(fd[0], "fd[0] read end:");
    print_fd_flags(fd[1], "fd[1] write end:");

    close(fd[0]);
    close(fd[1]);
    return 0;
}
/* Output:
   fd[0] read end:    fd=3  flags=0x0  (READ-ONLY)
   fd[1] write end:   fd=4  flags=0x1  (WRITE-ONLY)
*/

๐ŸŒŠ Pipe as a Byte Stream

Pipes have no concept of message boundaries. If Process A writes 100 bytes, Process B can read them in any chunk size โ€” 10 bytes at a time, 50 bytes, or all 100 at once. The kernel does not preserve write boundaries.

Example 3: Byte-stream nature of pipes

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

#define READ_CHUNK 5   /* Read only 5 bytes at a time */

int main(void)
{
    int fd[2];
    char *msg = "HELLO_WORLD_PIPE";  /* 16 chars */
    char buf[READ_CHUNK + 1];
    ssize_t n;

    pipe(fd);

    /* Write all at once */
    write(fd[1], msg, strlen(msg));
    close(fd[1]);   /* Signal EOF after write */

    printf("Written %zu bytes at once: \"%s\"\n", strlen(msg), msg);
    printf("Reading %d bytes at a time:\n", READ_CHUNK);

    /* Read in chunks โ€” demonstrates byte-stream behavior */
    int chunk = 1;
    while ((n = read(fd[0], buf, READ_CHUNK)) > 0) {
        buf[n] = '\0';
        printf("  Chunk %d: \"%s\" (%zd bytes)\n", chunk++, buf, n);
    }

    close(fd[0]);
    return 0;
}
/* Output:
   Written 16 bytes at once: "HELLO_WORLD_PIPE"
   Reading 5 bytes at a time:
     Chunk 1: "HELLO" (5 bytes)
     Chunk 2: "_WORL" (5 bytes)
     Chunk 3: "D_PIP" (5 bytes)
     Chunk 4: "E" (1 bytes)
*/
Key observation: Even though we wrote 16 bytes as one call, the reader can receive them in pieces. This is the byte-stream nature โ€” no message boundaries are preserved.

๐Ÿ“Š Pipe vs FIFO โ€” Quick Comparison
Feature Pipe (Anonymous) FIFO (Named Pipe)
Filesystem name โŒ None โœ… Has a name in FS
Related processes โœ… Required (shared ancestor) โœ… Any process can use
Creation pipe() mkfifo()
Unidirectional โœ… โœ…
Kernel buffered โœ… โœ…
Shell usage cmd1 | cmd2 mkfifo myfifo

๐ŸŽฏ Interview Questions โ€” Pipe Basics
Q1. What is a pipe in Linux?
A: A pipe is an IPC mechanism that allows one-way data flow between related processes. It is implemented as a kernel buffer. The pipe() syscall returns two file descriptors โ€” fd[0] for reading and fd[1] for writing.
Q2. What does fd[0] and fd[1] represent after pipe()?
A: fd[0] is the read end; fd[1] is the write end. Data written to fd[1] is read from fd[0].
Q3. Is a pipe bidirectional?
A: No. A single pipe is unidirectional. For bidirectional communication, you need two pipes โ€” one for each direction.
Q4. What is the byte-stream nature of pipes?
A: Pipes do not preserve message boundaries. If 100 bytes are written in one write() call, the reader may receive them in multiple smaller reads. There is no concept of “packets” in a pipe.
Q5. Can unrelated processes communicate via an anonymous pipe?
A: No. Anonymous pipes can only be shared between related processes (parent-child, siblings, etc.) through file descriptor inheritance across fork(). FIFOs (named pipes) are used for unrelated processes.
Q6. What happens if you call read() on an empty pipe?
A: The read() call blocks (sleeps) until data is written into the pipe by another process, or until all write ends are closed (in which case read() returns 0 = EOF).

Leave a Reply

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