Beej's Guide to Unix IPC — Chapter 5

FIFOs — Named Pipes

Pipes that live on disk — letting unrelated processes communicate through a named file.

Prerequisites: Pipes (Ch. 4) + File descriptors. That's it.
9
Chapters
3+
Simulations
0
Assumed Knowledge

Chapter 0: Why Named Pipes?

In the last chapter, you learned about pipes. They're great — but they have a fatal limitation: both processes must be related. You need to call pipe() before fork(), so only parent and child can share the connection. Two completely unrelated programs can't use them.

What if you want two independent programs — maybe a logger daemon and a monitoring tool — to communicate? You need a pipe that has a name on the filesystem, so both programs can find it by opening the same file.

That's exactly what a FIFO is. "First In, First Out" (pronounced "Fy-Foh"). Also called a named pipe. It looks like a regular file on disk, but it acts like a pipe: data written to it can be read from the other end, in order, and it doesn't actually store data on disk.

The mental model: Imagine a mailbox mounted on the wall between two apartments. Either tenant can drop a letter in or pick one up. They don't need to be related — they just need to know the address. A FIFO is that mailbox for processes.
FIFO vs Pipe

Click "Send" to push data through the FIFO. Unlike anonymous pipes, any process that knows the filename can connect.

FIFO: empty
What is the main advantage of FIFOs over anonymous pipes?

Chapter 1: Creating a FIFO

Since a FIFO is a special file on disk, you have to create it before any process can open it. There are two ways: from your C code with mknod(), or from the command line with mkfifo.

c
/* Create a FIFO from C code */
mknod("myfifo", S_IFIFO | 0644, 0);

The first argument is the filename. The second is the creation mode: S_IFIFO tells the kernel "make this a FIFO" and 0644 sets read/write permissions (just like chmod). The third argument is a device number — ignored for FIFOs, so pass 0.

From the command line, it's even simpler:

shell
$ mkfifo myfifo
$ ls -l myfifo
prw-r--r--  1 beej  beej  0 Jan 1 00:00 myfifo

Notice the p at the start of the permissions — that means "pipe." The file has zero bytes because FIFOs don't store data on disk. They're conduits, not containers.

Key insight: After creating the FIFO, any process can open() it for reading or writing using the filename. That's the whole point — the name on the filesystem is what lets unrelated processes find each other.
What does S_IFIFO tell mknod()?

Chapter 2: speak.c — The Writer

Let's build Beej's classic producer-consumer example. The writer process (speak.c) creates the FIFO and sends data into it. Here's the code:

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

#define FIFO_NAME "american_maid"

int main(void)
{
    char s[300];
    int num, fd;

    mknod(FIFO_NAME, S_IFIFO | 0666, 0);

    printf("waiting for readers...\n");
    fd = open(FIFO_NAME, O_WRONLY);    /* BLOCKS here */
    printf("got a reader--type some stuff\n");

    while (fgets(s, 300, stdin) != NULL) {
        if ((num = write(fd, s, strlen(s))) == -1)
            perror("write");
        else
            printf("speak: wrote %d bytes\n", num);
    }
    return 0;
}
Critical detail: The open(FIFO_NAME, O_WRONLY) call will block until another process opens the other end for reading. speak just sits there, frozen, displaying "waiting for readers..." until tick starts up. This is by design — you can't pour water into a pipe with no other end.

Notice that mknod() is called every time. If the FIFO already exists, mknod() fails silently (returns -1 with errno set to EEXIST), which is fine — the FIFO is already there.

What happens when speak calls open() with O_WRONLY on the FIFO?

Chapter 3: tick.c — The Reader

Now the consumer side. tick.c opens the same FIFO for reading and sucks out whatever speak puts in:

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

#define FIFO_NAME "american_maid"

int main(void)
{
    char s[300];
    int num, fd;

    mknod(FIFO_NAME, S_IFIFO | 0666, 0);

    printf("waiting for writers...\n");
    fd = open(FIFO_NAME, O_RDONLY);     /* BLOCKS here */
    printf("got a writer\n");

    do {
        if ((num = read(fd, s, 300)) == -1)
            perror("read");
        else {
            s[num] = '\0';
            printf("tick: read %d bytes: \"%s\"\n", num, s);
        }
    } while (num > 0);

    return 0;
}

The key sequence is: start speak in one terminal (it blocks on open), then start tick in another (it also blocks on open). The moment both sides are open, data flows. Type in the speak window, and tick prints it out.

