Chapter 46 · File 1 of 6
System V Message Queues
Introduction, Overview & Core Concepts
This is the first file in our Chapter 46 series. Before writing any code, let’s understand what a message queue is, why it was invented, and how it compares to pipes. If you are new to Linux IPC, start here.
What is IPC? (Quick Recap)
IPC stands for Inter-Process Communication. When two or more processes need to share data or coordinate their work, they need an IPC mechanism. Linux provides many:
System V IPC (also called XSI IPC) is the older set. It was designed in the 1980s on System V Unix before POSIX existed. Message queues are one part of it.
What is a System V Message Queue?
Think of a message queue like a post box inside the kernel. Any process that knows the address (the queue ID) can drop a message in, and any authorised process can pick a message out. The kernel stores all messages safely until someone reads them.
→
→
Key Characteristics
1. Identified by a Queue ID, not a File Descriptor
When you create a message queue, the kernel returns an integer called the queue identifier (msqid). This is not a file descriptor. You cannot use it with read(), write(), select(), or poll(). This is one of the biggest differences from pipes.
2. Message-Oriented (Not Byte-Stream)
Pipes give you a stream of raw bytes — the reader can read any number of bytes at a time. Message queues are different: each write sends one complete message, and each read receives one complete message. You cannot read half a message.
|
Pipe (Byte Stream)
Writer writes: “HELLOWORLD”
Reader reads: “HELL” (4 bytes) Reader reads: “OWOR” (4 bytes) Reader reads: “LD” (2 bytes) ← reader decides how much |
Message Queue (Message-Oriented)
Writer sends msg: “HELLO”
Writer sends msg: “WORLD” Reader gets msg: “HELLO” (whole) Reader gets msg: “WORLD” (whole) ← always whole messages |
3. Every Message Has a Type (Integer)
Each message carries a type field (a positive long integer). The reader can either read the first message in line (FIFO order) or select messages by type. This is very powerful — for example, different processes can read only the message types meant for them from a single shared queue.
→ gets only Login requests
→ gets only Logout requests
→ gets first in queue (any type)
Comparison: Pipes vs FIFOs vs System V Queues
| Feature | Pipe | FIFO (Named Pipe) | System V Msg Queue |
|---|---|---|---|
| I/O model | Byte stream | Byte stream | Messages (typed) |
| Identified by | File descriptor | Filename + FD | Queue ID (msqid) |
| Works with select/poll? | Yes | Yes | No |
| Unrelated processes? | No (parent-child only) | Yes (via filename) | Yes (via key) |
| Message types? | No | No | Yes |
| Kernel persistence? | No | No | Yes — survives process exit! |
| Requires explicit delete? | No | No (unlink the file) | Yes — msgctl(IPC_RMID) |
| Portable? | Yes | Yes | Older API, less portable |
msgctl(IPC_RMID), it stays in the kernel forever (until reboot or manual removal with ipcrm). Forgetting to clean up is one of the most common bugs with System V IPC.When to Use (and When to Avoid)
- You are maintaining or extending existing legacy code that already uses them.
- You need message-type-based filtering and do not want to use POSIX queues.
- You are working on an older embedded system that only has System V APIs.
Avoid for new code — prefer instead:
- POSIX message queues (
mq_open) — modern, supports select/poll, better designed. - Unix domain sockets — work with all standard I/O APIs, bidirectional.
- FIFOs + custom framing — simpler for many use cases.
Code Example 1 — Minimal Message Queue Flow
This is the simplest possible example showing the 4-step lifecycle of a message queue: create → send → receive → delete.
/* minimal_msgq.c — Minimal System V Message Queue Demo */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
/* Step 1: Define your message structure.
MUST start with a long called mtype.
The rest (mtext) can be anything you want. */
struct MyMessage {
long mtype; /* Message type — must be > 0 */
char mtext[100]; /* Payload */
};
int main(void)
{
int msqid;
struct MyMessage msg;
/* Step 2: Create a private message queue.
IPC_PRIVATE means only this process (and its children) can use it.
0666 = read+write permissions for owner, group, others */
msqid = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
if (msqid == -1) {
perror("msgget");
exit(EXIT_FAILURE);
}
printf("Queue created, ID = %d\n", msqid);
/* Step 3: Send a message — fill in type and text */
msg.mtype = 1; /* type 1 = some kind of 'request' */
strncpy(msg.mtext, "Hello from sender!", sizeof(msg.mtext) - 1);
/* msgsnd(queue_id, message_pointer, size_of_payload, flags)
Note: size is sizeof(mtext) — NOT sizeof(whole struct) */
if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
perror("msgsnd");
exit(EXIT_FAILURE);
}
printf("Message sent: '%s' (type=%ld)\n", msg.mtext, msg.mtype);
/* Step 4: Receive the message */
struct MyMessage received;
/* msgrcv(queue_id, buffer, max_size, type_to_receive, flags)
type=0 means: give me the first message regardless of type */
ssize_t bytes = msgrcv(msqid, &received, sizeof(received.mtext), 0, 0);
if (bytes == -1) {
perror("msgrcv");
exit(EXIT_FAILURE);
}
printf("Message received: '%s' (type=%ld)\n", received.mtext, received.mtype);
/* Step 5: Delete the queue — VERY IMPORTANT, don't forget! */
if (msgctl(msqid, IPC_RMID, NULL) == -1) {
perror("msgctl");
exit(EXIT_FAILURE);
}
printf("Queue deleted successfully.\n");
return 0;
}
gcc minimal_msgq.c -o minimal_msgq && ./minimal_msgq
Expected output:
Queue created, ID = 65536
Message sent: 'Hello from sender!' (type=1)
Message received: 'Hello from sender!' (type=1)
Queue deleted successfully.
Code Example 2 — Multiple Message Types
This example sends 3 messages with different types, then reads them in a specific type order to show how type-based selection works.
/* typed_messages.c — Demonstrating message type selection */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct Msg {
long mtype;
char mtext[80];
};
int send_msg(int qid, long type, const char *text) {
struct Msg m;
m.mtype = type;
strncpy(m.mtext, text, sizeof(m.mtext) - 1);
m.mtext[sizeof(m.mtext)-1] = '\0';
return msgsnd(qid, &m, sizeof(m.mtext), 0);
}
int main(void)
{
int qid = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
if (qid == -1) { perror("msgget"); exit(1); }
/* Send three messages with different types — they go into queue in this order */
send_msg(qid, 3, "I am type 3 — ERROR");
send_msg(qid, 1, "I am type 1 — INFO");
send_msg(qid, 2, "I am type 2 — WARNING");
printf("Sent 3 messages. Queue has: type3, type1, type2\n\n");
struct Msg rcv;
/* Read type 1 first — even though it's second in the queue */
msgrcv(qid, &rcv, sizeof(rcv.mtext), 1, 0);
printf("Read with type=1: '%s'\n", rcv.mtext);
/* Read type 2 */
msgrcv(qid, &rcv, sizeof(rcv.mtext), 2, 0);
printf("Read with type=2: '%s'\n", rcv.mtext);
/* Read type 0 = first remaining (type 3) */
msgrcv(qid, &rcv, sizeof(rcv.mtext), 0, 0);
printf("Read with type=0: '%s'\n", rcv.mtext);
msgctl(qid, IPC_RMID, NULL);
return 0;
}
Sent 3 messages. Queue has: type3, type1, type2Read with type=1: 'I am type 1 — INFO'Read with type=2: 'I am type 2 — WARNING'Read with type=0: 'I am type 3 — ERROR'
Notice: even though type=3 was sent first, it was read last because we asked for type=1 and type=2 first.
