Message Queue Attributes mq_getattr() and mq_setattr() in depth

 

Message Queue Attributes
Chapter 52 — mq_getattr() and mq_setattr() in depth

The struct mq_attr Structure

Every POSIX message queue has attributes stored in a struct mq_attr. You use this structure both to configure a queue at creation time and to query its current state later.

#include <mqueue.h>

struct mq_attr {
    long mq_flags;    /* 0 (blocking) or O_NONBLOCK */
    long mq_maxmsg;   /* Maximum number of messages in the queue */
    long mq_msgsize;  /* Maximum size in bytes of each message */
    long mq_curmsgs;  /* Number of messages currently in the queue */
};

Field Set at open? Changeable later? Notes
mq_flags Ignored on open Yes Only O_NONBLOCK can be set via mq_setattr()
mq_maxmsg Yes (O_CREAT) No Fixed at creation. Limited by /proc/sys/fs/mqueue/msg_max
mq_msgsize Yes (O_CREAT) No Fixed at creation. Limited by /proc/sys/fs/mqueue/msgsize_max
mq_curmsgs Ignored No Read-only: set by kernel. Shows current message count.

Key Rule: mq_maxmsg and mq_msgsize are fixed at creation and cannot be changed afterwards. mq_flags (specifically O_NONBLOCK) is the only thing you can change after opening via mq_setattr().

mq_getattr() — Reading Queue Attributes
#include <mqueue.h>

int mq_getattr(mqd_t mqd, struct mq_attr *attr);
/* Returns: 0 on success, -1 on error */

mq_getattr() fills in the struct mq_attr pointed to by attr with the current attributes of the queue associated with descriptor mqd. The most useful field is mq_curmsgs — it tells you how many messages are waiting right now.

#include <stdio.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>

void print_queue_attrs(mqd_t mqd)
{
    struct mq_attr attr;

    if (mq_getattr(mqd, &attr) == -1) {
        perror("mq_getattr");
        return;
    }

    printf("Queue Attributes:\n");
    printf("  mq_flags   = %ld  (%s)\n",
           attr.mq_flags,
           (attr.mq_flags & O_NONBLOCK) ? "O_NONBLOCK" : "blocking");
    printf("  mq_maxmsg  = %ld  (max messages allowed)\n", attr.mq_maxmsg);
    printf("  mq_msgsize = %ld  (max bytes per message)\n", attr.mq_msgsize);
    printf("  mq_curmsgs = %ld  (messages currently in queue)\n", attr.mq_curmsgs);
}

int main(void)
{
    struct mq_attr attr;
    attr.mq_flags   = 0;
    attr.mq_maxmsg  = 5;
    attr.mq_msgsize = 64;
    attr.mq_curmsgs = 0;

    mqd_t mqd = mq_open("/attr_demo", O_CREAT | O_RDWR, 0600, &attr);
    if (mqd == (mqd_t)-1) { perror("mq_open"); return 1; }

    printf("--- Just after open (empty queue) ---\n");
    print_queue_attrs(mqd);

    /* Send two messages */
    mq_send(mqd, "first",  6, 1);
    mq_send(mqd, "second", 7, 2);

    printf("\n--- After sending 2 messages ---\n");
    print_queue_attrs(mqd);

    mq_close(mqd);
    mq_unlink("/attr_demo");
    return 0;
}

Sample Output:

--- Just after open (empty queue) ---
Queue Attributes:
  mq_flags   = 0  (blocking)
  mq_maxmsg  = 5  (max messages allowed)
  mq_msgsize = 64  (max bytes per message)
  mq_curmsgs = 0  (messages currently in queue)

--- After sending 2 messages ---
Queue Attributes:
  mq_flags   = 0  (blocking)
  mq_maxmsg  = 5  (max messages allowed)
  mq_msgsize = 64  (max bytes per message)
  mq_curmsgs = 2  (messages currently in queue)

mq_setattr() — Modifying Queue Attributes
#include <mqueue.h>

int mq_setattr(mqd_t mqd, const struct mq_attr *newattr,
               struct mq_attr *oldattr);
/* Returns: 0 on success, -1 on error */

mq_setattr() changes the attributes of the queue descriptor. Only mq_flags (the O_NONBLOCK flag) is actually honoured — the kernel ignores any changes to mq_maxmsg and mq_msgsize.

The oldattr argument, if not NULL, is filled with the previous attributes (same as calling mq_getattr() first). Pass NULL if you don’t need the old values.

#include <stdio.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>

