System V Message Queues msgctl() — Control Operations

 

System V Message Queues
Chapter 46 · Part 2: msgctl() — Control Operations
46.3
Section
3
cmd Flags
2
Code Examples
12
Interview Q&A

Key Terms in This Section
msgctl() IPC_RMID IPC_STAT IPC_SET msqid_ds msqid EIDRM Control Operations Queue Deletion Permissions

What is msgctl()?

In System V IPC, once a message queue is created, you need a way to manage it — inspect its state, change its settings, or delete it when it is no longer needed. This is exactly what msgctl() (message queue control) does.

Think of msgctl() as the administrative interface to a message queue. Just like you use ioctl() to control device files, you use msgctl() to control message queue objects. It is a multipurpose system call — the cmd argument selects what operation to perform.

This is one of those system calls that every Linux systems programmer must know deeply, and it is a very common interview topic in embedded Linux and systems programming roles.

Function Signature
#include <sys/types.h>    /* For portability */
#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

/* Returns: 0 on success, -1 on error (errno set) */

Parameters explained:

Parameter Type Purpose
msqid int The message queue identifier returned by msgget(). This uniquely identifies which queue to operate on.
cmd int The control command — one of IPC_RMID, IPC_STAT, IPC_SET, or other values. This tells the kernel what action to perform.
buf struct msqid_ds * Pointer to a data structure buffer. Used to pass data in (for IPC_SET) or receive data out (for IPC_STAT). For IPC_RMID, this is ignored and can be NULL.
Key Point: The third argument buf is interpreted differently depending on cmd. For IPC_RMID it is completely ignored. For IPC_STAT the kernel writes into it. For IPC_SET the caller fills it and the kernel reads from it.

The Three Core cmd Values

The cmd argument controls what operation msgctl() performs. There are three fundamental values:

IPC_RMID — Remove / Delete the Queue

This command immediately and permanently deletes the message queue from the kernel. When IPC_RMID is used:

  • The queue object is destroyed along with its msqid_ds metadata structure.
  • All messages currently sitting in the queue are discarded and lost forever.
  • Any process currently blocked on msgsnd() (because the queue was full) or msgrcv() (waiting for a message) is immediately unblocked.
  • Those unblocked calls fail and return -1 with errno set to EIDRM (Identifier Removed).
  • The third argument buf is completely ignored for this operation.
Important: There is no “recycle bin” for message queues. Once you call msgctl(msqid, IPC_RMID, NULL), all unread messages in that queue are gone. Make sure the application logic handles this gracefully, especially when other processes may be using the queue.

IPC_STAT — Read Queue Metadata

This command reads the current state and metadata of the message queue into the msqid_ds structure pointed to by buf. After the call returns successfully, buf is filled with information about:

  • Ownership and permissions (msg_perm)
  • Time of last send, receive, and change
  • Number of messages currently in the queue
  • Maximum allowed bytes in the queue
  • PIDs of the last sender and receiver

This is used for monitoring and debugging — for example, to check if a queue is getting too full, or to find out who last interacted with it.

IPC_SET — Modify Queue Settings

This command updates selected fields in the queue’s msqid_ds structure. The caller fills in a msqid_ds structure and passes it via buf. The kernel reads from it and updates the queue’s metadata. Only these fields can be changed:

  • msg_perm.uid — owner user ID
  • msg_perm.gid — owner group ID
  • msg_perm.mode — permission bits (lower 9 bits)
  • msg_qbytes — maximum number of bytes in the queue (requires CAP_SYS_RESOURCE to raise above the system default)
Typical workflow for IPC_SET: First call IPC_STAT to read current values into a local msqid_ds, then modify only the fields you need, then call IPC_SET. This prevents accidentally overwriting fields you didn’t intend to change.

How msgctl() fits into the Message Queue Lifecycle
msgget()
Create Queue
msgsnd()
Send Messages
msgrcv()
Receive Messages
msgctl()
Control/Delete

