Pipe Introduction
IPC Fundamentals
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
When you call pipe(), the kernel creates an internal buffer (typically 65536 bytes on Linux) and gives you two file descriptors:
fd[0] is for reading (0 = stdin = reading), fd[1] is for writing (1 = stdout = writing).| 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 |
#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;
}
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)
*/
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)
*/
| 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 |
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.A:
fd[0] is the read end; fd[1] is the write end. Data written to fd[1] is read from fd[0].A: No. A single pipe is unidirectional. For bidirectional communication, you need two pipes โ one for each direction.
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.
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.
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).