int main(void)
{
    struct mq_attr create_attr = {
        .mq_flags   = 0,
        .mq_maxmsg  = 4,
        .mq_msgsize = 32,
    };

    mqd_t mqd = mq_open("/setattr_demo", O_CREAT | O_RDWR, 0600, &create_attr);
    if (mqd == (mqd_t)-1) { perror("mq_open"); return 1; }

    /* --- Switch to O_NONBLOCK mode --- */
    struct mq_attr new_attr = {0};
    new_attr.mq_flags = O_NONBLOCK;

    struct mq_attr old_attr;

    if (mq_setattr(mqd, &new_attr, &old_attr) == -1) {
        perror("mq_setattr");
        mq_close(mqd); mq_unlink("/setattr_demo"); return 1;
    }
    printf("Old mq_flags: %ld\n", old_attr.mq_flags);
    printf("Queue is now in O_NONBLOCK mode.\n");

    /* Try to receive from empty queue — should return immediately */
    char buf[32];
    unsigned int prio;
    ssize_t n = mq_receive(mqd, buf, sizeof(buf), &prio);
    if (n == -1) {
        if (errno == EAGAIN)
            printf("mq_receive: EAGAIN (queue empty, non-blocking works!)\n");
        else
            perror("mq_receive");
    }

    /* --- Switch back to blocking mode --- */
    new_attr.mq_flags = 0;  /* clear O_NONBLOCK */
    if (mq_setattr(mqd, &new_attr, NULL) == -1) {
        perror("mq_setattr restore");
    } else {
        printf("Queue switched back to blocking mode.\n");
    }

    mq_close(mqd);
    mq_unlink("/setattr_demo");
    return 0;
}

Output:

Old mq_flags: 0
Queue is now in O_NONBLOCK mode.
mq_receive: EAGAIN (queue empty, non-blocking works!)
Queue switched back to blocking mode.

Critical Rule: Receive Buffer Must Be ≥ mq_msgsize

When calling mq_receive(), the buffer you provide must be at least mq_msgsize bytes long. If it is smaller, mq_receive() fails with EMSGSIZE. This is a very common beginner mistake.

The correct pattern is to always query mq_msgsize first and allocate accordingly:

#include <stdio.h>
#include <stdlib.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>

/* Safe receive: allocates correct buffer size from queue attributes */
char *alloc_receive_buffer(mqd_t mqd, size_t *out_size)
{
    struct mq_attr attr;

    if (mq_getattr(mqd, &attr) == -1) {
        perror("mq_getattr");
        return NULL;
    }

    char *buf = malloc(attr.mq_msgsize);
    if (buf == NULL) {
        perror("malloc");
        return NULL;
    }

    if (out_size != NULL)
        *out_size = (size_t)attr.mq_msgsize;

    return buf;
}

int main(void)
{
    struct mq_attr attr = { .mq_maxmsg = 4, .mq_msgsize = 128 };
    mqd_t mqd = mq_open("/bufsize_demo", O_CREAT | O_RDWR, 0600, &attr);
    if (mqd == (mqd_t)-1) { perror("mq_open"); return 1; }

    mq_send(mqd, "hello world", 12, 3);

    size_t buf_size;
    char *buf = alloc_receive_buffer(mqd, &buf_size);
    if (buf == NULL) {
        mq_close(mqd); mq_unlink("/bufsize_demo"); return 1;
    }

    unsigned int prio;
    ssize_t n = mq_receive(mqd, buf, buf_size, &prio);
    if (n == -1) {
        perror("mq_receive");
    } else {
        printf("Received %zd bytes: \"%s\" (prio=%u)\n", n, buf, prio);
    }

    free(buf);
    mq_close(mqd);
    mq_unlink("/bufsize_demo");
    return 0;
}

Default Attributes When NULL Is Passed to mq_open()

If you pass NULL as the attr argument to mq_open(), the kernel uses system defaults. On Linux, these defaults are controlled by files in /proc/sys/fs/mqueue/:

# View default limits
cat /proc/sys/fs/mqueue/msg_default     # default mq_maxmsg  (usually 10)
cat /proc/sys/fs/mqueue/msgsize_default # default mq_msgsize (usually 8192)
cat /proc/sys/fs/mqueue/msg_max         # hard ceiling for mq_maxmsg
cat /proc/sys/fs/mqueue/msgsize_max     # hard ceiling for mq_msgsize
cat /proc/sys/fs/mqueue/queues_max      # max number of queues system-wide
/* Demonstrating NULL attr — uses system defaults */
#include <stdio.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>

