Queue Control Operations – free linux system programming course

 

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/types.h>
#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?

Effect of msgctl(msqid, IPC_RMID, NULL)
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)
Race condition warning: Only the owner (or root) can delete a queue with IPC_RMID. If a client deletes the server’s queue by accident, all blocked processes get EIDRM errors. Design carefully when multiple processes share ownership.

Interview Questions — msgctl() & msqid_ds

Q1. What are the three commands for msgctl()? IPC_STAT (read queue metadata into msqid_ds), IPC_SET (update selected fields of msqid_ds in the kernel), and IPC_RMID (delete the queue immediately).
Q2. Which fields of msqid_ds can be modified by IPC_SET? msg_perm.uid, msg_perm.gid, msg_perm.mode (permissions), and msg_qbytes (max queue capacity). All other fields are read-only.
Q3. What is msg_qbytes? What is its default value? The maximum number of bytes that can be stored across all messages in the queue. The default on most Linux systems is 16384 bytes (MSGMNB). Only root can increase it above the system default.
Q4. What happens to processes blocked in msgsnd/msgrcv when the queue is deleted? They are woken up and their system call returns -1 with errno = EIDRM (identifier removed).
Q5. What information does msg_lspid and msg_lrpid give you? msg_lspid is the PID of the process that called msgsnd() most recently. msg_lrpid is the PID of the process that called msgrcv() most recently. Useful for debugging.
Q6. How do you delete a message queue from the shell? Using ipcrm -q msqid (by ID) or ipcrm -Q key (by key). To list all queues first: ipcs -q.
Q7. Can msg_cbytes ever exceed msg_qbytes? No. The kernel enforces msg_qbytes as the hard limit. msgsnd() blocks (or fails with EAGAIN) when msg_cbytes would exceed msg_qbytes.

Leave a Reply

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