Introduction
Pipes and message queues are both message-passing IPC mechanisms, but they differ substantially in relationship requirements, directionality, structure, and persistence. An anonymous pipe is the simplest: a unidirectional, unnamed kernel buffer usable only between processes that share a common ancestor (typically parent and child after fork()), and it disappears once both ends are closed. A named pipe, or FIFO, extends this idea by giving the pipe a name in the filesystem, allowing unrelated processes to open and use it. Message queues go further still, letting processes exchange discrete, typed/structured messages that can persist in the kernel even after the sending process exits, and are usable by any process that knows the queue's key or name, related or not.
Cricket analogy: An anonymous pipe is like two players from the same academy passing notes only while both are on the training ground together, disappearing once either leaves; a named pipe (FIFO) is like a notice board any player can read by name, even from a different academy; message queues are like a structured team messaging app where posts persist and can be tagged by type for anyone with access.
Syntax
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
struct my_msg {
long mtype; /* must be > 0, used for selective receive */
char mtext[128];
};
int main(void) {
key_t key = ftok("/tmp", 'Q');
int msqid = msgget(key, IPC_CREAT | 0666);
struct my_msg m = { .mtype = 1 };
snprintf(m.mtext, sizeof(m.mtext), "hello queue");
msgsnd(msqid, &m, sizeof(m.mtext), 0);
struct my_msg r;
msgrcv(msqid, &r, sizeof(r.mtext), 1, 0);
msgctl(msqid, IPC_RMID, NULL); /* remove queue explicitly */
return 0;
}Explanation
Anonymous pipes created with pipe() provide a raw unidirectional byte stream with no message boundaries; data written is just a sequence of bytes, and the pipe's lifetime is tied to open file descriptors held by related processes. Named pipes created with mkfifo() appear as special files in the filesystem, so any process (related or not) that has permission can open() them by pathname, though data flow is still an unstructured byte stream. Message queues (System V via msgget()/msgsnd()/msgrcv(), or POSIX via mq_open()/mq_send()/mq_receive()) add structure: each message has an explicit boundary and, in System V's case, a type field used for selective retrieval. Crucially, System V message queues persist in the kernel independent of any process's lifetime until explicitly removed with msgctl(IPC_RMID), unlike pipes which vanish when their descriptors are closed.
Cricket analogy: pipe() creating a raw unidirectional stream is like a coach shouting instructions only heard by the specific player standing next to them, tied to that moment; mkfifo() creating a named pipe is like posting instructions on the dressing room door where any player with access can read them by walking up; System V message queues persisting until msgctl(IPC_RMID) are like a permanent team memo board that stays up even after the coach who posted it has gone home, until someone explicitly takes it down.
Example
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
const char *fifo = "/tmp/my_fifo";
mkfifo(fifo, 0666); /* create named pipe once */
pid_t pid = fork();
if (pid == 0) {
/* unrelated-style writer: opens by pathname */
int wfd = open(fifo, O_WRONLY);
write(wfd, "named pipe msg", 14);
close(wfd);
_exit(0);
} else {
int rfd = open(fifo, O_RDONLY);
char buf[32] = {0};
read(rfd, buf, sizeof(buf) - 1);
printf("received: %s\n", buf);
close(rfd);
unlink(fifo); /* remove FIFO from filesystem */
}
return 0;
}Output
Running this program prints "received: named pipe msg". Unlike the anonymous pipe example, this FIFO exists as a real filesystem path (/tmp/my_fifo) visible via ls, and any process on the system with correct permissions could open it by name (not just descendants of a common fork()), demonstrating the key relaxation FIFOs provide over anonymous pipes. It must, however, be explicitly removed with unlink(); it does not clean itself up automatically the way an anonymous pipe's kernel buffer does when descriptors close.
Cricket analogy: The named-pipe example is like posting match instructions on a physical noticeboard (/tmp/my_fifo) that any player passing by can read even if they never trained together, unlike a private in-huddle whisper — but someone must physically take the notice down (unlink()) or it stays pinned up forever.
Key Takeaways
- Anonymous pipes (pipe()) are unidirectional, usable only between related processes, and have no persistence beyond open file descriptors.
- Named pipes / FIFOs (mkfifo()) add a filesystem name, allowing unrelated processes to communicate, but still carry an unstructured byte stream.
- Message queues (msgget()/msgsnd()/msgrcv() or POSIX mq_* calls) carry structured, typed messages and can persist in the kernel independent of process lifetime.
- Message queues work between unrelated processes identified by a key or name, not requiring a common ancestor.
Practice what you learned
1. What is a fundamental limitation of an anonymous pipe created with pipe()?
2. How does a named pipe (FIFO) differ from an anonymous pipe?
3. What key feature distinguishes a System V message queue from a pipe?
4. Which call removes a System V message queue from the kernel?
Was this page helpful?
You May Also Like
Interprocess Communication
How independent processes with separate address spaces exchange data and coordinate using OS-provided IPC mechanisms.
Shared Memory
The fastest IPC mechanism, mapping common memory into multiple processes, at the cost of requiring explicit synchronization.
System Calls
Understand how applications request kernel services through system calls and the user-to-kernel mode transition.