msgctl() can be called at any stage — not just at the end. For example, you might call it after msgget() to adjust the queue’s byte limit before any messages are sent, or periodically call it with IPC_STAT to monitor queue depth.

Code Example 1 — Deleting a Message Queue with IPC_RMID

This is the most common use of msgctl(). Every queue you create should be cleaned up — otherwise it persists in kernel memory until the system reboots.

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

int main(void)
{
    key_t key;
    int   msqid;

    /* Step 1: Generate a unique key for this queue */
    key = ftok("/tmp", 'A');
    if (key == -1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }

    /* Step 2: Create (or open) the message queue */
    msqid = msgget(key, IPC_CREAT | 0660);
    if (msqid == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }
    printf("Queue created. msqid = %d\n", msqid);

    /* Step 3: ... (send/receive messages here) ... */

    /* Step 4: Delete the queue when done */
    /* IPC_RMID: third argument is ignored — pass NULL */
    if (msgctl(msqid, IPC_RMID, NULL) == -1) {
        perror("msgctl IPC_RMID");
        exit(EXIT_FAILURE);
    }
    printf("Queue msqid=%d deleted successfully.\n", msqid);

    return 0;
}
/*
 * Output:
 *   Queue created. msqid = 32769
 *   Queue msqid=32769 deleted successfully.
 *
 * Important: After IPC_RMID, any process blocked on msgsnd()
 * or msgrcv() on this queue will be woken up and their call
 * will return -1 with errno == EIDRM.
 */

Code Example 2 — Inspecting Queue State (IPC_STAT) and Modifying Permissions (IPC_SET)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

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

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

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

    printf("=== IPC_STAT Results ===\n");
    printf("Owner UID       : %d\n",  info.msg_perm.uid);
    printf("Owner GID       : %d\n",  info.msg_perm.gid);
    printf("Permissions     : %o\n",  info.msg_perm.mode & 0777);
    printf("Messages in queue: %lu\n", (unsigned long)info.msg_qnum);
    printf("Max queue bytes : %lu\n", (unsigned long)info.msg_qbytes);
    printf("Last msgsnd() time: %s",
           (info.msg_stime == 0) ? "Never\n" : ctime(&info.msg_stime));
    printf("Last msgrcv() time: %s",
           (info.msg_rtime == 0) ? "Never\n" : ctime(&info.msg_rtime));
    printf("PID of last sender  : %d\n", info.msg_lspid);
    printf("PID of last receiver: %d\n", info.msg_lrpid);

    /* ---- IPC_SET: Change permissions ----
     * Best practice: start from IPC_STAT result, then modify only
     * the fields you need. This avoids accidental overwrites.
     */
    info.msg_perm.mode = 0666;  /* Allow rw for everyone */

    if (msgctl(msqid, IPC_SET, &info) == -1) {
        perror("msgctl IPC_SET");
        exit(1);
    }
    printf("\nPermissions updated to 0666.\n");

    /* Confirm the change */
    if (msgctl(msqid, IPC_STAT, &info) == -1) {
        perror("msgctl IPC_STAT (verify)");
        exit(1);
    }
    printf("New permissions : %o\n", info.msg_perm.mode & 0777);

    /* Cleanup */
    msgctl(msqid, IPC_RMID, NULL);
    return 0;
}
/*
 * Sample Output:
 *   === IPC_STAT Results ===
 *   Owner UID       : 1000
 *   Owner GID       : 1000
 *   Permissions     : 600
 *   Messages in queue: 0
 *   Max queue bytes : 16384
 *   Last msgsnd() time: Never
 *   Last msgrcv() time: Never
 *   PID of last sender  : 0
 *   PID of last receiver: 0
 *
 *   Permissions updated to 0666.
 *   New permissions : 666
 */

What Happens to Blocked Processes on IPC_RMID?

This is a subtle but important behaviour that comes up in interviews:

