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.
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.Below is how the key fields of msqid_ds relate to the operations that affect them:
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.
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.
__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.
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.
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.
__msg_cbytes.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;
}
gcc -o stat_demo stat_demo.c && ./stat_demoAfter msgsnd, msg_qnum should be 1 and msg_lspid should be your process PID.
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;
}
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).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.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”.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.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.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.__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.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.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.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.Next: Message Queue Limits (46.5) → Section 46.6: Listing All Queues
