Message Queue Limits: System Constants & /proc Tunables POSIX Message Queues

 

POSIX Message Queues
Part 8 — Message Queue Limits: System Constants & /proc Tunables
🔢 POSIX
MQ_PRIO_MAX
📁 Proc
/proc/sys/fs/mqueue
🛡️ Privilege
CAP_SYS_RESOURCE

Why Limits Matter

POSIX message queues are a shared kernel resource. Without limits, a runaway process could exhaust kernel memory by creating thousands of queues or sending huge messages. Linux enforces limits at three levels:

  • POSIX standard limits — defined in <mqueue.h> (e.g., MQ_PRIO_MAX).
  • Linux kernel tunables — adjustable via /proc/sys/fs/mqueue/.
  • Per-process resource limits — set via RLIMIT_MSGQUEUE (enforced by the kernel’s rlimit mechanism).

Key Terms
MQ_PRIO_MAX MQ_OPEN_MAX msg_max msgsize_max queues_max HARD_MSGMAX CAP_SYS_RESOURCE RLIMIT_MSGQUEUE RLIMIT_NOFILE mq_maxmsg mq_msgsize

1. POSIX Standard Limits

SUSv3 defines two limits that implementations must honour:

POSIX-Defined Limits
Constant Meaning Linux Value
MQ_PRIO_MAX Maximum priority value allowed for a message (priorities 0 to MQ_PRIO_MAX−1) 32,768 (on Linux/x86)
MQ_OPEN_MAX Maximum number of queues a process can hold open simultaneously Not defined — uses file descriptor limits instead

MQ_OPEN_MAX on Linux: Linux does not define this limit separately. Since message queue descriptors are file descriptors on Linux, the applicable limits are RLIMIT_NOFILE (per-process open file limit) and the system-wide file descriptor limit. In other words, the total count of open files and open message queue descriptors together must not exceed these limits.

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

int main(void) {
    /* Print standard limits */
    printf("MQ_PRIO_MAX = %ld\n", (long)MQ_PRIO_MAX);

    /* MQ_OPEN_MAX may not be defined on Linux */
#ifdef MQ_OPEN_MAX
    printf("MQ_OPEN_MAX = %ld\n", (long)MQ_OPEN_MAX);
#else
    printf("MQ_OPEN_MAX not defined (Linux uses fd limits)\n");
#endif

    return 0;
}

2. Linux Kernel Tunables: /proc/sys/fs/mqueue

Linux exposes three files under /proc/sys/fs/mqueue/ that control how message queues are created. These can be read and written (with root) at runtime:

/proc/sys/fs/mqueue/ — Three Control Files
msg_max
Ceiling for mq_maxmsg (max messages in a new queue). Default: 10.
Min: 1 (kernel ≥2.6.28) | Max: HARD_MSGMAX = 131072/sizeof(void*) = 32768 on x86-32
msgsize_max
Ceiling for mq_msgsize (max size per message) for unprivileged processes. Default: 8192 bytes.
Min: 128 (kernel ≥2.6.28) | Max: 1,048,576 bytes
queues_max
System-wide limit on total number of message queues that can exist simultaneously. Default: 256.
Range: 0 to INT_MAX | Privileged (CAP_SYS_RESOURCE) processes can exceed this limit

Read current values:

cat /proc/sys/fs/mqueue/msg_max
# 10

cat /proc/sys/fs/mqueue/msgsize_max
# 8192

cat /proc/sys/fs/mqueue/queues_max
# 256

Change values (requires root):

# Allow up to 50 messages per queue
echo 50 > /proc/sys/fs/mqueue/msg_max

# Allow messages up to 65536 bytes
echo 65536 > /proc/sys/fs/mqueue/msgsize_max

# Allow up to 1024 queues system-wide
echo 1024 > /proc/sys/fs/mqueue/queues_max

# Permanent change (survives reboot) via sysctl.conf
echo "fs.mqueue.msg_max = 50"       >> /etc/sysctl.conf
echo "fs.mqueue.msgsize_max = 65536" >> /etc/sysctl.conf
echo "fs.mqueue.queues_max = 1024"  >> /etc/sysctl.conf
sysctl -p

3. How Limits Affect mq_open()

When you call mq_open() with O_CREAT and provide a struct mq_attr, the kernel checks your requested values against the limits:

Limit Enforcement on mq_open() for Unprivileged Process
Requested Value Ceiling Enforced Error if Exceeded
attr.mq_maxmsg msg_max (default 10) EINVAL
attr.mq_msgsize msgsize_max (default 8192) EINVAL
Total queues in system queues_max (default 256) ENOSPC
#include <mqueue.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

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

    /* Try to create a queue with message size exceeding msgsize_max (8192) */
    attr.mq_flags   = 0;
    attr.mq_maxmsg  = 10;
    attr.mq_msgsize = 100000;   /* This exceeds the 8192 default */
    attr.mq_curmsgs = 0;

    mqd = mq_open("/test_limit", O_CREAT | O_RDWR, 0600, &attr);
    if (mqd == (mqd_t)-1) {
        if (errno == EINVAL)
            printf("EINVAL: mq_msgsize exceeds msgsize_max limit\n");
        else
            perror("mq_open");
        return 1;
    }

    printf("Queue created successfully.\n");
    mq_close(mqd);
    mq_unlink("/test_limit");
    return 0;
}
/* To fix: either reduce mq_msgsize, or raise the limit as root:
 *   echo 200000 > /proc/sys/fs/mqueue/msgsize_max
 * or run the program as a privileged process. */

