POSIX Message Queues mqueue Filesystem & I/O Multiplexing

 

POSIX Message Queues
Part 7 — Linux-Specific Features: mqueue Filesystem & I/O Multiplexing
🗂️ Topic
mqueue FS
🔍 Inspect
/proc/mounts
🔄 I/O Model
poll/select/epoll

Linux Goes Beyond the Standard

POSIX defines the core message queue API. Linux adds several non-standard but highly useful features on top:

  • Message queues are exposed as files in a virtual filesystem (mqueue), so you can inspect them with standard tools like ls, cat, and rm.
  • Message queue descriptors are implemented as real file descriptors, so you can monitor them with select(), poll(), and epoll().

These features are Linux-specific. They are not available on other UNIX implementations that follow only the POSIX standard.

Key Terms
mqueue filesystem mount -t mqueue /dev/mqueue sticky bit QSIZE / NOTIFY_PID poll() select() epoll file descriptor /proc/mounts

1. The mqueue Virtual Filesystem

On Linux, POSIX IPC objects (message queues, shared memory, semaphores) are implemented inside virtual filesystems. For message queues, this filesystem type is called mqueue. Once you mount it, every message queue appears as a regular file in the mount directory.

Mount it like this (requires root):

# Create a mount point
mkdir /dev/mqueue

# Mount the mqueue filesystem
mount -t mqueue none /dev/mqueue

The first argument to -t is the filesystem type: mqueue. The source (here none) can be any string — it only appears in /proc/mounts and the mount command output. It has no functional meaning.

mqueue Filesystem Layout
/dev/mqueue/
newq  ← queue created with mq_open()
sensorq  ← another queue
logq  ← yet another queue
Each queue file shows: QSIZE (bytes of data), NOTIFY_PID, NOTIFY type, SIGNO

Verify the mount:

cat /proc/mounts | grep mqueue
# Output:
# none /dev/mqueue mqueue rw 0 0

Check directory permissions:

ls -ld /dev/mqueue
# Output:
# drwxrwxrwt 2 root root 40 Jul 26 12:09 /dev/mqueue
#                                                ^
#                               The 't' means sticky bit is set

The sticky bit (t in permissions) means that even though the directory is world-writable, an unprivileged process can only delete (unlink) message queues it owns. This is the same behaviour as /tmp.

2. Creating, Listing, and Deleting Queues via the Filesystem

Once the filesystem is mounted, queue operations map directly to filesystem operations:

# Create a queue (using a helper program or from C code)
./pmsg_create -c /newq

# List all queues
ls /dev/mqueue
# Output: newq

# Delete a queue using rm (equivalent to mq_unlink)
rm /dev/mqueue/newq

Using rm to delete a queue is exactly equivalent to calling mq_unlink("/newq") in C. Both remove the queue name from the filesystem.

/* Equivalent in C */
#include <mqueue.h>

int main(void) {
    /* These two lines do the same thing as: rm /dev/mqueue/newq */
    if (mq_unlink("/newq") == -1)
        perror("mq_unlink");
    return 0;
}

3. Reading Queue Information from the Virtual File

Each queue’s virtual file contains a single line of text with status information. You can read it with cat:

# Create queue and write 7 bytes
./pmsg_create -c /mq
./pmsg_send /mq abcdefg

cat /dev/mqueue/mq
# Output:
# QSIZE:7 NOTIFY:0 SIGNO:0 NOTIFY_PID:0

Fields in the mqueue Virtual File
Field Meaning
QSIZE Total bytes of data currently in the queue (sum of all message bodies)
NOTIFY_PID PID of the process registered for notification (0 = none registered)
NOTIFY Notification method: 0=SIGEV_SIGNAL, 1=SIGEV_NONE, 2=SIGEV_THREAD
SIGNO Signal number used when NOTIFY=0 (SIGEV_SIGNAL); 0 otherwise

Example with signal notification registered (SIGUSR1 = signal 10 on x86):

# Start signal-based notify program in background
./mq_notify_sig /mq &
# PID 18158

cat /dev/mqueue/mq
# QSIZE:7 NOTIFY:0 SIGNO:10 NOTIFY_PID:18158
# NOTIFY=0 means SIGEV_SIGNAL; SIGNO=10 means SIGUSR1

Example with thread notification registered:

# Start thread-based notify program
./mq_notify_thread /mq &
# PID 18160

cat /dev/mqueue/mq
# QSIZE:7 NOTIFY:2 SIGNO:0 NOTIFY_PID:18160
# NOTIFY=2 means SIGEV_THREAD

Reading these fields in C (reading the file programmatically):

#include <stdio.h>
#include <stdlib.h>

void print_queue_info(const char *qname)
{
    char path[256];
    FILE *fp;
    char buf[256];

    /* Build path like /dev/mqueue/myqueue (strip leading slash from qname) */
    snprintf(path, sizeof(path), "/dev/mqueue/%s",
             qname[0] == '/' ? qname + 1 : qname);

    fp = fopen(path, "r");
    if (!fp) { perror("fopen"); return; }

    if (fgets(buf, sizeof(buf), fp))
        printf("Queue '%s': %s", qname, buf);

    fclose(fp);
}

int main(void) {
    print_queue_info("/myqueue");
    return 0;
}

4. I/O Multiplexing: poll(), select(), and epoll on Message Queues

On Linux, a message queue descriptor is a genuine file descriptor. This means you can pass it to poll(), select(), or epoll_ctl() just like a socket or pipe file descriptor.

This solves a classic problem with System V message queues: you cannot easily wait for input on both a System V message queue and a socket (or pipe) in the same select() call. With POSIX message queues on Linux, you can.

