How to Use Named Pipes (FIFOs) for IPC in C
Named pipes, also known as FIFOs (First-In, First-Out), provide a
robust mechanism for inter-process communication (IPC) between unrelated
local processes on Unix-like operating systems. Unlike standard
anonymous pipes, which require a parent-child relationship established
through fork(), a named pipe exists as a special device
file in the filesystem. This article explains how named pipes operate in
the C programming language, demonstrating how independent programs can
open, read, and write to a shared FIFO using standard system-level file
operations.
The Concept of a FIFO in Unix
A named pipe behaves similarly to a standard pipe: data written to one end is read from the other in strict sequential order. However, because it has an entry in the filesystem directory tree, any process with appropriate permissions can access it using its path. The data itself does not reside on disk; the filesystem entry serves merely as an access point or reference handle, while the operating system kernel manages the communication buffer in memory.
Creating a Named Pipe
A named pipe is created programmatically in C using the
mkfifo() function, declared in
<sys/stat.h>.
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);The pathname parameter defines where the FIFO will exist
in the filesystem, and mode sets file permissions (subject
to the calling process's umask). If the FIFO already
exists, mkfifo() returns -1 and sets
errno to EEXIST.
Inter-Process Communication Mechanism
Once created, processes interact with the FIFO using standard POSIX
system calls: open(), read(),
write(), and close().
By default, opening a named pipe blocks until both the read and write ends are open:
- A process opening a FIFO with
O_RDONLYwill block until another process opens it withO_WRONLY. - Conversely, a process opening the FIFO with
O_WRONLYwill block until a reading process connects.
This synchronization behavior ensures that data is not written into a void and that a reader does not spin idly waiting for an initial connection.
Implementation: The Writer Process
The writer process creates the pipe (or assumes it exists), opens it for writing, sends data, and closes the descriptor.
// writer.c
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
int main() {
const char *fifo_path = "/tmp/my_fifo";
// Create the FIFO if it doesn't exist
mkfifo(fifo_path, 0666);
printf("Writer: Waiting for reader...\n");
int fd = open(fifo_path, O_WRONLY);
char message[] = "Hello from the unrelated writer process!";
write(fd, message, strlen(message) + 1);
close(fd);
printf("Writer: Data sent and connection closed.\n");
return 0;
}Implementation: The Reader Process
The reader process opens the same path for reading, retrieves the data from the kernel buffer, and processes it.
// reader.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
const char *fifo_path = "/tmp/my_fifo";
char buffer[128];
printf("Reader: Waiting for writer...\n");
int fd = open(fifo_path, O_RDONLY);
ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read > 0) {
printf("Reader received: %s\n", buffer);
}
close(fd);
return 0;
}Lifecycle and Cleanup
When all writer descriptors are closed, any subsequent
read() call by the reader returns 0, signaling
End-of-File (EOF). If a process attempts to write to a named pipe that
has no readers open, the kernel sends a SIGPIPE signal to
the writer, which terminates the process unless handled or ignored.
The FIFO special file remains in the filesystem after the
communicating processes terminate. To clean up the resource, a process
can remove it using the unlink() system call:
unlink("/tmp/my_fifo");