What is this about?
Linux provides a way to enumerate all System V message queues currently existing on the system — similar to running ipcs -q on the terminal. This is done programmatically using two special commands passed to msgctl(): MSG_INFO and MSG_STAT. Understanding these commands is important for system monitoring tools, debugging IPC applications, and kernel-level inspection.
In this tutorial we cover: what MSG_INFO and MSG_STAT do, how they differ from IPC_STAT, what data they return, and how to combine them to list all active message queues — just like ipcs does internally.
Internally, the Linux kernel maintains an array called the entries array to track all System V IPC objects (message queues, semaphores, shared memory segments). Each slot in this array either holds an active IPC object or is empty. When you call msgget(), the kernel assigns a slot in this array.
The key insight is: the msqid (message queue ID) returned by msgget() is NOT the same as the slot index. The msqid encodes both the slot index and a sequence number. However, to enumerate all queues, we need to iterate over the entries array directly using the index.
MSG_INFO is a Linux-specific (non-POSIX) extension to msgctl(). When called as:
int maxind = msgctl(0, MSG_INFO, (struct msqid_ds *) &msginfo);
It returns the highest used index in the kernel’s entries array. It also fills the msginfo structure (of type struct msginfo) with system-wide statistics.
| Field in struct msginfo | Meaning |
|---|---|
| msgpool | Size of kernel message pool (pages) |
| msgmap | Number of entries in message map |
| msgmax | Max bytes in a single message |
| msgmnb | Max bytes in a single queue |
| msgmni | Max number of message queues system-wide |
| msgssz | Message segment size |
| msgtql | Max messages system-wide |
| msgseg | Max segments in message pool |
msgctl() with MSG_INFO is ignored (we pass 0). The return value itself is the maximum index — not stored in the struct._GNU_SOURCE before including system headers because MSG_INFO and MSG_STAT are Linux extensions not part of the POSIX standard.MSG_STAT is similar to IPC_STAT but with a crucial difference:
| Feature | IPC_STAT | MSG_STAT |
|---|---|---|
| First argument | msqid (queue ID) | Array index (0, 1, 2…) |
| Returns | 0 on success | The actual msqid of that slot |
| Empty slot | N/A (invalid ID fails) | Returns EINVAL |
| No permission | EACCES | EACCES (skip it) |
| Purpose | Get info for known queue | Enumerate all queues |
The return value of msgctl(ind, MSG_STAT, &ds) is the actual msqid assigned to that queue. This is important because msqid != index.
Get the maximum index and system limits for message queues:
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <stdlib.h>
int main(void) {
struct msginfo info;
int maxind;
/* MSG_INFO fills a msginfo struct, returns max used index */
maxind = msgctl(0, MSG_INFO, (struct msqid_ds *) &info);
if (maxind == -1) {
perror("msgctl MSG_INFO");
exit(EXIT_FAILURE);
}
printf("Maximum used index in entries array: %d\n", maxind);
printf("\n--- System-wide Message Queue Limits ---\n");
printf("Max message queues (msgmni): %d\n", info.msgmni);
printf("Max bytes per queue (msgmnb): %d\n", info.msgmnb);
printf("Max bytes per message (msgmax): %d\n", info.msgmax);
printf("Max total messages (msgtql): %d\n", info.msgtql);
return 0;
}
gcc -o msginfo msginfo.c && ./msginfo
Sample Output:
Maximum used index in entries array: 4
Max message queues (msgmni): 32000
Max bytes per queue (msgmnb): 16384
Max bytes per message (msgmax): 8192
Max total messages (msgtql): 32000
This is the core technique from TLPI — enumerate every active queue on the system:
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <errno.h>
#include <stdlib.h>
int main(void) {
int maxind, ind, msqid;
struct msqid_ds ds;
struct msginfo msginfo;
/* Step 1: Get the highest used index in kernel entries array */
maxind = msgctl(0, MSG_INFO, (struct msqid_ds *) &msginfo);
if (maxind == -1) {
perror("msgctl MSG_INFO");
exit(EXIT_FAILURE);
}
printf("maxind: %d\n\n", maxind);
printf("%-6s %-10s %-12s %s\n", "index", "msqid", "key", "messages");
printf("----------------------------------------------\n");
/* Step 2: Iterate over all slots 0..maxind */
for (ind = 0; ind <= maxind; ind++) {
/* MSG_STAT uses array index, returns the msqid */
msqid = msgctl(ind, MSG_STAT, &ds);
if (msqid == -1) {
/* EINVAL = empty slot, EACCES = no permission — both are normal */
if (errno != EINVAL && errno != EACCES)
perror("msgctl MSG_STAT (unexpected)");
continue; /* Skip this slot */
}
/* Print info about this active queue */
printf("%-6d %-10d 0x%08lx %7ld\n",
ind,
msqid,
(unsigned long) ds.msg_perm.__key,
(long) ds.msg_qnum); /* Number of messages currently in queue */
}
return 0;
}
gcc -D_GNU_SOURCE -o svmsg_ls svmsg_ls.c && ./svmsg_ls
What each column means:
index — slot number in kernel entries array
msqid — the actual message queue ID (use this with msgsnd/msgrcv)
key — the IPC key used to create the queue (0x00000000 = IPC_PRIVATE)
messages — number of messages currently waiting in the queue
Extract full metadata for each queue including byte counts, owner, permissions, and timestamps:
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <time.h>
#include <errno.h>
#include <stdlib.h>
int main(void) {
int maxind, ind, msqid;
struct msqid_ds ds;
struct msginfo msginfo;
maxind = msgctl(0, MSG_INFO, (struct msqid_ds *) &msginfo);
if (maxind == -1) { perror("msgctl"); exit(1); }
for (ind = 0; ind <= maxind; ind++) {
msqid = msgctl(ind, MSG_STAT, &ds);
if (msqid == -1) {
if (errno != EINVAL && errno != EACCES)
perror("msgctl MSG_STAT");
continue;
}
printf("\n=== Queue at index %d (msqid = %d) ===\n", ind, msqid);
printf(" IPC key : 0x%08lx\n", (unsigned long)ds.msg_perm.__key);
printf(" Owner UID : %u\n", ds.msg_perm.uid);
printf(" Owner GID : %u\n", ds.msg_perm.gid);
printf(" Permissions : %04o\n", ds.msg_perm.mode & 0777);
printf(" Messages now : %lu\n", (unsigned long)ds.msg_qnum);
printf(" Bytes now : %lu\n", (unsigned long)ds.msg_cbytes);
printf(" Max bytes : %lu\n", (unsigned long)ds.msg_qbytes);
printf(" Last send : %s", ctime(&ds.msg_stime));
printf(" Last receive : %s", ctime(&ds.msg_rtime));
printf(" Last change : %s", ctime(&ds.msg_ctime));
}
return 0;
}
Both IPC_STAT and MSG_STAT fill a struct msqid_ds. Understanding its fields is essential for system programming:
| Field | Type | Description |
|---|---|---|
| msg_perm.uid | uid_t | Effective UID of the queue owner |
| msg_perm.gid | gid_t | Effective GID of the queue owner |
| msg_perm.cuid | uid_t | UID of the creator |
| msg_perm.cgid | gid_t | GID of the creator |
| msg_perm.mode | unsigned short | Permissions (lower 9 bits) |
| msg_perm.__key | key_t | IPC key (implementation detail) |
| msg_stime | time_t | Time of last msgsnd() |
| msg_rtime | time_t | Time of last msgrcv() |
| msg_ctime | time_t | Time of last msgctl() change |
| msg_qnum | msgqnum_t | Messages currently in queue |
| msg_qbytes | msglen_t | Max bytes queue can hold |
| msg_lspid | pid_t | PID of last msgsnd() caller |
| msg_lrpid | pid_t | PID of last msgrcv() caller |
| msg_cbytes | (Linux ext) | Bytes currently in queue |
Interview Questions — MSG_INFO, MSG_STAT, Queue Enumeration
MSG_STAT takes an array index (0, 1, 2…) as the first argument and returns the actual msqid as its return value. This allows iterating over the kernel’s internal entries array to discover all queues — even those you don’t already know about. MSG_STAT is a Linux-specific extension not in POSIX.
_GNU_SOURCE must be defined before including any system headers. Without it, the compiler will not see the MSG_INFO and MSG_STAT constants, leading to compilation errors.EACCES means a queue exists at that index but the calling process does not have read permission on it. This is also normal in a multi-user system — we skip it and move on.
Any other error (e.g., EFAULT) would indicate a programming bug or unexpected kernel condition, so those should be reported.
for (ind = 0; ind <= maxind; ind++). We must loop all the way to maxind even if some intermediate slots are empty.msg_qbytes is the maximum number of bytes the queue is allowed to hold. It acts as the queue’s capacity limit.
Together they let you compute how full a queue is: (msg_cbytes / msg_qbytes) * 100%. A superuser can increase msg_qbytes using msgctl(msqid, IPC_SET, &ds).
ipcs -q uses exactly the MSG_INFO + MSG_STAT technique described in this tutorial. It calls msgctl(0, MSG_INFO, ...) to get the maximum index, then loops from 0 to maxind calling msgctl(ind, MSG_STAT, &ds) on each slot. For each successful call, it prints the msqid, key, permissions, owner, and message count. Code example 2 in this tutorial is essentially a simplified reimplementation of ipcs -q.Next: Client-Server with Single Queue → EmbeddedPathashala Home
