System V Message Queues The msqid_ds Data Structure

 

System V Message Queues
Chapter 46 · Part 3: The msqid_ds Data Structure
46.4
Section
9
Structure Fields
2
Code Examples
12
Interview Q&A

Key Terms in This Section
msqid_ds ipc_perm msg_perm msg_stime msg_rtime msg_ctime msg_qnum msg_qbytes msg_lspid msg_lrpid msgqnum_t msglen_t

What is the msqid_ds Structure?

Every System V message queue that exists in the kernel is represented internally by a data structure called msqid_ds. This structure stores all the metadata about the queue — its ownership, permissions, usage statistics, timestamps, and capacity limits.

Think of it as the “inode” of a message queue. Just as a filesystem inode stores metadata about a file (owner, size, timestamps), the msqid_ds stores metadata about a message queue.

Understanding this structure is essential because:

  • It is the return value of msgctl(IPC_STAT) — you use it to inspect queue state.
  • It is the input to msgctl(IPC_SET) — you use it to change queue settings.
  • Its fields are updated automatically by the kernel when msgsnd() or msgrcv() is called.
  • It is a very common interview topic — interviewers often ask what each field means.

The Complete Structure Definition
/* Defined in <sys/msg.h> */
struct msqid_ds {
    struct ipc_perm  msg_perm;      /* Ownership and permissions */
    time_t           msg_stime;     /* Time of last msgsnd() */
    time_t           msg_rtime;     /* Time of last msgrcv() */
    time_t           msg_ctime;     /* Time of last change (creation or IPC_SET) */
    unsigned long    __msg_cbytes;  /* Current bytes in queue (non-standard) */
    msgqnum_t        msg_qnum;      /* Number of messages currently in queue */
    msglen_t         msg_qbytes;    /* Maximum bytes allowed in queue */
    pid_t            msg_lspid;     /* PID of process that called last msgsnd() */
    pid_t            msg_lrpid;     /* PID of process that called last msgrcv() */
};
Naming note: The structure uses the abbreviated spelling msq (instead of msgq) in its name msqid_ds. This is the only System V message queue interface that uses this abbreviated spelling — a historical inconsistency that exists purely to confuse programmers (the TLPI author notes this explicitly!).

Field-by-Field Explanation

AUTO = updated automatically by the kernel   IPC_SET = can be updated via IPC_SET

msg_perm IPC_SET (partial)
struct ipc_perm
This is a sub-structure that stores ownership and permission information. It contains: uid (owner UID), gid (owner GID), cuid (creator UID), cgid (creator GID), and mode (9-bit permission flags like a file). Via IPC_SET, you can change uid, gid, and mode. The creator fields (cuid, cgid) cannot be changed.
msg_stime AUTO
time_t
Stores the time (seconds since the Unix Epoch, Jan 1 1970) of the most recent successful msgsnd() call on this queue. When a queue is first created, this field is set to 0 (meaning “never”). Each successful send updates it to the current time.
msg_rtime AUTO
time_t
Stores the time of the most recent successful msgrcv() call on this queue. Initialised to 0 when the queue is created. Each successful receive updates it to the current time. Useful for detecting queues that have not been read for a long time.
msg_ctime AUTO
time_t
Set to the current time when the queue is created and updated again each time IPC_SET is called (i.e., whenever the queue’s metadata is changed). Unlike msg_stime and msg_rtime, this is not affected by sending or receiving messages — only by creation and configuration changes.
msg_qnum AUTO
msgqnum_t (unsigned integer)
The current number of messages sitting in the queue waiting to be read. Increases by 1 on each successful msgsnd() and decreases by 1 on each successful msgrcv(). A value of 0 means the queue is empty. You can use this to monitor queue backlog.
msg_qbytes IPC_SET
msglen_t (unsigned integer)
The maximum total bytes that all messages in the queue can occupy at any one time. When this limit is reached, msgsnd() blocks (or returns EAGAIN if IPC_NOWAIT is set). The system-wide default is in /proc/sys/kernel/msgmnb (typically 16384 bytes). Can be raised by a privileged process using IPC_SET with CAP_SYS_RESOURCE.
msg_lspid AUTO
pid_t
The Process ID (PID) of the process that performed the most recent successful msgsnd() call. Initialised to 0 at queue creation. Useful for debugging to identify which process last wrote to the queue.
msg_lrpid AUTO
pid_t
The Process ID of the process that performed the most recent successful msgrcv() call. Initialised to 0 at queue creation. Useful for debugging to identify which process last read from the queue.
__msg_cbytes AUTO
unsigned long
The current number of bytes occupied by all messages currently in the queue. Note the double-underscore prefix — this is a non-standard, Linux-specific field. It is not specified in POSIX/SUSv3. Portable code should avoid relying on this field; use msg_qnum instead for counting.