When a process calls msgrcv() on an empty queue and no message arrives, it blocks (sleeps) inside the kernel. Similarly, a process calling msgsnd() when the queue is full also blocks.

Now, if another process deletes the queue with msgctl(msqid, IPC_RMID, NULL), the kernel immediately wakes up all blocked processes. Their msgsnd() or msgrcv() call returns -1 and errno is set to EIDRM (“Identifier removed”).

/* Robust msgrcv() that handles queue deletion gracefully */
ssize_t result = msgrcv(msqid, &msg, sizeof(msg.mtext), 0, 0);
if (result == -1) {
    if (errno == EIDRM) {
        /* The queue was deleted while we were waiting.
           This is a normal shutdown scenario — exit cleanly. */
        printf("Queue was removed. Exiting...\n");
        exit(EXIT_SUCCESS);
    } else if (errno == EINTR) {
        /* Interrupted by a signal — retry or exit depending on design */
        printf("Interrupted by signal. Retrying...\n");
        /* ... retry logic ... */
    } else {
        perror("msgrcv");
        exit(EXIT_FAILURE);
    }
}

Who Can Call msgctl() — Privilege Requirements
Operation Who is allowed?
IPC_RMID The process must have the effective UID matching the queue’s owner or creator UID, or must have the CAP_IPC_OWNER capability (root typically has this).
IPC_STAT The process needs read permission on the queue (as defined in the msg_perm.mode field). Processes without any permission get EACCES.
IPC_SET Same as IPC_RMID — must match owner/creator UID or have CAP_IPC_OWNER. To raise msg_qbytes above the system default, the process additionally needs CAP_SYS_RESOURCE.

Common Errors Returned by msgctl()
errno value When it occurs
EINVAL msqid is not a valid message queue identifier, or cmd is an unrecognised command.
EACCES The calling process does not have permission to perform the requested IPC_STAT operation on this queue.
EPERM Attempted IPC_SET or IPC_RMID but the caller is not the owner, creator, or does not have the required capability.
EPERM Attempted to raise msg_qbytes above the system limit without CAP_SYS_RESOURCE.
EFAULT The buf pointer is an invalid address (for IPC_STAT or IPC_SET).

Viewing and Deleting Queues from the Shell

You can also inspect and remove System V message queues from the command line using standard Linux tools. This is very useful for debugging.

# List all System V message queues ipcs -q # Detailed view including permissions and sizes ipcs -q -l # Delete a specific queue by its msqid ipcrm -q <msqid> # Delete all message queues owned by the current user ipcs -q | awk ‘NR>2 { print $2 }’ | xargs -I{} ipcrm -q {}
Good habit: After testing programs that use msgget(IPC_CREAT...), always run ipcs -q to check you haven’t left orphan queues in the kernel. Leaked queues consume kernel memory and count against the system’s IPC object limit.

Interview Questions — msgctl() and Control Operations
Q1. What is the purpose of msgctl() and what does it return?
msgctl() is the control interface for System V message queues. It allows a process to perform administrative operations on a queue identified by msqid. The cmd argument selects the specific operation. It returns 0 on success and -1 on failure, with errno set to indicate the error.
Q2. What are the three main cmd values for msgctl() and what does each do?
IPC_RMID — Immediately deletes the queue. All messages are lost. Blocked processes are woken up with errno = EIDRM. The buf argument is ignored.

IPC_STAT — Copies the queue’s msqid_ds metadata structure into the buffer pointed to by buf. Used to inspect the queue’s state, size, timestamps, and permissions.

IPC_SET — Updates selected writable fields of the queue’s msqid_ds structure (uid, gid, mode, and msg_qbytes) using values from the buffer pointed to by buf.