Monitoring Multiple Sources with poll()
POSIX MQ
descriptor (fd)
Socket fd
Pipe fd
poll() /
select() /
epoll
Event
detected

Example using poll() to monitor a message queue and a pipe simultaneously:

#include <mqueue.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MQ_NAME  "/poll_demo_mq"
#define MSG_SIZE 256

int main(void)
{
    mqd_t mqd;
    struct mq_attr attr = { 0, 10, MSG_SIZE, 0 };
    int pipefd[2];
    struct pollfd fds[2];
    char buf[MSG_SIZE];

    /* Create queue (non-blocking) */
    mq_unlink(MQ_NAME);
    mqd = mq_open(MQ_NAME, O_CREAT | O_RDONLY | O_NONBLOCK, 0600, &attr);
    if (mqd == (mqd_t)-1) { perror("mq_open"); exit(1); }

    /* Create a pipe to simulate another fd source */
    if (pipe(pipefd) == -1) { perror("pipe"); exit(1); }

    /* Set up poll: watch mqd (fd) and read-end of pipe */
    fds[0].fd     = (int)mqd;   /* On Linux, mqd_t is a real fd */
    fds[0].events = POLLIN;

    fds[1].fd     = pipefd[0];
    fds[1].events = POLLIN;

    printf("Waiting for data on MQ or pipe...\n");

    /* In a real program this would loop; here just one iteration */
    int ret = poll(fds, 2, 5000);  /* 5-second timeout */
    if (ret == -1) { perror("poll"); }
    else if (ret == 0) { printf("Timeout — no data arrived.\n"); }
    else {
        if (fds[0].revents & POLLIN) {
            ssize_t n = mq_receive(mqd, buf, MSG_SIZE, NULL);
            if (n > 0) printf("MQ message: %.*s\n", (int)n, buf);
        }
        if (fds[1].revents & POLLIN) {
            ssize_t n = read(pipefd[0], buf, MSG_SIZE);
            if (n > 0) printf("Pipe data: %.*s\n", (int)n, buf);
        }
    }

    mq_close(mqd);
    mq_unlink(MQ_NAME);
    close(pipefd[0]); close(pipefd[1]);
    return 0;
}
gcc -o mq_poll_demo mq_poll_demo.c -lmqueue
./mq_poll_demo

Example using epoll with a message queue:

#include <mqueue.h>
#include <sys/epoll.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define MQ_NAME "/epoll_mq"
#define MSG_SIZE 256

int main(void)
{
    mqd_t mqd;
    struct mq_attr attr = { 0, 10, MSG_SIZE, 0 };
    int epfd;
    struct epoll_event ev, events[4];
    char buf[MSG_SIZE];

    mq_unlink(MQ_NAME);
    mqd = mq_open(MQ_NAME, O_CREAT | O_RDONLY | O_NONBLOCK, 0600, &attr);
    if (mqd == (mqd_t)-1) { perror("mq_open"); exit(1); }

    epfd = epoll_create1(0);
    if (epfd == -1) { perror("epoll_create1"); exit(1); }

    ev.events  = EPOLLIN;
    ev.data.fd = (int)mqd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, (int)mqd, &ev) == -1) {
        perror("epoll_ctl"); exit(1);
    }

    printf("epoll waiting on message queue...\n");

    int nfds = epoll_wait(epfd, events, 4, 5000);  /* 5-second timeout */
    for (int i = 0; i < nfds; i++) {
        if (events[i].data.fd == (int)mqd) {
            ssize_t n = mq_receive(mqd, buf, MSG_SIZE, NULL);
            if (n > 0) printf("Got message: %.*s\n", (int)n, buf);
        }
    }

    close(epfd);
    mq_close(mqd);
    mq_unlink(MQ_NAME);
    return 0;
}
⚠️ Portability Note: Using message queue descriptors with poll(), select(), or epoll() is Linux-specific. POSIX does not require that mqd_t be implemented as a file descriptor. Code using this feature will not compile or run on AIX, Solaris, or macOS.

Interview Questions & Answers
Q1. What is the mqueue filesystem in Linux?
It is a virtual filesystem that Linux provides to expose POSIX message queues as files. When mounted (e.g., at /dev/mqueue), each message queue appears as a file in that directory. You can list queues with ls, inspect their status with cat, and delete them with rm.
Q2. What does the sticky bit on /dev/mqueue mean?
The sticky bit means that even though the directory is world-writable, an unprivileged process can only unlink (delete) message queues it owns. This is the same semantics as /tmp — you cannot delete someone else’s queue without privilege.
Q3. What information does reading a queue’s virtual file show?
It shows: QSIZE (total bytes in the queue), NOTIFY_PID (PID registered for notification, or 0), NOTIFY (notification method: 0=signal, 1=none, 2=thread), and SIGNO (signal number if notification is via signal).
Q4. Why is it an advantage that Linux implements mqd_t as a real file descriptor?
Because you can monitor it with select(), poll(), and epoll() alongside sockets, pipes, and other file descriptors. This lets you wait for messages on a queue and network I/O in the same event loop — something impossible with System V message queues, which are not file descriptors.
Q5. Is using poll() on a POSIX message queue descriptor portable?
No. POSIX/SUSv3 does not require mqd_t to be a file descriptor. Using poll() or epoll() on a message queue descriptor is a Linux-specific feature. Code relying on this will fail on other UNIX systems such as macOS or Solaris.
Q6. How do you make the mqueue filesystem persist across reboots?
Add an entry to /etc/fstab:

none  /dev/mqueue  mqueue  defaults  0  0

This mounts the filesystem automatically at boot time.

Leave a Reply

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