mq_open · mq_close · mq_unlink Creating, Opening, Closing, and Removing POSIX Message Queues

 

mq_open · mq_close · mq_unlink
Chapter 52 Part 2 — Creating, Opening, Closing, and Removing POSIX Message Queues
Part 2
Queue Lifecycle
3
Core Functions
6
Open Flags

The Queue Lifecycle

Before you can send or receive any message, you must open a queue using mq_open(). This gives you a descriptor — a handle — to work with. When done, you close that handle with mq_close(), and when you want to permanently remove the queue, you unlink it with mq_unlink(). These three functions mirror the familiar open(), close(), and unlink() calls for regular files.

Key Terms

mq_open() mq_close() mq_unlink() mqd_t O_CREAT O_EXCL O_RDONLY O_WRONLY O_RDWR O_NONBLOCK mq_attr EAGAIN

mq_open() — The Full Story

The function signature looks like this:

#include <fcntl.h>     /* O_* flags */
#include <sys/stat.h>  /* mode constants */
#include <mqueue.h>

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 */

mq_open() is a variadic function. The first two arguments are always required. The third (mode) and fourth (attr) arguments are only needed when you pass O_CREAT in oflag.

mq_open() Arguments Explained
name
Queue name — must start with /, e.g. "/my_queue". Identifies the queue system-wide.
oflag
Bitmask of flags — controls creation behaviour and access mode (see table below).
mode
(Only with O_CREAT) Permission bits like 0644. Masked by process umask. Controls who can read/write the queue.
attr
(Only with O_CREAT) Pointer to mq_attr struct for queue size settings. Pass NULL for system defaults.

The oflag Bit Values

Flag Meaning Notes
O_CREAT Create queue if it doesn’t exist Requires mode and attr arguments
O_EXCL With O_CREAT: fail if queue already exists Returns EEXIST if queue exists
O_RDONLY Open for receiving only Can only call mq_receive()
O_WRONLY Open for sending only Can only call mq_send()
O_RDWR Open for both send and receive Most flexible option
O_NONBLOCK Non-blocking mode mq_send/receive fails with EAGAIN instead of blocking

How to Choose Your oflag
Just opening an existing queue?
Use: O_RDONLY, O_WRONLY, or O_RDWR alone
Creating a new queue (or open if exists)?
Use: O_CREAT | O_RDWR (plus mode and attr args)
Creating exclusively (fail if exists)?
Use: O_CREAT | O_EXCL | O_RDWR
Don’t want to block on send/receive?
Add: | O_NONBLOCK to any of the above

mq_open() — Code Examples

Example 1: Create a new queue with custom attributes

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

