System V Message Queues The msqid_ds Structure Fields

 

System V Message Queues
Chapter 46 · Section 46.4 — The msqid_ds Structure Fields
8
Structure Fields
2
Code Examples
10+
Interview Qs

Key Concepts in This Section
msqid_ds msgctl() IPC_STAT IPC_SET msg_qbytes msg_qnum msg_lspid msg_lrpid msg_ctime __msg_cbytes CAP_SYS_RESOURCE MSGMNB

What is msqid_ds?

Every System V message queue that exists in the Linux kernel has an associated data structure maintained inside the kernel. This structure is called msqid_ds. It acts like the “control block” of the message queue — it stores metadata about the queue such as how many messages are in it, how much data it holds, who last sent/received, and what the current limits are.

When you call msgctl() with the IPC_STAT operation, the kernel copies a snapshot of this structure into your user-space variable. When you call msgctl() with IPC_SET, the kernel updates selected fields from your variable back into the kernel’s copy. This is the only way user programs interact with these internal fields.

Mental Model: Think of msqid_ds like the header block of a file on disk — it does not contain the actual messages, but it records everything important about the queue. The kernel updates it automatically every time you send or receive a message.

The msqid_ds Structure — Visual Layout

Below is how the key fields of msqid_ds relate to the operations that affect them:

msqid_ds — Field Summary Diagram
Field Name
Set / Updated When
Who Can Modify
msg_ctime
Queue created; IPC_SET performed
Kernel (auto)
__msg_cbytes
msgsnd() / msgrcv() — tracks total bytes in queue
Kernel (auto)
msg_qnum
msgsnd() increments; msgrcv() decrements
Kernel (auto)
msg_qbytes
Initialized to MSGMNB; can be changed via IPC_SET
Privileged/Unpriv
msg_lspid
Updated on every successful msgsnd()
Kernel (auto)
msg_lrpid
Updated on every successful msgrcv()
Kernel (auto)

Detailed Explanation of Each Field

1. msg_ctime — Change Time

This field stores a time_t timestamp. The kernel sets it to the current time in two situations: (a) when the message queue is first created via msgget(), and (b) whenever msgctl(IPC_SET, ...) is successfully called on this queue.

It does NOT change when messages are sent or received — it only reflects structural changes to the queue’s configuration, not data-plane activity.

Analogy: Think of msg_ctime like the “last modified” timestamp of a configuration file. Editing the config updates it, but reading the file does not.

2. __msg_cbytes — Current Bytes in Queue

This field tracks the total number of bytes currently present in the mtext fields of all messages waiting in the queue. It starts at 0 when the queue is created, and the kernel adjusts it up and down after every successful msgsnd() and msgrcv() respectively.

Note the double underscore prefix — this field is not specified by SUSv3 (POSIX), but almost every real UNIX implementation provides it because it is essential for understanding how full the queue is at byte level.

Portability Note: Because __msg_cbytes is Linux-specific (not in the POSIX standard), portable programs should not rely on it. However, since most UNIX systems have an equivalent field, it is safe in practice for Linux-only code.

3. msg_qnum — Number of Messages

This field counts the total number of messages currently sitting in the queue. It is initialized to 0 on queue creation. The kernel increments it by 1 on a successful msgsnd() and decrements it by 1 on a successful msgrcv().

This is a simple integer counter — it counts messages, not bytes. If you send 3 messages of 1000 bytes each, msg_qnum becomes 3, while __msg_cbytes becomes 3000.

4. msg_qbytes — Queue Byte Limit (Most Important)

This field sets the maximum total bytes (in mtext fields) that can exist in the queue at any one time. It is the capacity control knob of the message queue.

At queue creation, it is initialized to the system-wide MSGMNB kernel parameter (default 16384 bytes on older kernels). After creation, it can be changed per-queue using msgctl(IPC_SET). The rules for changing it are:

Process Type Allowed Range for msg_qbytes
Unprivileged process 0 to MSGMNB (cannot exceed the system-wide limit)
Privileged process (CAP_SYS_RESOURCE) 0 to INT_MAX (2,147,483,647 on 32-bit platforms)

When the queue is full (i.e., __msg_cbytes would exceed msg_qbytes after adding a new message), msgsnd() blocks. If IPC_NOWAIT was passed, it fails immediately with EAGAIN.

Practical Use: You can use msgctl(IPC_SET) to raise msg_qbytes for a high-throughput queue, or lower it to throttle senders. This is the primary tuning knob for message queue capacity.

