← 1. Introduction 2. Open/Close/Unlink 3. Attributes → 4. Send/Receive → 5. Notify → 6. Linux Specifics → 7. Interview Q&A →
mq_open() is the starting point. It either creates a new queue or opens an existing one. The return value is an mqd_t descriptor.
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
mqd_t mq_open(const char *name, int oflag);
mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr);
/* Returns: mqd_t descriptor on success, (mqd_t)-1 on error */
name: The queue name. Must start with /, no other slashes. Example: "/sensor_queue".
oflag: Access mode flags combined with bitwise OR.
mode: Permission bits (like chmod). Only needed when creating.
attr: Pointer to struct mq_attr to set limits. Pass NULL for system defaults.
| Flag | Meaning |
|---|---|
O_RDONLY |
Open for receiving (reading) only |
O_WRONLY |
Open for sending (writing) only |
O_RDWR |
Open for both sending and receiving |
O_CREAT |
Create the queue if it doesn’t exist. Needs mode and attr. |
O_EXCL |
With O_CREAT: fail with EEXIST if the queue already exists |
O_NONBLOCK |
Non-blocking mode. send/receive return EAGAIN instead of blocking |
| mq_open(name, oflag, mode, attr) | ||
| ↓ | ||
| O_CREAT not set Open existing queue ENOENT if not found |
↔ | O_CREAT | O_EXCL Create new only EEXIST if already exists |
| O_CREAT alone Create if not exists, open if already exists |
||
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
/* Example 1: Server creates a queue for receiving requests */
mqd_t create_server_queue(const char *name)
{
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = 10; /* max 10 messages in queue */
attr.mq_msgsize = 512; /* max 512 bytes per message */
attr.mq_curmsgs = 0;
/* O_EXCL ensures we don't accidentally open a stale queue */
mqd_t mqd = mq_open(name,
O_CREAT | O_EXCL | O_RDONLY,
S_IRUSR | S_IWUSR, /* rw for owner only */
&attr);
if (mqd == (mqd_t)-1) {
if (errno == EEXIST) {
fprintf(stderr, "Queue %s already exists! Remove it first.\n", name);
} else {
perror("mq_open server");
}
}
return mqd;
}
/* Example 2: Client opens existing queue for sending */
mqd_t open_client_queue(const char *name)
{
/* Client only needs write access, queue must already exist */
mqd_t mqd = mq_open(name, O_WRONLY);
if (mqd == (mqd_t)-1) {
perror("mq_open client");
}
return mqd;
}
/* Example 3: Open or create in non-blocking mode */
mqd_t open_nonblocking(const char *name)
{
struct mq_attr attr = {0};
attr.mq_maxmsg = 5;
attr.mq_msgsize = 256;
mqd_t mqd = mq_open(name,
O_CREAT | O_RDWR | O_NONBLOCK,
0660,
&attr);
return mqd;
}
int main(void)
{
const char *qname = "/demo_queue";
/* Clean up any leftover queue from previous run */
mq_unlink(qname); /* ignore error if it doesn't exist */
mqd_t server_mqd = create_server_queue(qname);
if (server_mqd == (mqd_t)-1)
return 1;
printf("Server queue created: fd=%d\n", (int)server_mqd);
/* Open same queue with write permission for "client" side */
mqd_t client_mqd = mq_open(qname, O_WRONLY);
if (client_mqd == (mqd_t)-1) {
perror("client mq_open");
mq_close(server_mqd);
mq_unlink(qname);
return 1;
}
printf("Client opened queue: fd=%d\n", (int)client_mqd);
mq_close(server_mqd);
mq_close(client_mqd);
mq_unlink(qname);
return 0;
}
#include <mqueue.h>
int mq_close(mqd_t mqd);
/* Returns: 0 on success, -1 on error */
mq_close() closes the message queue descriptor mqd. Think of it exactly like close() for file descriptors.
Important points:
• The queue itself is not removed from the kernel. Only the descriptor is closed.
• If the process registered for notification via mq_notify() on this descriptor, calling mq_close() deregisters that notification.
• All descriptors are automatically closed when the process exits, but the queue persists until mq_unlink() is called.
• Reference counting: the kernel keeps a count. Closing the last descriptor does not destroy the queue.
/* Always check return value */
if (mq_close(mqd) == -1)
perror("mq_close");
/* Typical cleanup pattern in production code */
void cleanup_queue(mqd_t mqd, const char *name, int do_unlink)
{
if (mqd != (mqd_t)-1) {
if (mq_close(mqd) == -1)
perror("mq_close");
}
if (do_unlink && name != NULL) {
if (mq_unlink(name) == -1 && errno != ENOENT)
perror("mq_unlink");
}
}
#include <mqueue.h>
int mq_unlink(const char *name);
/* Returns: 0 on success, -1 on error (ENOENT if name not found) */
mq_unlink() removes the queue name from the system. This is analogous to unlink() for files.
How deletion actually works (reference counting):
| Step 1 Process A: mq_open() ref_count = 1 Process B: mq_open() ref_count = 2 |
→ | Step 2 Process A: mq_unlink() Name removed from /dev/mqueue/ ref_count = 2 (still alive!) |
→ | Step 3 Process A: mq_close() → ref=1 Process B: mq_close() → ref=0 Queue memory freed! |
/* Example: Safe unlink with existence check */
#include <mqueue.h>
#include <errno.h>
#include <stdio.h>
void safe_unlink(const char *name)
{
if (mq_unlink(name) == -1) {
if (errno == ENOENT) {
printf("Queue %s does not exist, nothing to remove.\n", name);
} else {
perror("mq_unlink");
}
} else {
printf("Queue %s unlinked successfully.\n", name);
}
}
/* Example: Registering cleanup with atexit */
#include <stdlib.h>
static const char *g_qname = NULL;
void cleanup_on_exit(void)
{
if (g_qname != NULL)
mq_unlink(g_qname);
}
int main(void)
{
g_qname = "/atexit_queue";
atexit(cleanup_on_exit);
struct mq_attr attr = {.mq_maxmsg=5, .mq_msgsize=64};
mqd_t mqd = mq_open(g_qname, O_CREAT|O_RDWR, 0600, &attr);
if (mqd == (mqd_t)-1) { perror("mq_open"); return 1; }
printf("Queue created. Will be unlinked on exit.\n");
/* ... do work ... */
mq_close(mqd);
return 0; /* atexit will call cleanup_on_exit */
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#define QNAME "/ep_demo"
#define MAXMSG 8
#define MSGSIZE 128
static mqd_t s_mqd = (mqd_t)-1;
/* Called on abnormal exit */
static void cleanup(void)
{
if (s_mqd != (mqd_t)-1)
mq_close(s_mqd);
mq_unlink(QNAME);
}
int main(void)
{
struct mq_attr attr = {
.mq_flags = 0,
.mq_maxmsg = MAXMSG,
.mq_msgsize = MSGSIZE,
.mq_curmsgs = 0,
};
atexit(cleanup);
/* Remove any stale queue from a previous crash */
mq_unlink(QNAME);
/* Create new queue */
s_mqd = mq_open(QNAME,
O_CREAT | O_EXCL | O_RDWR,
S_IRUSR | S_IWUSR,
&attr);
if (s_mqd == (mqd_t)-1) {
perror("mq_open");
exit(EXIT_FAILURE);
}
printf("[+] Queue '%s' created (fd=%d)\n", QNAME, (int)s_mqd);
/* Send a test message */
const char *msg = "Test message";
if (mq_send(s_mqd, msg, strlen(msg)+1, 1) == -1) {
perror("mq_send");
exit(EXIT_FAILURE);
}
printf("[+] Sent: \"%s\"\n", msg);
/* Receive it back */
char buf[MSGSIZE];
unsigned int prio;
if (mq_receive(s_mqd, buf, MSGSIZE, &prio) == -1) {
perror("mq_receive");
exit(EXIT_FAILURE);
}
printf("[+] Received: \"%s\" (prio=%u)\n", buf, prio);
/* Explicit cleanup (atexit would also do this) */
mq_close(s_mqd);
s_mqd = (mqd_t)-1;
mq_unlink(QNAME);
printf("[+] Queue removed. Exiting.\n");
return 0;
}
| errno | Function | Cause |
|---|---|---|
ENOENT |
mq_open, mq_unlink | Queue doesn’t exist and O_CREAT not specified |
EEXIST |
mq_open | O_CREAT | O_EXCL used but queue already exists |
EACCES |
mq_open | Permission denied (check mode bits and uid) |
EINVAL |
mq_open | Name invalid (missing leading slash) or attr values out of range |
EMFILE |
mq_open | Process has too many open file descriptors |
ENFILE |
mq_open | System-wide file descriptor limit reached |
ENOSPC |
mq_open | Kernel limit on number of message queues exceeded |
Q1: What is the difference between mq_close() and mq_unlink()?
mq_close() closes the per-process descriptor (like closing a file) but the queue remains in the kernel. mq_unlink() removes the queue name and schedules the queue for destruction, but the queue memory is only freed when all open descriptors are also closed.
Q2: When would you use O_EXCL with mq_open()?
When the server process wants to guarantee it is creating a fresh queue and not accidentally opening a stale one from a previous crash. If the queue already exists, mq_open() fails with EEXIST, prompting cleanup before retry.
Q3: Can two processes open the same POSIX MQ with different access modes?
Yes. Process A can open it with O_RDONLY (receiver) and Process B with O_WRONLY (sender). The queue supports multiple simultaneous descriptors.
Q4: What happens to a POSIX MQ when the creating process exits without calling mq_unlink()?
All file descriptors are closed automatically on exit, but the queue persists in the kernel. Other processes can still open it. The queue stays until someone explicitly calls mq_unlink() or the system reboots.
Q5: What is the second parameter of mq_open() when NOT creating?
Only the 2-argument form is used: mq_open(name, oflag) — no mode and no attr. These are only needed when O_CREAT is specified.
Q6: What error is returned if you try to open a queue for writing but the queue was created with mode 0444?
EACCES (Permission denied). The mode bits are checked by the kernel just like file permissions.
Q7: After mq_unlink(), can other processes still use the queue?
Yes, if they already have it open. The name is removed (new opens will fail with ENOENT) but existing descriptors remain valid until they are closed. This is the same semantics as unlink() on files.
Learn how to configure and query queue properties with mq_getattr() and mq_setattr().