When Each Field Gets Updated — Summary Table
Field Initial Value Updated by Settable via IPC_SET?
msg_perm Set from caller context at creation Queue creation, IPC_SET Yes (uid, gid, mode)
msg_stime 0 (never) Each successful msgsnd() No
msg_rtime 0 (never) Each successful msgrcv() No
msg_ctime Creation time Creation, IPC_SET No (set automatically)
msg_qnum 0 msgsnd() +1, msgrcv() -1 No
msg_qbytes System default (msgmnb) IPC_SET only Yes
msg_lspid 0 Each successful msgsnd() No
msg_lrpid 0 Each successful msgrcv() No
__msg_cbytes 0 msgsnd() / msgrcv() No (Linux-specific)

Deep Dive: The ipc_perm Sub-structure

The msg_perm field is itself a structure. It is the same ipc_perm sub-structure used across all System V IPC objects (message queues, semaphores, shared memory).

struct ipc_perm {
    key_t          __key;    /* Key supplied to msgget() — non-modifiable */
    uid_t          uid;      /* Effective UID of owner — settable via IPC_SET */
    gid_t          gid;      /* Effective GID of owner — settable via IPC_SET */
    uid_t          cuid;     /* Effective UID of creator — NOT settable */
    gid_t          cgid;     /* Effective GID of creator — NOT settable */
    unsigned short mode;     /* Permission bits (rwxrwxrwx format) — settable via IPC_SET */
    unsigned short __seq;    /* Sequence number (used in msqid generation) */
};
Owner vs. Creator: The distinction between owner and creator is important. The creator is the process that originally called msgget(IPC_CREAT). The owner starts as the creator, but can be changed via IPC_SET. This allows a privileged setup process to create a queue and then hand ownership to another UID. The creator fields (cuid, cgid) can never be changed.
Permission bits: The mode field works exactly like Unix file permissions — 9 bits: 3 for owner (read/write), 3 for group (read/write), 3 for others (read/write). The execute bit has no meaning for IPC objects and is ignored. For example, 0660 allows read+write for owner and group, nothing for others.

Understanding the Timestamp Fields

All three timestamp fields — msg_stime, msg_rtime, msg_ctime — are of type time_t and store time as seconds since the Unix Epoch (midnight, 1 January 1970, UTC). This is the same format as the value returned by time().

A value of 0 in msg_stime or msg_rtime means “this operation has never occurred on this queue.” This is a useful sentinel: if msg_stime is 0, the queue has never had a message sent to it. If msg_rtime is 0, no message has ever been received from it.

msg_ctime is different — it is set to the current time at the moment of queue creation and also updated whenever IPC_SET is called. It records the last “configuration change” to the queue, similar to the st_ctime field in a file’s stat structure.

To print a timestamp field in human-readable form, use ctime(): printf("Last send: %s", ctime(&info.msg_stime)); Note that ctime() returns a string with a trailing newline, so you typically don’t need \n in your format string.

msg_qbytes — The Queue Capacity Limit

This field is the most operationally important one for system performance. It controls how much data can accumulate in the queue before msgsnd() is forced to block.