int main(void)
{
    mqd_t mqd;
    struct mq_attr attr;

    /* Configure queue: hold up to 8 messages, each max 512 bytes */
    attr.mq_flags   = 0;        /* not used by mq_open */
    attr.mq_maxmsg  = 8;        /* max number of messages in queue */
    attr.mq_msgsize = 512;      /* max size of a single message */
    attr.mq_curmsgs = 0;        /* not used by mq_open */

    /*
     * O_CREAT | O_RDWR  : create if not exists, open for read+write
     * 0644              : owner rw, group r, others r
     * &attr             : use our custom attributes
     */
    mqd = mq_open("/custom_queue", O_CREAT | O_RDWR, 0644, &attr);
    if (mqd == (mqd_t)-1) {
        perror("mq_open");
        return 1;
    }

    printf("Queue created/opened. Descriptor = %d\n", (int)mqd);

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

Example 2: Open with default attributes (pass NULL for attr)

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

int main(void)
{
    mqd_t mqd;

    /*
     * Pass NULL for attr — kernel uses default values.
     * On Linux defaults: mq_maxmsg=10, mq_msgsize=8192
     */
    mqd = mq_open("/default_queue", O_CREAT | O_RDWR, 0600, NULL);
    if (mqd == (mqd_t)-1) {
        perror("mq_open");
        return 1;
    }

    printf("Queue with default attributes created.\n");

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

Example 3: Open existing queue for read-only (no O_CREAT)

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

int main(void)
{
    mqd_t mqd;

    /*
     * No O_CREAT — queue must already exist.
     * Only two arguments needed when not creating.
     */
    mqd = mq_open("/existing_queue", O_RDONLY);
    if (mqd == (mqd_t)-1) {
        perror("mq_open: queue may not exist");
        return 1;
    }

    printf("Opened existing queue for reading.\n");
    mq_close(mqd);
    return 0;
}

Example 4: Exclusive creation — fail if queue already exists

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

int main(void)
{
    mqd_t mqd;
    struct mq_attr attr = { .mq_maxmsg = 5, .mq_msgsize = 256 };

    /*
     * O_CREAT | O_EXCL: atomically create-or-fail.
     * Useful when you need to guarantee you are the creator.
     */
    mqd = mq_open("/unique_queue", O_CREAT | O_EXCL | O_RDWR, 0600, &attr);
    if (mqd == (mqd_t)-1) {
        if (errno == EEXIST) {
            printf("Queue already exists — another process created it.\n");
        } else {
            perror("mq_open");
        }
        return 1;
    }

    printf("We are the creator of /unique_queue\n");
    mq_close(mqd);
    mq_unlink("/unique_queue");
    return 0;
}

Example 5: Non-blocking open

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

int main(void)
{
    mqd_t mqd;
    char buf[256];
    struct mq_attr attr = { .mq_maxmsg = 5, .mq_msgsize = 256 };

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

    /* Immediate return with EAGAIN if queue is empty — no blocking */
    if (mq_receive(mqd, buf, sizeof(buf), NULL) == -1) {
        if (errno == EAGAIN) {
            printf("Queue is empty — no message available right now.\n");
        } else {
            perror("mq_receive");
        }
    }

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

mq_close() — Closing a Queue Descriptor
#include <mqueue.h>

int mq_close(mqd_t mqdes);
/* Returns: 0 on success, -1 on error */

mq_close() closes the message queue descriptor mqdes. It is analogous to close() for file descriptors. Important points:

Does NOT delete the queue. The queue and its messages remain in the kernel. Other processes that have it open continue normally.
Removes notification registration. If this process had called mq_notify() on this descriptor, that registration is automatically cancelled on close.
Automatic on process exit or exec(). The OS closes all open message queue descriptors when a process terminates or calls exec(). But you should close them explicitly to avoid running out of descriptors.

mq_close() vs mq_unlink() — The Critical Distinction
mq_close()
  • Closes your descriptor
  • Queue still exists in kernel
  • Messages are preserved
  • Other processes unaffected
  • Like closing a file handle
mq_unlink()
  • Removes queue name
  • No new opens allowed
  • Messages survive until last close
  • Queue destroyed at last close
  • Like deleting a file

Code Example: Proper open, use, close pattern

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

int main(void)
{
    mqd_t mqd;
    struct mq_attr attr = { .mq_maxmsg = 5, .mq_msgsize = 128 };

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

    mq_send(mqd, "test message", 13, 1);
    printf("Message sent. Now closing descriptor.\n");

    /* Close descriptor — queue still alive in kernel */
    if (mq_close(mqd) == -1) {
        perror("mq_close");
        return 1;
    }
    printf("Descriptor closed. Queue still exists.\n");

    /* Re-open the same queue — still accessible */
    mqd = mq_open("/close_demo", O_RDONLY);
    if (mqd == (mqd_t)-1) {
        perror("re-open failed");
        return 1;
    }

    char buf[128];
    unsigned int prio;
    ssize_t n = mq_receive(mqd, buf, sizeof(buf), &prio);
    if (n > 0) {
        buf[n] = '\0';
        printf("Re-opened and received: %s (priority %u)\n", buf, prio);
    }

    mq_close(mqd);
    mq_unlink("/close_demo");  /* NOW remove the queue */
    return 0;
}

mq_unlink() — Removing a Message Queue
#include <mqueue.h>

int mq_unlink(const char *name);
/* Returns: 0 on success, -1 on error */

mq_unlink() removes the queue’s name from the system and schedules the queue for destruction. The actual destruction is deferred until the last open descriptor is closed — this is the reference counting mechanism at work.

After mq_unlink(), new calls to mq_open() with the same name will not find the old queue. If a process tries to open it, a fresh queue will be created (or the open will fail if O_CREAT is not specified).

What Happens After mq_unlink()
Before unlink
Name visible, queue alive, A and B have it open
mq_unlink() called
Name removed from /dev/mqueue. A and B still can use queue.
A closes
B still has it open. Queue data survives.
B closes (last)
refcount = 0. Queue and all its messages are freed.

Code Example: mq_unlink basic usage

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

int main(void)
{
    struct mq_attr attr = { .mq_maxmsg = 5, .mq_msgsize = 64 };

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

    mq_send(mqd, "data", 5, 1);

    /*
     * Unlink BEFORE close — safe pattern.
     * The name disappears immediately but the queue lives until close.
     */
    if (mq_unlink("/temp_queue") == -1) {
        perror("mq_unlink");
        mq_close(mqd);
        return 1;
    }
    printf("Queue name removed. Data still readable until close.\n");

    /* We can still receive because mqd is still open */
    char buf[64];
    ssize_t n = mq_receive(mqd, buf, sizeof(buf), NULL);
    if (n > 0) {
        buf[n] = '\0';
        printf("Received after unlink: %s\n", buf);
    }

    /* Last close — queue is now fully destroyed */
    mq_close(mqd);
    printf("Queue destroyed (last close after unlink).\n");
    return 0;
}

Code Example: mq_unlink cleanup in a server process

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

#define QUEUE_NAME "/server_queue"

static mqd_t g_mqd = (mqd_t)-1;

/* Signal handler: clean up queue on Ctrl+C */
static void cleanup_handler(int sig)
{
    if (g_mqd != (mqd_t)-1)
        mq_close(g_mqd);
    mq_unlink(QUEUE_NAME);
    printf("\nQueue cleaned up. Exiting.\n");
    exit(0);
}

int main(void)
{
    struct mq_attr attr = { .mq_maxmsg = 10, .mq_msgsize = 256 };

    signal(SIGINT, cleanup_handler);

    /* Remove any stale queue from a previous run */
    mq_unlink(QUEUE_NAME);  /* Ignore error if it doesn't exist */

    g_mqd = mq_open(QUEUE_NAME, O_CREAT | O_RDONLY, 0666, &attr);
    if (g_mqd == (mqd_t)-1) { perror("mq_open"); return 1; }

    printf("Server listening on %s (press Ctrl+C to quit)...\n", QUEUE_NAME);

    char buf[256];
    unsigned int prio;
    while (1) {
        ssize_t n = mq_receive(g_mqd, buf, sizeof(buf), &prio);
        if (n > 0) {
            buf[n] = '\0';
            printf("Received [prio=%u]: %s\n", prio, buf);
        }
    }

    return 0;  /* never reached */
}

Behaviour After fork(), exec(), and Process Exit

Understanding what happens to message queue descriptors across fork() and exec() is important for multi-process programs.

After fork()

  • Child gets copies of all parent’s message queue descriptors
  • Both parent and child can send/receive on the queue
  • Child does NOT inherit the parent’s mq_notify() registrations
  • Closing in child does not close parent’s descriptor (they are independent copies)
After exec()

  • All message queue descriptors are closed
  • All notification registrations are deregistered
  • The queue itself still exists — only descriptors in this process are gone
  • New program can re-open the queue by name
After process termination

  • All descriptors closed automatically (same effect as exec)
  • All notification registrations removed
  • Queue lives on in kernel until unlinked and last descriptor closed

Code Example: fork() with message queues

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

#define QUEUE_NAME "/fork_demo"
#define MSG_SIZE   128

int main(void)
{
    struct mq_attr attr = { .mq_maxmsg = 5, .mq_msgsize = MSG_SIZE };
    mqd_t mqd;
    pid_t pid;

    /* Parent creates queue */
    mqd = mq_open(QUEUE_NAME, O_CREAT | O_RDWR, 0666, &attr);
    if (mqd == (mqd_t)-1) { perror("mq_open"); return 1; }

    pid = fork();
    if (pid == -1) { perror("fork"); return 1; }

    if (pid == 0) {
        /* ---- CHILD: has a copy of mqd ---- */
        const char *msg = "Hello from child!";
        mq_send(mqd, msg, strlen(msg) + 1, 5);
        printf("[Child %d] Sent message\n", getpid());
        mq_close(mqd);   /* close child's copy */
        return 0;

    } else {
        /* ---- PARENT: wait then read ---- */
        wait(NULL);  /* wait for child to finish */

        char buf[MSG_SIZE];
        unsigned int prio;
        ssize_t n = mq_receive(mqd, buf, sizeof(buf), &prio);
        if (n > 0) {
            buf[n] = '\0';
            printf("[Parent] Received from child: %s (prio=%u)\n", buf, prio);
        }

        mq_close(mqd);
        mq_unlink(QUEUE_NAME);
    }

    return 0;
}

Descriptor vs Description — Important Distinction

Just like file descriptors and file descriptions (open file table entries), POSIX message queues have two levels:

Term What It Is Per-process or Shared?
Message queue descriptor (mqd_t) The handle returned by mq_open(). Like a file descriptor number. Per-process. Duplicated by fork().
Message queue description Kernel’s internal open queue entry — holds the open flags (O_NONBLOCK etc.). Shared between parent and child after fork().

Practical implication: After fork(), parent and child share the same underlying queue description. If the parent changes the O_NONBLOCK flag via mq_setattr(), the child sees the change too — because they share the same queue description object.

Interview Questions — mq_open, mq_close, mq_unlink
Q1: What is the difference between mq_close() and mq_unlink()?
mq_close() closes the calling process’s descriptor for the queue — like closing a file handle. The queue and its messages remain. mq_unlink() removes the queue’s name from the filesystem and marks it for deletion. The actual deletion happens when the last process that has it open calls mq_close() (or terminates). This is the reference counting mechanism.
Q2: What flags combination do you use to create a new queue and fail if it already exists?
O_CREAT | O_EXCL | O_RDWR. The O_EXCL flag causes mq_open() to fail with EEXIST if a queue with that name already exists. This is an atomic check-and-create operation.
Q3: What happens to message queue descriptors after a fork()?
The child process gets copies of all the parent’s message queue descriptors. Both parent and child can use them independently. However, the child does NOT inherit the parent’s mq_notify() registrations. The descriptors point to the same underlying queue description, so changes to open flags (e.g., setting O_NONBLOCK) affect both.
Q4: What error does mq_send() or mq_receive() return when opened with O_NONBLOCK and the queue is full/empty?
EAGAIN. In non-blocking mode, if mq_send() would need to block (queue full) or mq_receive() would need to block (queue empty), they return -1 immediately with errno set to EAGAIN.
Q5: Can you still receive messages from a queue after calling mq_unlink() on it?
Yes, as long as you still have an open descriptor (mqd_t) for it. mq_unlink() only removes the name and marks the queue for deletion — it does not immediately destroy the queue data. Processes holding open descriptors can continue to send and receive until they close their descriptors.
Q6: What is mqd_t and how does it compare to a file descriptor?
mqd_t is the type returned by mq_open() — it is a message queue descriptor. On Linux it is an int, similar to a file descriptor. It identifies an open message queue within the process. SUSv3 only guarantees it is not an array type (can be assigned and passed by value). On Solaris it is a void *.
Q7: If mq_open() is called with O_CREAT but the queue already exists, what happens?
The existing queue is simply opened — the mode and attr arguments are ignored. No error is returned. This is different from O_CREAT | O_EXCL which would fail in this case. If you want to guarantee creation fails when queue exists, always add O_EXCL.
Q8: What happens to mq_notify() registrations when a process terminates?
All of the process’s message queue descriptors are closed (as if mq_close() were called on each). As a side effect of closing each descriptor, any mq_notify() registrations made through those descriptors are automatically deregistered. Other processes can then register for notification on those queues.

Leave a Reply

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