Q3. What happens to processes blocked on msgrcv() or msgsnd() when IPC_RMID is called?
They are immediately unblocked by the kernel. Their blocked system call returns -1 with errno set to EIDRM (Identifier Removed). A well-written application must check for this error and handle it gracefully — typically by cleaning up and exiting rather than retrying, since the queue no longer exists.
Q4. When performing IPC_SET, what is the recommended best practice?
Always perform an IPC_STAT first, copy all current values into the local msqid_ds, modify only the specific fields you want to change, and then call IPC_SET. This avoids accidentally overwriting fields you did not intend to change. For example, if you only want to change msg_qbytes, you must still provide correct values for uid, gid, and mode — so reading them first with IPC_STAT is essential.
Q5. Who has permission to delete a message queue using IPC_RMID?
A process can delete a message queue if: (a) its effective UID matches the queue’s owner UID (msg_perm.uid), (b) its effective UID matches the queue’s creator UID (msg_perm.cuid), or (c) it has the CAP_IPC_OWNER capability (which the root user possesses by default). Without one of these, msgctl() fails with errno = EPERM.
Q6. What special capability is needed to increase msg_qbytes beyond the system default?
The CAP_SYS_RESOURCE capability is required to raise msg_qbytes (the maximum byte capacity of the queue) above the system-wide default limit. An ordinary user can reduce msg_qbytes below the current value without any special capability, but raising it requires privilege. The system default is typically visible in /proc/sys/kernel/msgmnb.
Q7. What error is returned if you call msgctl() with an invalid msqid?
msgctl() returns -1 and sets errno to EINVAL if the msqid does not refer to a valid, existing message queue. This can happen if the queue was deleted by another process, or if the wrong ID was used. This is why robust programs always check the return value of every IPC system call.
Q8. How would you use ipcs and ipcrm from the shell to manage message queues?
ipcs -q lists all existing System V message queues visible to the current user, showing their key, ID, owner, permissions, number of messages, and byte size. ipcrm -q <msqid> deletes a specific queue by its numeric ID — equivalent to calling msgctl(msqid, IPC_RMID, NULL) in C code. These tools are essential for debugging and cleaning up leaked IPC objects during development.
Q9. Why must you always delete System V message queues when done with them?
Unlike POSIX named semaphores or pipes, System V message queues have kernel persistence — they survive even after all processes that created or used them have exited. They remain in kernel memory until explicitly deleted via IPC_RMID or a system reboot. Leaked queues waste kernel memory, count against system-wide IPC resource limits (/proc/sys/kernel/msgmni), and can cause msgget() to fail with ENOSPC in long-running systems.
Q10. What is the difference between IPC_STAT and IPC_SET in terms of the buf parameter direction?
For IPC_STAT, buf is an output parameter — the kernel writes the queue’s current msqid_ds data into the buffer you provide. Your buffer must be large enough to hold a struct msqid_ds.

For IPC_SET, buf is an input parameter — the kernel reads the values you have placed in the buffer and uses them to update the queue’s metadata. The direction is reversed: caller → kernel for IPC_SET, kernel → caller for IPC_STAT.

Q11. What fields of the msqid_ds structure can be updated via IPC_SET?
Only four fields can be updated via IPC_SET: msg_perm.uid (owner user ID), msg_perm.gid (owner group ID), msg_perm.mode (permission bits), and msg_qbytes (maximum queue byte capacity). Other fields like timestamps, message count, and PID fields are maintained automatically by the kernel and cannot be set directly by userspace.
Q12. Write the minimal code to delete a message queue safely, handling the case where it may have already been deleted.
if (msgctl(msqid, IPC_RMID, NULL) == -1) {
    if (errno == EINVAL) {
        /* Queue does not exist — already deleted, not an error */
        fprintf(stderr, "Queue %d already removed.\n", msqid);
    } else {
        perror("msgctl IPC_RMID");
        exit(EXIT_FAILURE);
    }
}

The key is checking for EINVAL separately — in a multi-process system, another process may have already called IPC_RMID before you did. Treating EINVAL as a non-fatal condition here is good defensive programming.

Continue Learning

Next: msqid_ds Data Structure — Understanding Every Field

Next: msqid_ds Structure → Back to Course Index

Leave a Reply

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