How the limit works:

  • The kernel tracks the total bytes of all messages currently in the queue via __msg_cbytes.
  • When a new msgsnd() arrives, the kernel checks if adding the new message would push __msg_cbytes over msg_qbytes.
  • If so, and IPC_NOWAIT is not set, the sending process blocks until enough messages are consumed to make room.
  • If IPC_NOWAIT is set, msgsnd() immediately returns -1 with errno = EAGAIN.
# Check the system-wide default max queue size cat /proc/sys/kernel/msgmnb # Check the system-wide max number of message queues cat /proc/sys/kernel/msgmni # Temporarily increase max queue size to 1MB (requires root) echo 1048576 > /proc/sys/kernel/msgmnb

Code Example 1 — Reading and Printing All msqid_ds Fields
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

/* Helper: print time_t field. Shows "Never" if t == 0 */
static void print_time(const char *label, time_t t)
{
    if (t == 0)
        printf("%-28s: Never\n", label);
    else
        printf("%-28s: %s", label, ctime(&t)); /* ctime adds \n */
}

int main(void)
{
    int           msqid;
    struct msqid_ds info;

    /* Create a private queue with read+write for owner only */
    msqid = msgget(IPC_PRIVATE, 0600);
    if (msqid == -1) { perror("msgget"); exit(1); }

    /* Read queue metadata */
    if (msgctl(msqid, IPC_STAT, &info) == -1) {
        perror("msgctl IPC_STAT");
        exit(1);
    }

    printf("====== msqid_ds contents (msqid=%d) ======\n\n", msqid);

    /* Ownership and permissions */
    printf("%-28s: %d\n",    "msg_perm.uid  (owner)",  info.msg_perm.uid);
    printf("%-28s: %d\n",    "msg_perm.gid  (owner)",  info.msg_perm.gid);
    printf("%-28s: %d\n",    "msg_perm.cuid (creator)", info.msg_perm.cuid);
    printf("%-28s: %d\n",    "msg_perm.cgid (creator)", info.msg_perm.cgid);
    printf("%-28s: %04o\n",  "msg_perm.mode (perms)",  info.msg_perm.mode & 0777);

    /* Timestamps */
    print_time("msg_stime (last msgsnd)", info.msg_stime);
    print_time("msg_rtime (last msgrcv)", info.msg_rtime);
    print_time("msg_ctime (last change)", info.msg_ctime);

    /* Queue statistics */
    printf("%-28s: %lu\n", "msg_qnum  (messages now)",
           (unsigned long)info.msg_qnum);
    printf("%-28s: %lu bytes\n", "msg_qbytes (max capacity)",
           (unsigned long)info.msg_qbytes);
    printf("%-28s: %d\n", "msg_lspid (last sender PID)",  info.msg_lspid);
    printf("%-28s: %d\n", "msg_lrpid (last receiver PID)", info.msg_lrpid);

    /* Cleanup */
    msgctl(msqid, IPC_RMID, NULL);
    return 0;
}
/*
 * Sample Output (right after creation — no messages sent/received yet):
 *   ====== msqid_ds contents (msqid=98308) ======
 *   msg_perm.uid  (owner)      : 1000
 *   msg_perm.gid  (owner)      : 1000
 *   msg_perm.cuid (creator)    : 1000
 *   msg_perm.cgid (creator)    : 1000
 *   msg_perm.mode (perms)      : 0600
 *   msg_stime (last msgsnd)    : Never
 *   msg_rtime (last msgrcv)    : Never
 *   msg_ctime (last change)    : Mon Jun  8 09:00:00 2026
 *   msg_qnum  (messages now)   : 0
 *   msg_qbytes (max capacity)  : 16384 bytes
 *   msg_lspid (last sender PID): 0
 *   msg_lrpid (last receiver PID): 0
 */

Code Example 2 — Watching msqid_ds Fields Change After Send and Receive
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct message {
    long mtype;
    char mtext[64];
};

