Anonymous Pipes in C: Parent and Child Process IPC
This article explains how to create and use anonymous unidirectional
pipes for inter-process communication (IPC) between a parent and child
process in the C programming language. It details the mechanics of the
pipe() and fork() system calls, illustrates
how file descriptors are inherited and managed to enforce one-way
communication, and provides a concise code example demonstrating
practical implementation.
Understanding the
pipe() System Call
An anonymous pipe is a unidirectional data channel managed by the
operating system kernel. In C (on POSIX-compliant systems), an anonymous
pipe is created using the pipe() system call, defined in
<unistd.h>.
The function takes an array of two integers as an argument:
int pipe(int pipefd[2]);When successful, pipe() allocates two new file
descriptors and populates the array:
pipefd[0]is opened for reading.pipefd[1]is opened for writing.
Data written to pipefd[1] is buffered by the kernel
until it is read from pipefd[0]. The call returns
0 on success and -1 on error.
Inheriting Descriptors with
fork()
Because anonymous pipes lack names in the filesystem, they can only
be shared between related processes. When a parent process calls
fork(), the kernel creates a child process that receives
duplicate copies of all the parent's open file descriptors, including
pipefd[0] and pipefd[1].
To establish a pipe:
- The parent must call
pipe()before callingfork(). - The parent calls
fork(). - Both processes now hold references to both ends of the pipe.
Enforcing Unidirectional Flow
Although both processes initially have read and write capabilities, standard anonymous pipes are designed for unidirectional communication. To enforce this and avoid undefined behavior or deadlocks:
- Decide the direction of communication: For example, parent (sender) to child (receiver).
- Close unused ends in both processes:
- The parent closes the read end
(
pipefd[0]) because it will only write. - The child closes the write end
(
pipefd[1]) because it will only read.
- The parent closes the read end
(
Closing the unused write end in the reading process is critical. A
read() operation returns 0 (End-of-File) only
when all open write descriptors for the pipe are closed. If the reader
leaves its own write descriptor open, read() will block
indefinitely waiting for further input.
Implementation Example
The following program demonstrates a parent process sending a text string to its child process through an anonymous pipe:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define BUFFER_SIZE 128
int main(void) {
int pipefd[2];
pid_t pid;
char message[] = "Hello from the parent process!";
char buffer[BUFFER_SIZE];
// 1. Create the pipe
if (pipe(pipefd) == -1) {
perror("pipe failed");
exit(EXIT_FAILURE);
}
// 2. Fork the child process
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// Child process: reads from the pipe
close(pipefd[1]); // Close unused write end
ssize_t bytes_read = read(pipefd[0], buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0'; // Null-terminate string
printf("Child received: %s\n", buffer);
}
close(pipefd[0]); // Close read end after reading
exit(EXIT_SUCCESS);
} else {
// Parent process: writes to the pipe
close(pipefd[0]); // Close unused read end
write(pipefd[1], message, strlen(message));
close(pipefd[1]); // Close write end to send EOF to child
wait(NULL); // Wait for child to terminate
exit(EXIT_SUCCESS);
}
}Summary of Operations
pipe(pipefd)creates read/write descriptors.fork()duplicates those descriptors into a new child process.- Each process immediately closes the descriptor it will not use.
- The sender uses
write()to send byte streams into the pipe. - The receiver uses
read()to consume byte streams until0(EOF) is returned. - Both processes close their active descriptors to free system resources.