FIFO Semantics
open() behavior
Intermediate+
FIFO open() โ A Rendezvous Point
Opening a FIFO is special โ it acts as a rendezvous between the reader and writer. The kernel makes both processes wait at the open() call until both ends are connected. This provides implicit synchronization.
Understanding the exact rules for blocking vs nonblocking FIFO opens is important for avoiding race conditions and unexpected program hangs.
Key Concepts
| Flags | No matching open on other end | Matching open exists |
|---|---|---|
| O_RDONLY (blocking) | Blocks until a writer opens | Returns immediately |
| O_WRONLY (blocking) | Blocks until a reader opens | Returns immediately |
| O_RDONLY | O_NONBLOCK | Returns immediately (fd valid) | Returns immediately |
| O_WRONLY | O_NONBLOCK | Fails: errno = ENXIO | Returns immediately |
| O_RDWR | Returns immediately (single process holds both ends) | Returns immediately |
/* fifo_open_demo.c โ shows blocking open() */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#define FIFO_PATH "/tmp/ep_blocking_fifo"
int main(void)
{
mkfifo(FIFO_PATH, 0666);
pid_t pid = fork();
if (pid == 0) {
/* Child: waits 2 seconds, then opens as writer */
printf("[Child] Sleeping 2 seconds before opening as writer...\n");
sleep(2);
printf("[Child] Opening FIFO as writer (O_WRONLY)...\n");
int fd = open(FIFO_PATH, O_WRONLY);
printf("[Child] Writer opened! Sending data...\n");
const char *msg = "Sent from child after 2-second delay";
write(fd, msg, strlen(msg));
close(fd);
_exit(0);
} else {
/* Parent: opens as reader โ will BLOCK for ~2 seconds */
printf("[Parent] Opening FIFO as reader (O_RDONLY)...\n");
printf("[Parent] Blocking now โ waiting for writer...\n");
time_t t1 = time(NULL);
int fd = open(FIFO_PATH, O_RDONLY); /* BLOCKS HERE ~2 seconds */
time_t t2 = time(NULL);
printf("[Parent] Reader opened after ~%ld second(s) wait!\n", t2 - t1);
char buf[128];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
buf[n] = '\0';
printf("[Parent] Received: \"%s\"\n", buf);
close(fd);
wait(NULL);
}
unlink(FIFO_PATH);
return 0;
}
/* Output:
[Parent] Opening FIFO as reader (O_RDONLY)...
[Parent] Blocking now โ waiting for writer...
[Child] Sleeping 2 seconds before opening as writer...
[Child] Opening FIFO as writer (O_WRONLY)...
[Child] Writer opened! Sending data...
[Parent] Reader opened after ~2 second(s) wait!
[Parent] Received: "Sent from child after 2-second delay"
*/
/* fifo_nonblock_open.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#define FIFO_PATH "/tmp/ep_nonblock_fifo"
int main(void)
{
mkfifo(FIFO_PATH, 0666);
printf("=== Test 1: O_RDONLY | O_NONBLOCK (no writer) ===\n");
int fd_r = open(FIFO_PATH, O_RDONLY | O_NONBLOCK);
if (fd_r == -1)
printf("Reader open failed: %s\n", strerror(errno));
else
printf("Reader opened immediately (fd=%d) โ no blocking even without writer!\n", fd_r);
printf("\n=== Test 2: O_WRONLY | O_NONBLOCK (no reader) ===\n");
/* NOTE: We now have fd_r open as reader, so writer should succeed */
/* Let's first test without a reader: close fd_r temporarily */
/* Actually since we have fd_r open, writer will succeed */
int fd_w = open(FIFO_PATH, O_WRONLY | O_NONBLOCK);
if (fd_w == -1) {
if (errno == ENXIO)
printf("Writer open failed with ENXIO (no reader present)\n");
else
printf("Writer open failed: %s\n", strerror(errno));
} else {
printf("Writer opened (fd=%d) โ reader was present\n", fd_w);
}
printf("\n=== Test 3: Send/receive data using nonblocking fds ===\n");
if (fd_r != -1 && fd_w != -1) {
const char *msg = "Nonblocking FIFO test";
write(fd_w, msg, strlen(msg));
char buf[64];
ssize_t n = read(fd_r, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Read from FIFO: \"%s\"\n", buf);
}
}
if (fd_r != -1) close(fd_r);
if (fd_w != -1) close(fd_w);
unlink(FIFO_PATH);
return 0;
}
Just like anonymous pipes, a FIFO reader sees EOF (read returns 0) when all write-end descriptors are closed. But FIFOs have an extra nuance: the FIFO can be re-opened by a new writer after EOF, unlike anonymous pipes which are destroyed.
Example 3: FIFO EOF and re-open behavior
/* fifo_eof_reopen.c โ demonstrate FIFO can be used again after EOF */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#define FIFO_PATH "/tmp/ep_reopen_fifo"
#define BUF_SIZE 128
int main(void)
{
mkfifo(FIFO_PATH, 0666);
pid_t pid = fork();
if (pid == 0) {
/* ===== CHILD: writer, writes twice ===== */
printf("[Writer] First write session\n");
int fd = open(FIFO_PATH, O_WRONLY);
write(fd, "First message", 13);
close(fd); /* Close โ reader sees EOF */
printf("[Writer] Closed. Sleeping 1 second...\n");
sleep(1);
printf("[Writer] Second write session (FIFO re-opened)\n");
fd = open(FIFO_PATH, O_WRONLY);
write(fd, "Second message", 14);
close(fd);
_exit(0);
} else {
/* ===== PARENT: reader, reads in a loop ===== */
char buf[BUF_SIZE];
ssize_t n;
int session = 1;
/* Session 1 */
int fd = open(FIFO_PATH, O_RDONLY);
while ((n = read(fd, buf, BUF_SIZE - 1)) > 0) {
buf[n] = '\0';
printf("[Reader] Session %d: \"%s\"\n", session, buf);
}
printf("[Reader] Session %d EOF received\n", session++);
close(fd);
/* FIFO still exists! Re-open for session 2 */
fd = open(FIFO_PATH, O_RDONLY);
while ((n = read(fd, buf, BUF_SIZE - 1)) > 0) {
buf[n] = '\0';
printf("[Reader] Session %d: \"%s\"\n", session, buf);
}
printf("[Reader] Session %d EOF received\n", session);
close(fd);
wait(NULL);
}
unlink(FIFO_PATH);
return 0;
}
/* Output:
[Writer] First write session
[Reader] Session 1: "First message"
[Reader] Session 1 EOF received
[Writer] Closed. Sleeping 1 second...
[Writer] Second write session (FIFO re-opened)
[Reader] Session 2: "Second message"
[Reader] Session 2 EOF received
*/
A server process that reads from a FIFO will see EOF when the last client (writer) closes. To prevent this, a server can open the FIFO with O_RDWR โ this keeps a write-end reference open permanently, so EOF is never triggered unexpectedly.
/* fifo_server_rdwr.c โ server that survives client disconnects */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#define FIFO_PATH "/tmp/ep_server_fifo"
#define MAX_CLIENTS 10
int main(void)
{
mkfifo(FIFO_PATH, 0666);
printf("[Server] Waiting for clients...\n");
/* O_RDWR: opens both ends โ server holds write end open
* This means: server won't see EOF even when no clients are connected.
* The server stays alive between client connections. */
int fd = open(FIFO_PATH, O_RDWR);
if (fd == -1) {
perror("open O_RDWR");
return 1;
}
char buf[256];
ssize_t n;
int msg_count = 0;
while (msg_count < MAX_CLIENTS) {
n = read(fd, buf, sizeof(buf) - 1);
if (n == 0) {
/* Without O_RDWR this would terminate the server
* With O_RDWR: this shouldn't happen (server holds write end) */
printf("[Server] EOF received (unexpected with O_RDWR)\n");
break;
}
if (n > 0) {
buf[n] = '\0';
printf("[Server] Message %d: \"%s\"\n", ++msg_count, buf);
}
}
close(fd);
unlink(FIFO_PATH);
return 0;
}
A: They unblock each other. If process A is waiting in open(O_RDONLY) and process B calls open(O_WRONLY), both open() calls complete and return. This is the “rendezvous” behavior.
A: ENXIO is returned when a process tries to open a FIFO for writing (O_WRONLY) with O_NONBLOCK flag, but no process currently has the FIFO open for reading.
A: Yes. The FIFO file persists in the filesystem after the first writer closes. New writers can open and write to it again. This enables a server to serve multiple sequential clients.
A: A server process can open a FIFO O_RDWR to keep an implicit write-end reference. This prevents the server from seeing a spurious EOF when the last client closes their write end โ the server’s own write reference keeps the write-end count above zero.
A: A regular file’s EOF means you’ve reached the end of data that exists in the file. A FIFO’s read() returning 0 means all write-end descriptors are closed โ there is no current writer. More data could arrive later if a new writer opens the FIFO.