speak (Writer)
open(FIFO, O_WRONLY) → write(fd, data, len)
↓ bytes flow through named pipe on disk
tick (Reader)
open(FIFO, O_RDONLY) → read(fd, buf, len)
What happens when speak exits? The read() in tick returns 0 (EOF), so tick knows all writers have closed. Conversely, if tick exits while speak is still running, speak gets a SIGPIPE signal and prints "Broken Pipe." You can handle this gracefully with a signal handler.
What does read() return when all writers close their end of the FIFO?

Chapter 4: Blocking Behavior

By default, FIFOs block in two situations, and this blocking is the core synchronization mechanism:

open() for writing blocks until at least one reader has opened the FIFO. You can't push data into a pipe with no other end.
open() for reading blocks until at least one writer has opened the FIFO. No point reading from a pipe nobody is writing to.

After both sides are connected:

This makes FIFOs self-synchronizing. The producer and consumer naturally pace each other, like a conveyor belt that pauses when either end stops.

OperationBlocks when...Returns when...
open(O_WRONLY)No readers yetA reader opens the FIFO
open(O_RDONLY)No writers yetA writer opens the FIFO
read()Buffer empty, writers existData arrives or all writers close
write()Buffer fullReader drains some data
When does open(FIFO, O_WRONLY) return?

Chapter 5: O_NDELAY — Non-Blocking Mode

Sometimes you don't want open() to block. Maybe you want to check if a reader/writer exists without freezing your whole program. The O_NDELAY flag (also called O_NONBLOCK) changes the blocking behavior:

c
/* Non-blocking open for reading */
fd = open(FIFO_NAME, O_RDONLY | O_NDELAY);

The effects depend on which side you're on:

SideNormal (blocking)With O_NDELAY
Writer open()Blocks until reader existsReturns -1 immediately if no reader
Reader open()Blocks until writer existsReturns immediately (success)
Reader read()Blocks until data arrivesReturns 0 if no data in pipe
The catch: With O_NDELAY on the reader, read() returns 0 both when there's no data and when the writer has exited (EOF). You can no longer distinguish between "no data yet" and "writer is gone." Beej's advice: stick with blocking whenever possible.
What is the downside of using O_NDELAY on the reader?

Chapter 6: Multiple Writers

Here's something pipes can't do: multiple unrelated processes can write to the same FIFO. Since the FIFO is a file on disk, any process with the right permissions can open it.

Start one tick (reader) and two or three speaks (writers) in different terminals. Type in any writer window, and the reader gets all the messages. When one writer exits, the reader keeps going — it only gets EOF when all writers close their end.

Key insight: The kernel reference-counts the open file descriptors. read() returns 0 (EOF) only when the writer count drops to zero. As long as at least one writer has the FIFO open, read() will block waiting for data.
speak A
write(fd, "hello", 5)
speak B
write(fd, "world", 5)
↓ both feed into the same FIFO
tick (Reader)
read(fd, buf, 300)

What about multiple readers? That gets weird. Sometimes one reader gets everything, sometimes messages alternate unpredictably between readers. The kernel doesn't guarantee fair distribution. In practice, you usually want a single reader.

When does a FIFO reader get EOF (read returns 0)?

Chapter 7: FIFO Producer-Consumer Simulator

Watch messages flow through a named pipe. Multiple producers can feed into the FIFO, and the consumer reads them in order. Notice how data arrives in FIFO order regardless of which producer sent it.

Named Pipe Message Flow

Click a producer to send a message. The consumer processes messages in the order they arrive in the FIFO buffer.

Speed 5
Can two unrelated processes (not parent-child) communicate through a FIFO?

Chapter 8: Beyond FIFOs

FIFOs solve the "unrelated processes" problem that pipes couldn't. But they're still just byte streams — there's no concept of message boundaries or types. If you need to send structured, typed messages with priority retrieval, you want message queues.

FeaturePipesFIFOsMessage Queues
ProcessesRelated onlyAnyAny
PersistenceIn-memoryFile on diskKernel-managed
Message typesNoNoYes (mtype field)
PriorityNoNoYes (retrieve by type)
DirectionOne-wayOne-wayOne-way (per queue)
Coming up: File locking for coordinating access to shared files, message queues for typed messages, semaphores for synchronization, and shared memory for raw speed.

"Having the name of the pipe right there on disk sure makes it easier, doesn't it?" — Beej

What do FIFOs lack that message queues provide?