System V Message Queues Listing All Queues with MSG_INFO & MSG_STAT

 

System V Message Queues
Chapter 46 – Part 5  |  Listing All Queues with MSG_INFO & MSG_STAT
46.6
TLPI Section
2
Key Flags
3
Code Examples

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.

Key Terms & Concepts

msgctl() MSG_INFO MSG_STAT IPC_STAT msginfo struct msqid_ds struct kernel entries array EACCES / EINVAL ipcs command

1. The Kernel’s Internal Data Structure for Message Queues

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.

Kernel entries[] array layout:
index 0
MQ-A
msqid=0
index 1
empty
EINVAL
index 2
MQ-B
msqid=258
index 3
empty
EINVAL
index 4
MQ-C
msqid=516
index 5..N
maxind
Blue = active queue  |  Grey = empty slot (returns EINVAL)
Key point: To enumerate all queues, we first find the maximum used index (via MSG_INFO), then loop from 0 to maxind calling MSG_STAT on each index. Empty slots return EINVAL or EACCES and should be skipped.

2. MSG_INFO — Get the Maximum Index & System-wide Statistics

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
Note: The first argument to msgctl() with MSG_INFO is ignored (we pass 0). The return value itself is the maximum index — not stored in the struct.
Header required: You must define _GNU_SOURCE before including system headers because MSG_INFO and MSG_STAT are Linux extensions not part of the POSIX standard.

3. MSG_STAT — Get Info About a Queue by Array Index

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.

How msqid is formed from index + sequence number:
Array Index
2
+
Sequence × MSGMNI
256
=
msqid
258

Code Example 1: Basic Usage of MSG_INFO

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;
}
Compile & Run:
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

Code Example 2: List All Message Queues (like ipcs -q)

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;
}
Compile & Run:
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

Code Example 3: Detailed Queue Info Using MSG_STAT

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;
}
msg_cbytes is the total number of bytes currently held in the queue across all messages. msg_qbytes is the maximum limit. Together they tell you how full the queue is.

4. The msqid_ds Structure — What It Contains

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

Q1. What is the difference between MSG_STAT and IPC_STAT in msgctl()?
IPC_STAT takes an actual msqid (queue identifier) as the first argument and returns queue metadata via a msqid_ds structure. It requires you to already know the queue’s ID.

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.

Q2. Why do we need to define _GNU_SOURCE to use MSG_INFO and MSG_STAT?
MSG_INFO and MSG_STAT are Linux-specific extensions to the System V IPC API. They are not part of the POSIX standard. To expose their declarations from the system headers (<sys/msg.h>), the feature test macro _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.
Q3. When iterating with MSG_STAT, why do we skip EINVAL and EACCES errors but report other errors?
EINVAL means the array slot at that index is currently empty (no message queue assigned there). This is completely normal — not every index in the kernel’s entries array has an active queue.

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.

Q4. What does MSG_INFO return, and what is the significance of that return value?
MSG_INFO returns the highest used index in the kernel’s entries array — not the number of active queues. For example, if three queues are at indices 0, 2, and 4, MSG_INFO returns 4. This is used as the upper bound for the enumeration loop: for (ind = 0; ind <= maxind; ind++). We must loop all the way to maxind even if some intermediate slots are empty.
Q5. What is the relationship between a queue’s array index and its msqid?
The msqid is computed as: msqid = index + (sequence_number × MSGMNI). The sequence number is incremented each time a slot is reused (i.e., a queue is deleted and a new one is created at the same index). This ensures that old msqids become invalid after a queue is deleted, preventing accidental reuse of stale IDs by processes holding old identifiers. MSG_STAT returns this msqid as its return value when you pass an index.
Q6. What is the msg_cbytes field in msqid_ds and how does it differ from msg_qbytes?
msg_cbytes is the total number of bytes currently held in the queue across all waiting messages. It is a Linux-specific extension (not in POSIX).

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).

Q7. How does ipcs -q work internally? Can you replicate its behavior in C?
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.
Q8. What fields of msqid_ds would you use to detect a “stuck” or clogged message queue?
To detect a clogged queue: check msg_qnum (messages waiting to be read — high value suggests consumer is not reading), msg_cbytes (bytes currently used — close to msg_qbytes means the queue is nearly full), and msg_rtime (time of last msgrcv() — if very old, the consumer may have died). Together, many messages + full byte usage + stale msg_rtime strongly indicates a clogged queue.

Leave a Reply

Your email address will not be published. Required fields are marked *