Memra

A pipe with a name on disk

◈ 9 cards

Create and use a FIFO with mkfifo, explain why the reader has to be started first, and say exactly what a name buys you over an ordinary pipe.

The limitation a name removes

An ordinary pipe has two restrictions you cannot argue with. It has no name, so nothing can refer to it; and the processes it joins must share an ancestor, because the only way to get hold of one end is to inherit it. That rules out the obvious case: a program started this morning wanting to send data to a program started this afternoon by another user.

A FIFO — a named pipe — removes both restrictions by giving the pipe a pathname:

mkfifo mypipe
ls -l mypipe

The listing shows type p in the first column and size 0, and the size stays 0 no matter how much data flows through it. That is the fact to hold on to: the directory entry is real and persistent, but the data still lives in a kernel buffer exactly as a pipe's does. A FIFO is a name attached to a pipe, not a file that behaves like one.

With the name in place, any two processes that can see the pathname and have the right permissions can use it — related or not, started hours apart, running as different users.

Worked example: the reader must go first

mkfifo mypipe
cat mypipe &
ls -l > mypipe

Line 2 starts a reader in the background, and it immediately blocks: opening a FIFO for reading does not return until some process opens the other end for writing. Line 3 is that writer, so the cat wakes up, the listing flows through the buffer, and it appears on your screen.

Now drop the &:

mkfifo mypipe
cat mypipe

Your shell hangs. Not because anything is broken — cat is blocked inside open(), waiting for a writer that can never appear, because the shell that would have to start it is the shell now waiting on cat. Ctrl+C, and put the reader in the background.

This blocking is the behaviour that surprises people, and it is worth stating in the form that makes it obvious: opening a FIFO blocks until both ends are open. Opening for reading waits for a writer; opening for writing waits for a reader. Reading an empty ordinary file, by contrast, returns end-of-file at once — an empty file has no other end to wait for.

Two writers, one reader

Start one reader in the background and then run two separate writers into the same FIFO. Both succeed, both sets of bytes reach the single reader, and the reader sees one merged stream — but the order in which the two contributions interleave is not defined. It depends on how the kernel happens to schedule the two writers. Small writes tend to arrive whole, because a write up to PIPE_BUF bytes is atomic, but which whole write arrives first is a race.

The same applies in the shape the chapter's own example uses: one producer feeding several consumers through tee and several FIFOs. Every consumer gets its data; the order the consumers' outputs appear on your screen is a race. If the ordering matters, you must impose it — by having one consumer, or by collecting into files and combining them afterwards.

Cleaning up

A FIFO is a filesystem object, so it survives every process that used it, and it survives a reboot. It goes away when you rm it, or when the filesystem holding it is unmounted — never on its own. An ordinary pipe needs no cleanup at all, because it stops existing the moment its last descriptor is closed.

pipeFIFOdata lives ina kernel buffera kernel buffer (same)has a name?noyes, a pathnamewho may use itonly processes sharing anancestorany process that can seethe namelifetimedies with its lastdescriptorpersists in the file systemcreated bythe shell pipe bar, orpipe()mkfifo command, or mkfifo()removed bynothing to removerm, or unlink()ls -l shows a FIFO as type p, size 0, always.
Only one row is about the data. A FIFO buys a *name* — everything else in the right-hand column follows from having one.
NORMAL ~/memra/learn/comp-325/fifos-at-the-shell-level utf-8 LF