← 1. Intro ← 2. Open/Close 3. Attributes 4. Send/Receive → 5. Notify → 6. Linux Specifics → 7. Interview Q&A →
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().
#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)
#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.
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;
}
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
*/
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
*/
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.
Deep dive into mq_send(), mq_receive(), mq_timedsend(), mq_timedreceive() with priority ordering examples.