5. msg_lspid — Last Sender PID

This is set to 0 when the queue is created. After each successful msgsnd(), the kernel records the PID of the process that sent the message here. This allows a receiver to identify who sent the most recent message.

6. msg_lrpid — Last Receiver PID

Similar to msg_lspid, this is set to 0 at creation and updated to the PID of the most recent receiver on every successful msgrcv(). It allows a sender to audit which process last consumed a message.

POSIX / SUSv3 Compliance of Fields

SUSv3 (the POSIX standard) mandates all the fields above except __msg_cbytes. The double-underscore prefix is a conventional Linux signal that the field is an implementation extension. Despite this, most real UNIX variants (Solaris, HP-UX, AIX, FreeBSD) provide an equivalent field because tracking current queue occupancy in bytes is practically essential.

Interview Tip: If asked “which fields of msqid_ds are not in POSIX?”, the answer is __msg_cbytes.

Code Example 1 — Reading msqid_ds with IPC_STAT

This program creates a message queue, sends one message, then reads and prints all key fields of the associated msqid_ds structure using IPC_STAT.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <time.h>

/* Message structure for msgsnd/msgrcv */
struct my_msg {
    long mtype;          /* message type - must be > 0 */
    char mtext[64];      /* message payload */
};

int main(void)
{
    int msqid;
    struct msqid_ds ds;
    struct my_msg msg;

    /* Step 1: Create a private message queue (key = IPC_PRIVATE) */
    msqid = msgget(IPC_PRIVATE, IPC_CREAT | 0600);
    if (msqid == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }
    printf("Created message queue, msqid = %d\n\n", msqid);

    /* Step 2: Send one message to the queue */
    msg.mtype = 1;
    strncpy(msg.mtext, "Hello EmbeddedPathashala", sizeof(msg.mtext));
    if (msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0) == -1) {
        perror("msgsnd");
        exit(EXIT_FAILURE);
    }
    printf("Sent message: \"%s\"\n\n", msg.mtext);

    /* Step 3: Retrieve msqid_ds structure using IPC_STAT */
    if (msgctl(msqid, IPC_STAT, &ds) == -1) {
        perror("msgctl IPC_STAT");
        exit(EXIT_FAILURE);
    }

    /* Step 4: Print all important fields */
    printf("===== msqid_ds Fields =====\n");
    printf("msg_qnum   (messages in queue)  : %lu\n",
           (unsigned long) ds.msg_qnum);
    printf("msg_qbytes (max bytes allowed)  : %lu\n",
           (unsigned long) ds.msg_qbytes);
    printf("msg_lspid  (last sender PID)    : %d\n",
           (int) ds.msg_lspid);
    printf("msg_lrpid  (last receiver PID)  : %d\n",
           (int) ds.msg_lrpid);
    printf("msg_ctime  (last change time)   : %s",
           ctime(&ds.msg_ctime));

    /* Step 5: Cleanup - remove the queue */
    if (msgctl(msqid, IPC_RMID, NULL) == -1) {
        perror("msgctl IPC_RMID");
        exit(EXIT_FAILURE);
    }
    printf("\nMessage queue removed.\n");
    return 0;
}
Compile & Run: gcc -o stat_demo stat_demo.c && ./stat_demo
After msgsnd, msg_qnum should be 1 and msg_lspid should be your process PID.

Code Example 2 — Changing msg_qbytes with IPC_SET

This example mirrors Listing 46-5 from TLPI. It shows how to use IPC_STAT to fetch the current structure, modify msg_qbytes, and push the update back with IPC_SET. The pattern is always: read first, modify, write back.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/msg.h>

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

    /* Create a private message queue */
    msqid = msgget(IPC_PRIVATE, IPC_CREAT | 0600);
    if (msqid == -1) { perror("msgget"); exit(1); }

    /* --- Read current state --- */
    if (msgctl(msqid, IPC_STAT, &ds) == -1) {
        perror("msgctl IPC_STAT"); exit(1);
    }
    printf("Before: msg_qbytes = %lu bytes\n",
           (unsigned long) ds.msg_qbytes);

    /*
     * Modify msg_qbytes.
     * Unprivileged process: max = MSGMNB (typically 16384)
     * Privileged process  : max = INT_MAX
     *
     * Here we set it to 4096 bytes (lower than default).
     * Once the queue holds 4096 bytes of message data,
     * further msgsnd() calls will block.
     */
    ds.msg_qbytes = 4096;

    /* --- Write back to kernel --- */
    if (msgctl(msqid, IPC_SET, &ds) == -1) {
        perror("msgctl IPC_SET"); exit(1);
    }

    /* Verify the change */
    if (msgctl(msqid, IPC_STAT, &ds) == -1) {
        perror("msgctl IPC_STAT verify"); exit(1);
    }
    printf("After : msg_qbytes = %lu bytes\n",
           (unsigned long) ds.msg_qbytes);

    /* Cleanup */
    msgctl(msqid, IPC_RMID, NULL);
    printf("Queue removed.\n");
    return 0;
}
Important: The IPC_SET pattern is always: IPC_STAT first → modify field → IPC_SET. Never call IPC_SET with an uninitialized structure — you may accidentally corrupt other fields like permissions.