static void show_queue_stats(int msqid, const char *when)
{
    struct msqid_ds info;
    if (msgctl(msqid, IPC_STAT, &info) == -1) {
        perror("msgctl IPC_STAT"); return;
    }
    printf("\n--- State: %-30s ---\n", when);
    printf("  msg_qnum  (messages in queue): %lu\n",
           (unsigned long)info.msg_qnum);
    printf("  msg_stime (last send)         : %s",
           info.msg_stime ? ctime(&info.msg_stime) : "Never\n");
    printf("  msg_rtime (last recv)         : %s",
           info.msg_rtime ? ctime(&info.msg_rtime) : "Never\n");
    printf("  msg_lspid (last sender PID)   : %d\n",  info.msg_lspid);
    printf("  msg_lrpid (last receiver PID) : %d\n",  info.msg_lrpid);
}

int main(void)
{
    int msqid;
    struct message msg;

    msqid = msgget(IPC_PRIVATE, 0600);
    if (msqid == -1) { perror("msgget"); exit(1); }

    /* --- State 1: Just after creation --- */
    show_queue_stats(msqid, "After creation");

    /* --- Send a message --- */
    msg.mtype = 1;
    strncpy(msg.mtext, "Hello from EmbeddedPathashala!", sizeof(msg.mtext)-1);
    if (msgsnd(msqid, &msg, strlen(msg.mtext)+1, 0) == -1) {
        perror("msgsnd"); exit(1);
    }

    /* --- State 2: After sending --- */
    show_queue_stats(msqid, "After msgsnd()");

    /* --- Receive the message --- */
    if (msgrcv(msqid, &msg, sizeof(msg.mtext), 0, 0) == -1) {
        perror("msgrcv"); exit(1);
    }

    /* --- State 3: After receiving --- */
    show_queue_stats(msqid, "After msgrcv()");

    msgctl(msqid, IPC_RMID, NULL);
    return 0;
}
/*
 * This example clearly shows:
 *  - msg_qnum goes 0 → 1 → 0 as a message is sent then received
 *  - msg_stime is "Never" until the first msgsnd()
 *  - msg_rtime is "Never" until the first msgrcv()
 *  - msg_lspid and msg_lrpid are 0 until the first send/receive
 */

Interview Questions — msqid_ds Structure
Q1. What is the msqid_ds structure and what is its purpose?
msqid_ds is the kernel-maintained data structure associated with every System V message queue. It serves as the “metadata record” for the queue — storing ownership, permissions, usage timestamps, current message count, byte capacity, and PIDs of the last sender and receiver. Every message queue has exactly one such structure. It is accessed via msgctl() with IPC_STAT (to read) or IPC_SET (to modify certain fields).
Q2. What initial values do msg_stime and msg_rtime have after queue creation?
Both msg_stime and msg_rtime are set to 0 when the message queue is first created. A value of 0 means “this operation has never been performed on this queue.” msg_stime is updated to the current time on each successful msgsnd(), and msg_rtime is updated on each successful msgrcv(). This is a common interview question — the answer is 0, not the creation time.
Q3. What is the difference between msg_stime and msg_ctime?
msg_stime tracks the last time a message was sent to the queue via msgsnd(). It is 0 until the first send and then updated on every subsequent send.

msg_ctime tracks the last time the queue’s configuration was changed — either when the queue was first created, or when msgctl(IPC_SET) was called to modify the queue’s metadata. Sending and receiving messages does not update msg_ctime.

Q4. What does msg_qnum represent and how does it change during normal operation?
msg_qnum stores the number of messages currently in the queue at the time of the last IPC_STAT call. It starts at 0 when the queue is created. Each successful msgsnd() increments it by 1, and each successful msgrcv() decrements it by 1. You can use this to implement a “queue depth monitor” — polling IPC_STAT periodically and alerting if msg_qnum grows too large, which would indicate a consumer is not keeping up with a producer.
Q5. What is the difference between the owner and creator fields in ipc_perm?
The creator (cuid, cgid) identifies the process that originally called msgget(IPC_CREAT) and is immutable — it can never be changed by any means.

