System V typed messages with priority retrieval — like a mailbox where you can pick letters by category.
FIFOs let unrelated processes communicate, but they have a limitation: data is just a raw byte stream. There's no concept of discrete messages, no types, no priorities. If you write "ERROR:disk full" and then "INFO:backup ok", the reader gets a stream of bytes and has to figure out where one message ends and the next begins.
Message queues solve this. Each message is a discrete unit with a type field (a positive integer). The receiver can pull messages by type — "give me all the error messages first" — or just grab the next message regardless of type.
One more thing: System V message queues persist in the kernel until explicitly destroyed. Even if every process using the queue exits, the queue (and its messages) remain. Use ipcs to see them and ipcrm to delete them.
Click to send typed messages. Notice how you can retrieve by type, not just FIFO order.
Every System V IPC object (message queue, semaphore, shared memory) is identified by a key — a system-wide unique number. Two processes that want to use the same queue must agree on the same key.
You could just hardcode a number, but what if another unrelated program uses the same number? The solution is ftok():
c #include <sys/ipc.h> key_t key; key = ftok("/home/beej/somefile", 'b');
ftok() takes two arguments: a path to an existing file that the process can read, and an arbitrary id character. It combines information about the file (like its inode number) with the id to generate a probably-unique key.
Once you have a key, you create (or connect to) a message queue with msgget():
c #include <sys/msg.h> int msqid; key_t key; key = ftok("/home/beej/somefile", 'b'); msqid = msgget(key, 0666 | IPC_CREAT);
msgget() returns a message queue ID on success, or -1 on failure. The arguments:
| Argument | Purpose |
|---|---|
key | The system-wide unique key from ftok() |
msgflg | Permissions (like chmod) OR'd with IPC_CREAT to create if it doesn't exist |
The 0666 sets rw-rw-rw- permissions. The IPC_CREAT flag means "create the queue if it doesn't exist; if it does, just connect to it." The receiver typically connects without IPC_CREAT, relying on the sender to create it first.
Every message has the same structure: a long type field followed by the actual data. The template is in <sys/msg.h>:
c struct msgbuf { long mtype; /* message type, must be > 0 */ char mtext[1]; /* message data */ };
But you're not stuck with a one-byte payload! You can define your own struct, as long as the first field is a long:
c struct my_msgbuf { long mtype; /* must be first, must be positive */ char mtext[200]; /* the actual message data */ }; /* Or get fancy: */ struct pirate_msgbuf { long mtype; struct pirate_info { char name[30]; char ship_type; int notoriety; int cruelty; int booty_value; } info; };
mtype field must be a positive number. It cannot be zero or negative. This is the field used for selective retrieval with msgrcv(). Think of it as a category label for the message.To put a message onto the queue, you use msgsnd():
c int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
| Argument | Purpose |
|---|---|
msqid | Queue ID from msgget() |
msgp | Pointer to your message struct |
msgsz | Size of the data portion (NOT including mtype) |
msgflg | Flags (0 is fine for now) |
The tricky part is msgsz. It's the size of the data only — you subtract the size of the long mtype field:
c struct my_msgbuf buf; buf.mtype = 1; strcpy(buf.mtext, "Hello, queue!"); /* size = total struct - sizeof(long) */ msgsnd(msqid, &buf, sizeof(struct my_msgbuf) - sizeof(long), 0);
msgsnd(msqid, &buf, strlen(buf.mtext)+1, 0). The +1 accounts for the null terminator.The real magic happens on the receiving end. msgrcv() can pull messages selectively by type:
c int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
The msgtyp argument is the key to selective retrieval. Its behavior depends on its value:
| msgtyp Value | Effect |
|---|---|
| 0 (zero) | Retrieve the next message, regardless of type (pure FIFO) |
| Positive | Get the next message with exactly that mtype |
| Negative | Get the first message whose mtype is ≤ |msgtyp| |
c struct my_msgbuf buf; /* Get the next type-2 message */ msgrcv(msqid, &buf, sizeof(buf.mtext), 2, 0); /* Get ANY next message (type 0 = any) */ msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0);
Message queues don't disappear when processes exit. You must explicitly destroy them. There are two ways:
ipcs to list all IPC objects, then ipcrm -q <msqid> to remove a specific queue.msgctl(msqid, IPC_RMID, NULL) to destroy the queue from your code.c #include <sys/msg.h> /* Destroy the message queue */ msgctl(msqid, IPC_RMID, NULL);
The msgctl() function does many things, but IPC_RMID is the most common use: it removes the message queue immediately. Any blocked msgrcv() calls on other processes will return with an error.
ipcrm from the command line to clean up.Beej's classic example uses two programs: kirk (the sender) adds messages to the queue, and spock (the receiver) pulls them out.
kirk.c (sender) #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> struct my_msgbuf { long mtype; char mtext[200]; }; int main(void) { struct my_msgbuf buf; int msqid; key_t key; key = ftok("kirk.c", 'B'); msqid = msgget(key, 0644 | IPC_CREAT); printf("Enter lines of text, ^D to quit:\n"); buf.mtype = 1; while (fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL) { int len = strlen(buf.mtext); if (buf.mtext[len-1] == '\n') buf.mtext[len-1] = '\0'; msgsnd(msqid, &buf, len+1, 0); } msgctl(msqid, IPC_RMID, NULL); return 0; }
spock.c (receiver) #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> struct my_msgbuf { long mtype; char mtext[200]; }; int main(void) { struct my_msgbuf buf; int msqid; key_t key; key = ftok("kirk.c", 'B'); /* same key as kirk */ msqid = msgget(key, 0644); /* no IPC_CREAT */ printf("spock: ready to receive messages, captain.\n"); for(;;) { msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0); printf("spock: \"%s\"\n", buf.mtext); } return 0; }
Watch typed messages flow through a System V message queue. Send messages with different types, then retrieve them — either in FIFO order (type 0) or selectively by type.
Send messages with types 1-3, then receive. "Recv Any" gets the next message; "Recv Type N" gets the first message of that type.
Message queues are powerful but have some quirks:
sysctl.mq_open, etc.) are a more modern alternative with similar functionality.| Feature | Pipes/FIFOs | Message Queues | Shared Memory |
|---|---|---|---|
| Data model | Byte stream | Discrete messages | Raw memory |
| Message types | No | Yes (mtype) | N/A |
| Persistence | Die with process | Kernel-persistent | Kernel-persistent |
| Speed | Fast | Medium | Fastest |
| Needs sync? | Built-in | Built-in | Yes (semaphores) |
"spock: ready to receive messages, captain." — Beej