Privileged processes (CAP_SYS_RESOURCE):

  • msg_max is bypassed — but HARD_MSGMAX (kernel constant) still applies.
  • msgsize_max is completely ignored — the process can set any size.
  • Once queues_max is reached, only privileged processes can create more queues.

4. Per-User Limit: RLIMIT_MSGQUEUE

Linux also enforces RLIMIT_MSGQUEUE, which limits the total amount of kernel memory that can be consumed by all message queues owned by the calling process’s real user ID.

This is measured in bytes. The accounting formula per queue is approximately:

/* Memory charged for each queue: */
bytes = mq_maxmsg * (mq_msgsize + sizeof(struct msg_msg));

View and change per-process limits with getrlimit / setrlimit:

#include <sys/resource.h>
#include <stdio.h>

int main(void)
{
    struct rlimit rl;

    if (getrlimit(RLIMIT_MSGQUEUE, &rl) == -1) {
        perror("getrlimit");
        return 1;
    }

    printf("RLIMIT_MSGQUEUE: soft=%ld, hard=%ld\n",
           (long)rl.rlim_cur, (long)rl.rlim_max);

    /* Raise soft limit to hard limit */
    rl.rlim_cur = rl.rlim_max;
    if (setrlimit(RLIMIT_MSGQUEUE, &rl) == -1)
        perror("setrlimit");

    return 0;
}

Or check and set via the shell:

# Show current limit
ulimit -q
# 819200  (800 KB default on many systems)

# Increase to 4 MB for current shell session
ulimit -q 4194304
Note: When RLIMIT_MSGQUEUE is reached, mq_open() with O_CREAT fails with ENOMEM. This applies to the total of all queues owned by the user, not just the current process.

5. Checking and Querying Limits Programmatically
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>

/* Read an integer from a /proc file */
static long read_proc(const char *path)
{
    FILE *fp = fopen(path, "r");
    long val = -1;
    if (fp) { fscanf(fp, "%ld", &val); fclose(fp); }
    return val;
}

int main(void)
{
    struct rlimit rl;

    printf("=== POSIX Message Queue Limits ===\n\n");

    /* Standard limits */
    printf("MQ_PRIO_MAX (max message priority)  : %d\n", MQ_PRIO_MAX);

    /* /proc tunables */
    printf("\n/proc/sys/fs/mqueue tunables:\n");
    printf("  msg_max     (max msgs per queue)   : %ld\n",
           read_proc("/proc/sys/fs/mqueue/msg_max"));
    printf("  msgsize_max (max msg size, bytes)  : %ld\n",
           read_proc("/proc/sys/fs/mqueue/msgsize_max"));
    printf("  queues_max  (max queues system-wide): %ld\n",
           read_proc("/proc/sys/fs/mqueue/queues_max"));

    /* Per-process resource limit */
    if (getrlimit(RLIMIT_MSGQUEUE, &rl) == 0)
        printf("\nRLIMIT_MSGQUEUE (per-user memory cap): soft=%ld hard=%ld\n",
               (long)rl.rlim_cur, (long)rl.rlim_max);

    /* File descriptor limit (applies to mqd on Linux) */
    if (getrlimit(RLIMIT_NOFILE, &rl) == 0)
        printf("RLIMIT_NOFILE   (fd + mqd combined)  : soft=%ld hard=%ld\n",
               (long)rl.rlim_cur, (long)rl.rlim_max);

    return 0;
}

Interview Questions & Answers
Q1. What are the three files in /proc/sys/fs/mqueue/ and what do they control?
msg_max — ceiling for mq_maxmsg (max messages per queue, default 10). msgsize_max — ceiling for mq_msgsize (max message body size, default 8192 bytes). queues_max — system-wide limit on the total number of queues that can exist (default 256).
Q2. What is HARD_MSGMAX and who does it apply to?
HARD_MSGMAX is a kernel constant that acts as an absolute ceiling for mq_maxmsg, even for privileged processes. It is calculated as 131072 / sizeof(void*), which is 32,768 on a 32-bit system and 16,384 on a 64-bit system. Privileged processes can bypass msg_max but cannot exceed HARD_MSGMAX.
Q3. What error does mq_open() return when a requested attribute exceeds a limit?
EINVAL if mq_maxmsg or mq_msgsize exceeds the corresponding /proc limit. ENOSPC if the system-wide queues_max has been reached. ENOMEM if RLIMIT_MSGQUEUE for the user is exhausted.
Q4. Why does Linux not define MQ_OPEN_MAX?
Because on Linux, message queue descriptors are real file descriptors. The limit on how many a process can open is therefore governed by RLIMIT_NOFILE, which also limits regular file descriptors. Linux does not need a separate MQ_OPEN_MAX constant — the two resource types share the same pool.
Q5. What is RLIMIT_MSGQUEUE and how is it different from queues_max?
RLIMIT_MSGQUEUE is a per-user limit on the total kernel memory consumed by all message queues belonging to a given user’s real UID. It is measured in bytes, not in number of queues. queues_max limits the total count of queues system-wide regardless of ownership. Both limits can independently prevent queue creation.
Q6. How can you permanently change mqueue limits across reboots?
Add sysctl settings to /etc/sysctl.conf (or a file under /etc/sysctl.d/), for example: fs.mqueue.msg_max = 50. Run sysctl -p to apply immediately. These are re-applied automatically at boot.

Leave a Reply

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