Loading learning content...
While pipes and FIFOs provide simple byte-stream communication between processes, they lack structure. When Process A sends data to Process B, there's no inherent way to distinguish between different types of messages, no mechanism to prioritize urgent communications, and no natural boundary between discrete messages in the stream.
Message queues solve these limitations by providing a structured, typed, and prioritized messaging system. Unlike pipes, message queues preserve message boundaries—each message is a discrete unit that arrives intact. Unlike shared memory, message queues handle synchronization automatically—no explicit semaphores required.
The System V IPC suite, introduced in AT&T's Unix System V in the early 1980s, was the first standardized implementation of message queues in Unix. Despite being over four decades old, System V message queues remain widely used and form the conceptual foundation for understanding all message-based IPC.
By the end of this page, you will understand the complete architecture of System V message queues: their kernel-level implementation, the APIs for creation, sending, receiving, and management, key-based identification, message structure requirements, and the operational characteristics that determine when System V queues are the right choice for your IPC needs.
System V message queues don't exist in isolation—they're part of a broader IPC framework that also includes shared memory segments and semaphores. Understanding this architecture provides crucial context for message queue operations.
All System V IPC mechanisms share a common architectural pattern:
Objects Exist in Kernel Space: Unlike pipes (which are file descriptors) or files (which are filesystem entries), System V IPC objects live directly in kernel memory. They persist independently of any process's lifecycle.
Key-Based Identification: Each IPC object is identified by a numeric key (type key_t). Unrelated processes can connect to the same object by using the same key—without needing a parent-child relationship or a filesystem path.
Permission Structure: Each object has owner/group/other permissions similar to files, controlling which processes can access it.
Explicit Lifecycle Management: Unlike pipes that close automatically, System V objects persist until explicitly deleted—surviving process termination, system load changes, and even partial system failures.
| Feature | Message Queues | Shared Memory | Semaphores |
|---|---|---|---|
| Purpose | Structured message passing | Fast data sharing | Synchronization |
| Data Preservation | Kernel copies messages | Direct memory access | Counter values only |
| Synchronization | Built-in blocking | None (needs semaphores) | Core function |
| Message Boundaries | Yes, preserved | No (raw bytes) | N/A |
| Typical Use Case | Task distribution | Large data sharing | Critical sections |
A critical characteristic of System V IPC: objects persist in kernel memory even after all processes using them terminate. This is both a feature (enables restart recovery) and a danger (can leak kernel resources). Always design cleanup strategies for System V IPC resources.
Before creating a message queue, you need a key—a numeric identifier that allows unrelated processes to find the same queue. The ftok() function generates keys deterministically from a pathname and a project identifier.
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
Parameters:
pathname: Path to an existing, accessible file (used to derive a unique key)proj_id: A project identifier (only the low-order 8 bits are used)Returns:
key_t value on success-1 on error (with errno set)The ftok() function combines three pieces of information:
This means:
123456789101112131415161718192021222324252627282930313233343536
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/ipc.h>#include <errno.h>#include <string.h> int main() { const char *keyfile = "/tmp/myapp_keyfile"; int proj_id = 'A'; // Using character literal (common convention) // Generate the key key_t key = ftok(keyfile, proj_id); if (key == -1) { fprintf(stderr, "ftok failed: %s\n", strerror(errno)); fprintf(stderr, "Ensure %s exists and is accessible\n", keyfile); return EXIT_FAILURE; } printf("Generated key: 0x%08x (%d)\n", key, key); printf("This key can be used by multiple processes\n"); printf("using the same file and project ID.\n"); // Demonstrate reproducibility key_t key2 = ftok(keyfile, proj_id); printf("\nSecond ftok() call: 0x%08x\n", key2); printf("Keys match: %s\n", (key == key2) ? "YES" : "NO"); // Different project ID = different key key_t key3 = ftok(keyfile, 'B'); printf("\nWith proj_id 'B': 0x%08x\n", key3); printf("Different from original: %s\n", (key != key3) ? "YES" : "NO"); return EXIT_SUCCESS;}The ftok() function has important limitations: (1) The file must exist when ftok() is called—if deleted and recreated, the inode changes and so does the key; (2) Symbolic links are followed, so the actual file's inode is used; (3) On some systems, if the file is on a remote filesystem, results may be unpredictable. For maximum reliability, use a stable, local file that's part of your application installation.
Instead of using ftok(), you can create a private queue using the special key IPC_PRIVATE:
key_t key = IPC_PRIVATE; // Value is 0
int msqid = msgget(key, IPC_CREAT | 0666);
IPC_PRIVATE guarantees a new, unique message queue every time. However, there's a catch: the resulting queue ID must be communicated to other processes through some other mechanism (environment variables, file, shared memory, etc.), since they can't derive it from a shared key.
Use IPC_PRIVATE when:
Use ftok() when:
The msgget() system call creates a new message queue or obtains the identifier of an existing queue.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
Parameters:
| Parameter | Description |
|---|---|
key | The key value from ftok() or IPC_PRIVATE |
msgflg | Flags controlling creation behavior and permissions |
Returns:
-1 on error (with errno set)IPC_CREAT, fail if the queue already exists. This prevents race conditions when multiple processes try to create the same queue.0666 for read/write by all). Applied to owner, group, and others.12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>#include <errno.h>#include <string.h> #define KEYFILE "/tmp/msgqueue_demo"#define PROJ_ID 'M' int main() { // Ensure keyfile exists FILE *fp = fopen(KEYFILE, "w"); if (fp) fclose(fp); // Generate the key key_t key = ftok(KEYFILE, PROJ_ID); if (key == -1) { perror("ftok"); return EXIT_FAILURE; } // ======================================== // Scenario 1: Create a new queue // ======================================== int msqid = msgget(key, IPC_CREAT | IPC_EXCL | 0666); if (msqid == -1) { if (errno == EEXIST) { printf("Queue already exists, opening existing...\n"); // Open existing queue (no IPC_EXCL) msqid = msgget(key, 0666); if (msqid == -1) { perror("msgget (open existing)"); return EXIT_FAILURE; } } else { perror("msgget (create)"); return EXIT_FAILURE; } } else { printf("Created new message queue\n"); } printf("Message Queue ID: %d\n", msqid); printf("Key: 0x%08x\n", key); // ======================================== // Scenario 2: Private queue // ======================================== int private_msqid = msgget(IPC_PRIVATE, IPC_CREAT | 0600); if (private_msqid == -1) { perror("msgget (private)"); } else { printf("\nPrivate Queue ID: %d\n", private_msqid); printf("This queue can only be shared via fork()\n"); // Clean up private queue immediately msgctl(private_msqid, IPC_RMID, NULL); } return EXIT_SUCCESS;}| errno | Cause | Solution |
|---|---|---|
EACCES | Queue exists but caller lacks permission | Check queue permissions with ipcs |
EEXIST | IPC_CREAT | IPC_EXCL but queue exists | Remove IPC_EXCL or delete existing queue |
ENOENT | Queue doesn't exist and IPC_CREAT not set | Add IPC_CREAT flag or verify queue was created |
ENOSPC | System limit on message queues reached | Delete unused queues or increase MSGMNI |
ENOMEM | Insufficient kernel memory | Free system memory or reduce queue sizes |
System V message queues have a strict requirement for message format: every message must begin with a long integer representing the message type, followed by the actual data.
struct msgbuf {
long mtype; // Message type (must be > 0)
char mtext[1]; // Message data (flexible array)
};
This is a template—you define your own structure following this pattern:
struct my_message {
long mtype; // REQUIRED: Message type
// Your data follows...
int command_code;
char payload[256];
double values[10];
// ... any data you need
};
The mtype field is not just metadata—it's a fundamental feature that enables:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
#include <sys/msg.h> // ================================================// Example 1: Simple command/response protocol// ================================================#define MSG_REQUEST 1L#define MSG_RESPONSE 2L struct simple_message { long mtype; char text[128];}; // ================================================// Example 2: Multi-type message system// ================================================#define MSG_LOGIN 1L#define MSG_LOGOUT 2L#define MSG_DATA 3L#define MSG_HEARTBEAT 4L struct system_message { long mtype; pid_t sender_pid; time_t timestamp; union { struct { char username[32]; char password[64]; } login; struct { int session_id; } logout; struct { size_t length; char buffer[1024]; } data; struct { int sequence; } heartbeat; } payload;}; // ================================================// Example 3: Worker task distribution// ================================================#define MSG_TASK_LOW 100L // Low priority#define MSG_TASK_NORMAL 50L // Normal priority #define MSG_TASK_HIGH 10L // High priority#define MSG_TASK_URGENT 1L // Urgent priority struct task_message { long mtype; // Priority encoded in type int task_id; char task_data[512]; pid_t assigned_worker; // 0 if unassigned};The mtype value MUST be greater than 0. Using mtype ≤ 0 causes msgsnd() to fail with EINVAL. This is a common source of bugs—ensure all message types are positive integers.
The msgsnd() system call adds a message to a queue.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
Parameters:
| Parameter | Description |
|---|---|
msqid | Queue identifier from msgget() |
msgp | Pointer to message structure (must begin with long mtype) |
msgsz | Size of message data excluding the mtype field |
msgflg | Flags controlling send behavior |
Returns:
0 on success-1 on errorA common source of bugs: msgsz is the size of the data portion only, not including the mtype field:
struct my_msg {
long mtype; // NOT counted in msgsz
char data[100]; // This IS counted
};
struct my_msg msg;
msg.mtype = 1;
strcpy(msg.data, "Hello");
// CORRECT: size of data only
msgsnd(msqid, &msg, sizeof(msg.data), 0);
// OR: total size minus mtype
msgsnd(msqid, &msg, sizeof(msg) - sizeof(long), 0);
// WRONG: includes mtype size
// msgsnd(msqid, &msg, sizeof(msg), 0); // Sends extra bytes!
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>#include <errno.h> #define MAX_TEXT 512 struct message { long mtype; char text[MAX_TEXT];}; int main() { key_t key = ftok("/tmp/msgdemo", 'S'); int msqid = msgget(key, IPC_CREAT | 0666); if (msqid == -1) { perror("msgget"); return EXIT_FAILURE; } struct message msg; // ======================================== // Blocking send (default behavior) // ======================================== msg.mtype = 1; snprintf(msg.text, MAX_TEXT, "Hello from PID %d", getpid()); // msgsz = size of text field only size_t msgsz = sizeof(msg.text); printf("Sending message (blocking mode)...\n"); if (msgsnd(msqid, &msg, msgsz, 0) == -1) { perror("msgsnd"); return EXIT_FAILURE; } printf("Message sent successfully\n"); // ======================================== // Non-blocking send (IPC_NOWAIT) // ======================================== msg.mtype = 2; snprintf(msg.text, MAX_TEXT, "Urgent message!"); printf("\nSending message (non-blocking mode)...\n"); if (msgsnd(msqid, &msg, msgsz, IPC_NOWAIT) == -1) { if (errno == EAGAIN) { printf("Queue is full, message not sent\n"); } else { perror("msgsnd"); } } else { printf("Message sent successfully\n"); } // ======================================== // Sending multiple message types // ======================================== for (int i = 1; i <= 5; i++) { msg.mtype = i; // Different types for each snprintf(msg.text, MAX_TEXT, "Message type %d content", i); if (msgsnd(msqid, &msg, msgsz, 0) == -1) { perror("msgsnd loop"); } else { printf("Sent message type %d\n", i); } } printf("\nQueue now contains messages of types 1-5\n"); printf("Use 'ipcs -q' to view queue status\n"); return EXIT_SUCCESS;}msg_qbytes limit), the call blocks until space becomes available or the queue is removed.errno set to EAGAIN. Never blocks the caller.The msgrcv() system call retrieves messages from a queue with powerful selection capabilities.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
Parameters:
| Parameter | Description |
|---|---|
msqid | Queue identifier |
msgp | Buffer to receive message |
msgsz | Maximum data size to receive (excluding mtype) |
msgtyp | Message type selection (see below) |
msgflg | Flags controlling receive behavior |
Returns:
mtext on success-1 on errorThe msgtyp parameter provides flexible message selection:
| msgtyp Value | Behavior |
|---|---|
= 0 | Receive the first message in the queue (any type) |
> 0 | Receive the first message with exactly this type |
< 0 | Receive the first message with type ≤ |msgtyp| (lowest type first) |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>#include <errno.h> #define MAX_TEXT 512 struct message { long mtype; char text[MAX_TEXT];}; void demonstrate_msgrcv(int msqid) { struct message msg; size_t msgsz = sizeof(msg.text); ssize_t received; // ======================================== // Pattern 1: Receive ANY message (FIFO) // ======================================== printf("\n--- Pattern 1: Receive any message (msgtyp = 0) ---\n"); received = msgrcv(msqid, &msg, msgsz, 0, IPC_NOWAIT); if (received != -1) { printf("Received type %ld: %s\n", msg.mtype, msg.text); } else if (errno == ENOMSG) { printf("Queue is empty\n"); } // ======================================== // Pattern 2: Receive SPECIFIC type // ======================================== printf("\n--- Pattern 2: Receive specific type (msgtyp = 3) ---\n"); received = msgrcv(msqid, &msg, msgsz, 3, IPC_NOWAIT); if (received != -1) { printf("Received type %ld: %s\n", msg.mtype, msg.text); } else if (errno == ENOMSG) { printf("No message of type 3 available\n"); } // ======================================== // Pattern 3: Priority-based reception // ======================================== printf("\n--- Pattern 3: Priority reception (msgtyp = -5) ---\n"); printf("Will receive lowest type <= 5\n"); received = msgrcv(msqid, &msg, msgsz, -5, IPC_NOWAIT); if (received != -1) { printf("Received type %ld: %s\n", msg.mtype, msg.text); } else if (errno == ENOMSG) { printf("No message with type <= 5 available\n"); } // ======================================== // Pattern 4: Blocking wait for message // ======================================== printf("\n--- Pattern 4: Blocking receive ---\n"); printf("Waiting for message of type 1 (Ctrl+C to cancel)...\n"); received = msgrcv(msqid, &msg, msgsz, 1, 0); // Blocks! if (received != -1) { printf("Received: %s\n", msg.text); } // ======================================== // Pattern 5: Truncate long messages // ======================================== printf("\n--- Pattern 5: Allow message truncation ---\n"); char small_buf[64]; struct { long mtype; char text[64]; } small_msg; // MSG_NOERROR: truncate if message is larger than buffer received = msgrcv(msqid, &small_msg, sizeof(small_msg.text), 0, IPC_NOWAIT | MSG_NOERROR); if (received != -1) { printf("Received %zd bytes (may be truncated)\n", received); }}Using negative msgtyp values creates priority ordering: msgrcv(msqid, &msg, msgsz, -100, 0) will receive the message with the lowest type value that is ≤ 100. If you have messages of types 1, 5, 50, and 100, it receives type 1 first. This enables priority queuing without separate queues!
The msgctl() system call performs various control operations on a message queue.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
Commands:
| Command | Description |
|---|---|
IPC_STAT | Copy queue info into buf |
IPC_SET | Apply settings from buf to queue |
IPC_RMID | Remove the message queue immediately |
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>#include <time.h>#include <pwd.h> void print_queue_info(int msqid) { struct msqid_ds info; if (msgctl(msqid, IPC_STAT, &info) == -1) { perror("msgctl IPC_STAT"); return; } printf("\n===== Message Queue Info (ID: %d) =====\n", msqid); // Ownership and permissions struct passwd *owner = getpwuid(info.msg_perm.uid); printf("Owner: %s (UID %d)\n", owner ? owner->pw_name : "unknown", info.msg_perm.uid); printf("Permissions: %03o\n", info.msg_perm.mode & 0777); // Queue statistics printf("\nCurrent messages: %lu\n", info.msg_qnum); printf("Current size: %lu bytes\n", info.__msg_cbytes); printf("Maximum size: %lu bytes\n", info.msg_qbytes); // Process info printf("\nLast send PID: %d\n", info.msg_lspid); printf("Last recv PID: %d\n", info.msg_lrpid); // Timestamps printf("\nLast send: %s", ctime(&info.msg_stime)); printf("Last recv: %s", ctime(&info.msg_rtime)); printf("Last change: %s", ctime(&info.msg_ctime));} void resize_queue(int msqid, size_t new_size) { struct msqid_ds info; // First get current settings if (msgctl(msqid, IPC_STAT, &info) == -1) { perror("msgctl IPC_STAT"); return; } printf("Current max size: %lu bytes\n", info.msg_qbytes); // Modify the maximum size info.msg_qbytes = new_size; if (msgctl(msqid, IPC_SET, &info) == -1) { perror("msgctl IPC_SET"); printf("Note: Increasing size may require root privileges\n"); return; } printf("New max size: %lu bytes\n", new_size);} void remove_queue(int msqid) { printf("Removing message queue %d...\n", msqid); if (msgctl(msqid, IPC_RMID, NULL) == -1) { perror("msgctl IPC_RMID"); return; } printf("Queue removed successfully\n"); printf("All pending messages are lost!\n");} int main() { // Create a queue for demonstration int msqid = msgget(IPC_PRIVATE, IPC_CREAT | 0666); if (msqid == -1) { perror("msgget"); return EXIT_FAILURE; } print_queue_info(msqid); resize_queue(msqid, 32768); // Increase to 32KB remove_queue(msqid); return EXIT_SUCCESS;}When IPC_RMID is issued, the queue is destroyed immediately. All messages still in the queue are lost. Any processes blocked in msgrcv() or msgsnd() are awakened and receive EIDRM. There is no way to recover the queue or its contents. Always ensure all messages are processed before removal in production systems.
We've covered the complete System V message queue API. Let's consolidate the essential concepts:
ftok() generates keys from pathname + project ID, enabling unrelated processes to find the same queue.msgctl(IPC_RMID).long mtype > 0, followed by data. The type enables selective reception.msgsz parameter is data size only, excluding the mtype field.msgrcv() can receive any message (type=0), specific type (type>0), or priority-based (type<0).IPC_NOWAIT for non-blocking operations.You now understand System V message queues—the foundational message-passing IPC mechanism. Next, we'll explore POSIX message queues, which provide a more modern API with additional features like notification and better POSIX compliance.