The owner (uid, gid) starts as the same as the creator but can be changed via msgctl(IPC_SET). This is useful when a privileged setup process creates a queue and then transfers ownership to a less-privileged user. Deletion rights are based on matching the owner OR creator UID.

Q6. What does msg_qbytes control and what happens when it is exceeded?
msg_qbytes is the maximum total bytes that all messages combined can occupy in the queue simultaneously. When a process calls msgsnd() and the new message would cause the total to exceed msg_qbytes, the following happens: if IPC_NOWAIT is not set, the calling process blocks until space becomes available. If IPC_NOWAIT is set, msgsnd() returns -1 with errno = EAGAIN. The default system value is in /proc/sys/kernel/msgmnb.
Q7. What are msg_lspid and msg_lrpid, and what are their initial values?
msg_lspid holds the PID of the process that made the most recent successful msgsnd() call. msg_lrpid holds the PID of the process that made the most recent successful msgrcv() call. Both are initialised to 0 at queue creation (since no send or receive has occurred yet). These fields are extremely useful for debugging — for example, to identify which process is filling up a queue or which process last consumed a message.
Q8. Why does __msg_cbytes have a double-underscore prefix?
The double-underscore prefix (__msg_cbytes) is a convention in C to mark fields as implementation-specific / non-standard. This field is Linux-specific and not defined in POSIX or SUSv3. Portable code targeting multiple Unix platforms should avoid depending on it. On Linux, it stores the current total bytes occupied by all messages in the queue, but since it is non-standard, its name and existence cannot be guaranteed on other systems like macOS, Solaris, or AIX.
Q9. If you want to change only the permissions of a message queue, what is the correct approach?
The correct approach is: (1) First call msgctl(msqid, IPC_STAT, &info) to populate your local msqid_ds structure with the current values. (2) Then modify only info.msg_perm.mode to the new permission bits. (3) Finally call msgctl(msqid, IPC_SET, &info) to apply the change. Never fill a msqid_ds from scratch for IPC_SET — you would risk zeroing out uid and gid values and accidentally changing the queue’s ownership.
Q10. What type are the msg_stime, msg_rtime, and msg_ctime fields, and what does a value of 0 mean?
All three are of type time_t, which stores time as seconds since the Unix Epoch (January 1, 1970, 00:00:00 UTC). A value of 0 is the sentinel meaning “this event has never occurred” — specifically, it means: no message has ever been sent (msg_stime), no message has ever been received (msg_rtime), or (in theory, though this never happens) the queue was never created or modified (msg_ctime). In practice msg_ctime is always nonzero because it is set at creation time.
Q11. How is the msqid_ds structure similar to a filesystem inode?
The analogy is very close: just as a filesystem inode is a kernel data structure that stores metadata about a file (owner, group, permissions, access/modify/change timestamps, size, link count), the msqid_ds structure stores metadata about a message queue (owner, group, permissions, send/receive/change timestamps, message count, byte capacity, and last-actor PIDs). Neither structure stores the actual data content — the inode doesn’t store file bytes, and msqid_ds doesn’t store message content.
Q12. What is the msgqnum_t and msglen_t data type, and where are they defined?
msgqnum_t and msglen_t are unsigned integer types used to type the msg_qnum and msg_qbytes fields respectively. They are defined in <sys/msg.h> and their exact width may vary by platform (typically 32-bit or 64-bit). They are specified in SUSv3 to ensure these fields are always unsigned — message counts and byte sizes are inherently non-negative. Using a defined type rather than plain unsigned long improves portability across different Unix implementations.

Continue Learning

You have completed Sections 46.3 and 46.4 of Chapter 46 — System V Message Queues.

← Previous: msgctl() Operations Back to Course Index

Leave a Reply

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