Chapter 46 · File 4 of 6
Queue Control Operations
msgctl() · msqid_ds · IPC_STAT · IPC_SET · IPC_RMID
The msgctl() system call is the Swiss army knife of message queue management. It lets you inspect queue properties, change settings, and delete the queue. You also use this call in every cleanup path.
The msgctl() System Call
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
/* msqid — queue identifier cmd — what to do: IPC_STAT, IPC_SET, or IPC_RMID buf — pointer to msqid_ds struct (used by STAT and SET; NULL for RMID) Returns 0 on success, -1 on error */
| Command | What it does | buf argument |
|---|---|---|
IPC_STAT |
Reads current queue information into buf | Must point to a valid msqid_ds (output) |
IPC_SET |
Updates selected queue properties from buf | Must point to a msqid_ds with new values (input) |
IPC_RMID |
Deletes the queue immediately | Pass NULL |
The msqid_ds Structure
This structure holds all metadata about a message queue. It is filled by IPC_STAT and read/written by IPC_SET.
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 (msgctl) */
unsigned long msg_cbytes; /* Current bytes in queue */
msgqnum_t msg_qnum; /* Current number of messages in queue */
msglen_t msg_qbytes; /* Max bytes the queue can hold */
pid_t msg_lspid; /* PID of last msgsnd() caller */
pid_t msg_lrpid; /* PID of last msgrcv() caller */
};
/* The ipc_perm sub-struct */
struct ipc_perm {
key_t __key; /* Key given to msgget() */
uid_t uid; /* Owner's user ID */
gid_t gid; /* Owner's group ID */
uid_t cuid; /* Creator's user ID */
gid_t cgid; /* Creator's group ID */
unsigned short mode; /* Permission bits */
};
Code Example 1 — IPC_STAT: Reading Queue Information
/* msgctl_stat.c — Read queue properties with IPC_STAT */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct Msg { long mtype; char text[64]; };
void print_queue_info(int qid)
{
struct msqid_ds info;
if (msgctl(qid, IPC_STAT, &info) == -1) {
perror("msgctl IPC_STAT");
return;
}
printf("--- Queue Info (ID=%d) ---\n", qid);
printf(" Current messages : %lu\n", (unsigned long)info.msg_qnum);
printf(" Bytes in queue : %lu\n", (unsigned long)info.msg_cbytes);
printf(" Max queue bytes : %lu\n", (unsigned long)info.msg_qbytes);
printf(" Permissions : %04o\n", info.msg_perm.mode & 0777);
printf(" Owner UID : %u\n", info.msg_perm.uid);
printf(" Last send PID : %d\n", info.msg_lspid);
printf(" Last recv PID : %d\n", info.msg_lrpid);
if (info.msg_stime)
printf(" Last msgsnd() : %s", ctime(&info.msg_stime));
if (info.msg_rtime)
printf(" Last msgrcv() : %s", ctime(&info.msg_rtime));
printf("-------------------------\n");
}
int main(void)
{
int qid = msgget(IPC_PRIVATE, IPC_CREAT | 0644);
if (qid == -1) { perror("msgget"); return 1; }
printf("=== Queue just created (empty) ===\n");
print_queue_info(qid);
/* Send two messages */
struct Msg m1 = { .mtype = 1 }; strcpy(m1.text, "First message");
struct Msg m2 = { .mtype = 2 }; strcpy(m2.text, "Second message");
msgsnd(qid, &m1, sizeof(m1.text), 0);
msgsnd(qid, &m2, sizeof(m2.text), 0);
printf("\n=== After sending 2 messages ===\n");
print_queue_info(qid);
msgctl(qid, IPC_RMID, NULL);
return 0;
}
Code Example 2 — IPC_SET: Changing Queue Limits
You can change the maximum bytes the queue can hold (msg_qbytes) and the queue’s ownership/permissions using IPC_SET. The rest of the fields are read-only.
/* msgctl_set.c — Change the queue byte limit */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main(void)
{
int qid = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
if (qid == -1) { perror("msgget"); return 1; }
/* Step 1: Read current settings */
struct msqid_ds ds;
if (msgctl(qid, IPC_STAT, &ds) == -1) { perror("IPC_STAT"); return 1; }
printf("Current msg_qbytes = %lu\n", (unsigned long)ds.msg_qbytes);
/* Step 2: Modify msg_qbytes — increase to 65536 bytes */
/* Only root can increase beyond the system default (MSGMNB)
Any user can DECREASE to a value <= current */
ds.msg_qbytes = 65536;
if (msgctl(qid, IPC_SET, &ds) == -1) {
perror("IPC_SET (you may need root to increase beyond default)");
} else {
/* Verify */
msgctl(qid, IPC_STAT, &ds);
printf("New msg_qbytes = %lu\n", (unsigned long)ds.msg_qbytes);
}
/* Change permissions */
ds.msg_perm.mode = 0600; /* Only owner can r/w */
msgctl(qid, IPC_SET, &ds);
printf("Permissions changed to 0600\n");
msgctl(qid, IPC_RMID, NULL);
return 0;
}
Code Example 3 — Safe Cleanup with Signal Handler
A real program should delete its queue even when interrupted by Ctrl+C. This example sets up a signal handler to clean up:
/* safe_cleanup.c — Guarantee queue deletion even on SIGINT/SIGTERM */
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
static int g_qid = -1; /* Global so signal handler can access it */
void cleanup_handler(int sig)
{
if (g_qid != -1) {
printf("\n[Signal %d] Deleting queue %d...\n", sig, g_qid);
if (msgctl(g_qid, IPC_RMID, NULL) == -1)
perror("msgctl IPC_RMID in handler");
else
printf("Queue deleted. Exiting.\n");
g_qid = -1;
}
exit(0);
}
int main(void)
{
/* Register cleanup for common termination signals */
signal(SIGINT, cleanup_handler);
signal(SIGTERM, cleanup_handler);
g_qid = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
if (g_qid == -1) { perror("msgget"); return 1; }
printf("Queue created (ID=%d). Press Ctrl+C to test cleanup.\n", g_qid);
/* Simulate a running server */
struct { long mtype; char text[64]; } m;
for (int i = 1; i <= 5; i++) {
m.mtype = 1;
snprintf(m.text, sizeof(m.text), "Message %d", i);
msgsnd(g_qid, &m, sizeof(m.text), 0);
printf("Sent message %d\n", i);
sleep(1);
}
/* Normal exit also cleans up */
cleanup_handler(0);
return 0;
}
What Happens When You Delete a Queue?
| Who is affected | What happens |
|---|---|
| Processes blocked in msgsnd() | Return -1, errno = EIDRM |
| Processes blocked in msgrcv() | Return -1, errno = EIDRM |
| Messages still in the queue | All messages are destroyed — unread data is lost |
| Future msgget() with same key | Creates a completely new queue (different msqid) |
Interview Questions — msgctl() & msqid_ds
ipcrm -q msqid (by ID) or ipcrm -Q key (by key). To list all queues first: ipcs -q.