Interview Questions
Q1. What is msqid_ds and where does the kernel store it?
msqid_ds is a kernel data structure associated with every System V message queue. The kernel maintains it in kernel memory, one per message queue. It stores metadata about the queue: how many messages it holds, byte capacity, last-sender/receiver PIDs, and timestamps. User programs access it only via msgctl(IPC_STAT) and msgctl(IPC_SET).
Q2. What is the difference between msg_qnum and __msg_cbytes?
msg_qnum counts the number of messages in the queue (an integer count). __msg_cbytes counts the total bytes of mtext data across all messages. A queue with 3 messages of 100 bytes each has msg_qnum = 3 and __msg_cbytes = 300. The queue is full when __msg_cbytes reaches msg_qbytes, regardless of how many messages are present.
Q3. What triggers an update to msg_ctime?
msg_ctime is updated only when the queue is created or when msgctl(IPC_SET) is successfully called. It does NOT change on msgsnd() or msgrcv() — those operations update msg_stime and msg_rtime respectively. The ‘c’ in msg_ctime stands for “change” (of attributes), not “create”.
Q4. What happens when msgsnd() is called and the queue is full?
If the total bytes already in the queue equals msg_qbytes, and a new msgsnd() would push it over the limit, the calling process blocks until space becomes available (another process calls msgrcv()). If IPC_NOWAIT flag was passed, msgsnd() returns immediately with errno = EAGAIN instead of blocking.
Q5. How does an unprivileged process change msg_qbytes? What is the limit?
An unprivileged process can call msgctl(IPC_SET) to change msg_qbytes, but only within the range 0 to MSGMNB (the system-wide maximum, typically 16384 bytes by default). A privileged process with the CAP_SYS_RESOURCE capability can set it all the way up to INT_MAX (2,147,483,647 on 32-bit). The MSGMNB ceiling itself can be raised by a privileged user by writing to /proc/sys/kernel/msgmnb.
Q6. What is the correct sequence for modifying msg_qbytes using msgctl?
The correct sequence is: (1) call msgctl(msqid, IPC_STAT, &ds) to read the current structure into ds, (2) modify ds.msg_qbytes, (3) call msgctl(msqid, IPC_SET, &ds) to write the updated structure back to the kernel. Never skip the IPC_STAT step — doing so risks corrupting other fields with uninitialized values.
Q7. Is __msg_cbytes specified by POSIX/SUSv3?
No. __msg_cbytes is NOT part of SUSv3 (POSIX). The double-underscore prefix signals that it is an implementation extension. However, most UNIX implementations (Linux, Solaris, AIX, etc.) provide an equivalent field because tracking current queue occupancy in bytes is essential for correct behaviour. For strictly portable code, avoid relying on it.
Q8. What do msg_lspid and msg_lrpid tell you?
msg_lspid holds the PID of the process that most recently called msgsnd() on this queue. msg_lrpid holds the PID of the process that most recently called msgrcv(). Both are initialized to 0 at queue creation. They are useful for debugging — e.g., to discover which process is filling up or draining the queue.
Q9. If you call msgctl(IPC_SET) without first calling IPC_STAT, what can go wrong?
If you declare a struct msqid_ds ds locally without initializing it and call IPC_SET directly, the permissions field (msg_perm.mode) and other fields will contain garbage values. This could accidentally change the queue’s permissions, locking out legitimate processes. Always do IPC_STAT first.
Q10. What capability is needed to set msg_qbytes above MSGMNB?
The CAP_SYS_RESOURCE Linux capability is required. This capability allows a process to override resource limits. Without it, trying to set msg_qbytes above MSGMNB will fail with EPERM.

Leave a Reply

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