int main(void)
{
    /* Pass NULL for attr — kernel picks defaults */
    mqd_t mqd = mq_open("/default_attr_q",
                         O_CREAT | O_RDWR,
                         0600,
                         NULL);   /* <-- NULL here */
    if (mqd == (mqd_t)-1) { perror("mq_open"); return 1; }

    struct mq_attr attr;
    mq_getattr(mqd, &attr);

    printf("System default mq_maxmsg  = %ld\n", attr.mq_maxmsg);
    printf("System default mq_msgsize = %ld\n", attr.mq_msgsize);

    mq_close(mqd);
    mq_unlink("/default_attr_q");
    return 0;
}

/* Typical output on Linux:
   System default mq_maxmsg  = 10
   System default mq_msgsize = 8192
*/

Practical: Checking If Queue Is Full or Empty

Use mq_getattr() to poll the queue state. This is useful in producer logic to avoid blocking unexpectedly.

#include <stdio.h>
#include <mqueue.h>

/* Returns 1 if queue is full, 0 if not, -1 on error */
int mq_is_full(mqd_t mqd)
{
    struct mq_attr attr;
    if (mq_getattr(mqd, &attr) == -1)
        return -1;
    return (attr.mq_curmsgs == attr.mq_maxmsg) ? 1 : 0;
}

/* Returns 1 if queue is empty, 0 if not, -1 on error */
int mq_is_empty(mqd_t mqd)
{
    struct mq_attr attr;
    if (mq_getattr(mqd, &attr) == -1)
        return -1;
    return (attr.mq_curmsgs == 0) ? 1 : 0;
}

/* Returns number of messages currently in the queue, -1 on error */
long mq_pending_count(mqd_t mqd)
{
    struct mq_attr attr;
    if (mq_getattr(mqd, &attr) == -1)
        return -1;
    return attr.mq_curmsgs;
}

/* Example usage */
int main(void)
{
    struct mq_attr attr = { .mq_maxmsg = 3, .mq_msgsize = 16 };
    mqd_t mqd = mq_open("/fullcheck", O_CREAT | O_RDWR, 0600, &attr);
    if (mqd == (mqd_t)-1) { perror("mq_open"); return 1; }

    printf("Empty? %s\n", mq_is_empty(mqd) ? "YES" : "NO");

    mq_send(mqd, "msg1", 5, 1);
    mq_send(mqd, "msg2", 5, 1);
    mq_send(mqd, "msg3", 5, 1);

    printf("Full?  %s\n", mq_is_full(mqd) ? "YES" : "NO");
    printf("Pending: %ld\n", mq_pending_count(mqd));

    mq_close(mqd);
    mq_unlink("/fullcheck");
    return 0;
}
/* Output:
   Empty? YES
   Full?  YES
   Pending: 3
*/

Interview Questions — Queue Attributes

Q1: What are the four fields of struct mq_attr and what does each do?
mq_flags: controls blocking/non-blocking mode (only O_NONBLOCK is valid).
mq_maxmsg: maximum number of messages the queue can hold at one time (set at creation).
mq_msgsize: maximum number of bytes per message (set at creation).
mq_curmsgs: read-only count of messages currently waiting in the queue (set by kernel).

Q2: Which fields of mq_attr can be changed after the queue is created?
Only mq_flags can be changed via mq_setattr(). Specifically, you can set or clear O_NONBLOCK. The fields mq_maxmsg and mq_msgsize are fixed at creation and cannot be changed.

Q3: What error does mq_receive() return if the buffer is smaller than mq_msgsize?
It fails with errno == EMSGSIZE. The correct approach is to call mq_getattr() first and allocate at least attr.mq_msgsize bytes for the receive buffer.

Q4: What is mq_curmsgs used for?
It tells you how many messages are currently queued. It is useful for checking whether the queue is empty or full before a send/receive, or for monitoring purposes. It is always set by the kernel and you cannot write to it.

Q5: Where are the system default and maximum limits for POSIX MQ attributes stored on Linux?
Under /proc/sys/fs/mqueue/: specifically msg_default, msgsize_default, msg_max, msgsize_max, and queues_max.

Q6: What does the oldattr parameter of mq_setattr() return?
It is filled with the queue’s attributes as they were before the change was applied — equivalent to calling mq_getattr() just before mq_setattr(). You can pass NULL if you don’t need the previous values.

Q7: Can two processes have different O_NONBLOCK settings for the same queue?
Yes. The O_NONBLOCK flag is per-descriptor, not per-queue. Process A can have the queue open in blocking mode while Process B has it open in non-blocking mode simultaneously, because each mqd_t is independent.

Next: Sending and Receiving Messages

Deep dive into mq_send(), mq_receive(), mq_timedsend(), mq_timedreceive() with priority ordering examples.

← Back to File 2 Go to File 4 →

Leave a Reply

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