1. Intro 2. Open/Close/Unlink 3. Attributes 4. Send/Receive 5. Notify 6. Linux Specifics 7. Interview Q&A
| Function | Purpose | Returns | Key Errors |
|---|---|---|---|
mq_open() |
Create or open a queue | mqd_t or (mqd_t)-1 | ENOENT, EEXIST, EACCES, EINVAL |
mq_close() |
Close a descriptor (not the queue) | 0 or -1 | EBADF |
mq_unlink() |
Remove queue name (destroy when ref=0) | 0 or -1 | ENOENT, EACCES |
mq_getattr() |
Get queue attributes into struct mq_attr | 0 or -1 | EBADF |
mq_setattr() |
Set mq_flags (O_NONBLOCK only) | 0 or -1 | EBADF |
mq_send() |
Add message with priority | 0 or -1 | EAGAIN (full+nonblock), EMSGSIZE |
mq_receive() |
Remove highest-priority message | bytes or -1 | EAGAIN (empty+nonblock), EMSGSIZE |
mq_timedsend() |
Send with absolute timeout | 0 or -1 | ETIMEDOUT, EINVAL |
mq_timedreceive() |
Receive with absolute timeout | bytes or -1 | ETIMEDOUT, EINVAL |
mq_notify() |
Register async notification | 0 or -1 | EBUSY, EBADF |
Q1: What is a POSIX message queue?
A kernel-maintained IPC mechanism that allows processes to exchange discrete messages. Each message has a byte length and an integer priority. Messages are always received in highest-priority-first order, with FIFO ordering among equal-priority messages.
Q2: What header is required and what library must you link?
Header: #include <mqueue.h>. Link: gcc prog.c -lrt. On modern glibc (≥2.17) the -lrt may be optional but should always be included for portability.
Q3: How do POSIX message queues differ from pipes?
Pipes are unidirectional byte streams with no message boundaries. POSIX MQs have discrete messages with explicit lengths, priorities, and priority-ordered delivery. Pipes are anonymous (related processes only) whereas POSIX MQs are named (unrelated processes can communicate).
Q4: What are the naming rules for POSIX MQ names?
Must begin with /. Must contain no other / characters. Example: /myqueue. On Linux these names appear under /dev/mqueue/.
Q5: What is the type of a POSIX MQ descriptor and is it related to file descriptors?
mqd_t. On Linux it is a genuine file descriptor (integer), visible in /proc/<pid>/fd/ and usable with select()/poll()/epoll(). On other UNIX systems it may be an opaque type.
Q6: Are POSIX message queues persistent?
Yes — they persist in the kernel until explicitly removed with mq_unlink() or the system reboots. Closing all descriptors does NOT destroy the queue.
Q7: When was POSIX MQ support added to the Linux kernel?
Kernel version 2.6.6.
Q8: What is the minimum priority and maximum priority for a POSIX MQ message?
Minimum: 0 (lowest). Maximum: MQ_PRIO_MAX – 1. POSIX requires MQ_PRIO_MAX ≥ 32. Linux sets it to 32768, so valid priorities are 0 to 32767.
Q9: What is the difference between mq_close() and mq_unlink()?
mq_close() releases the per-process descriptor (like close()). The queue stays in the kernel. mq_unlink() removes the queue’s name. The queue memory is freed only when both unlinked AND all descriptors closed (reference counting).
Q10: Write the correct mq_open() call to create a new queue with max 10 messages of 256 bytes each.
struct mq_attr attr = { .mq_maxmsg=10, .mq_msgsize=256 };
mqd_t mqd = mq_open("/myq", O_CREAT|O_EXCL|O_RDWR, 0600, &attr);
Q11: What is the purpose of O_EXCL in mq_open()?
When combined with O_CREAT, it causes mq_open() to fail with EEXIST if the queue already exists. Useful in server programs to detect stale queues from a previous crash.
Q12: How do you switch a queue from blocking to non-blocking mode after it is already open?
struct mq_attr new_attr = {0};
new_attr.mq_flags = O_NONBLOCK;
mq_setattr(mqd, &new_attr, NULL);
Q13: What does mq_setattr() actually change? What does it ignore?
It only honours changes to mq_flags (O_NONBLOCK). It silently ignores any changes to mq_maxmsg and mq_msgsize.
Q14: What is the correct receive buffer size to pass to mq_receive()?
At least mq_msgsize bytes. Always query with mq_getattr() first. Passing a smaller buffer returns EMSGSIZE.
Q15: What does mq_receive() return on success?
The number of bytes in the received message (ssize_t). Not the buffer size — the actual message length.
Q16: How is the absolute timeout for mq_timedreceive() constructed?
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 5; /* 5 seconds from now */
mq_timedreceive(mqd, buf, size, &prio, &ts);
Q17: What error does mq_timedreceive() return when the timeout expires?
Returns -1 with errno == ETIMEDOUT.
Q18: Can you send a C struct through a POSIX message queue?
Yes. Messages are raw byte arrays. Cast a pointer to the struct to const char * and pass sizeof(struct) as the length. Ensure mq_msgsize ≥ sizeof(struct) at creation.
Q19: What are the two notification methods for mq_notify()?
SIGEV_SIGNAL: sends a specified signal to the process.
SIGEV_THREAD: creates a new thread and calls a specified callback function.
Q20: What is the “one-shot” rule of mq_notify()?
A notification fires exactly once and then the registration is automatically cancelled. You must call mq_notify() again (typically inside the handler/callback) to receive future notifications.
Q21: When exactly does mq_notify() fire?
Only when a message is placed on a queue that was previously empty. If the queue already has messages when mq_notify() is called, the notification does not fire until the queue is first drained to empty and then a new message arrives.
Q22: What happens if two processes try to call mq_notify() on the same queue?
The second call fails with errno == EBUSY. Only one process at a time can be registered for notification on any given queue.
Q23: Why open the queue with O_NONBLOCK when using mq_notify()?
The signal handler or notification thread must drain the queue. In blocking mode, the last mq_receive() call after the queue becomes empty would block forever. O_NONBLOCK makes it return EAGAIN instead, allowing clean exit from the drain loop.
Q24: How do you cancel a notification?
Call mq_notify(mqd, NULL). Also, mq_close() automatically cancels any notification registered on that descriptor.
Q25: What is the lifetime requirement for data passed via sival_ptr in SIGEV_THREAD?
It must remain valid until the notification thread completes. Never point to a local variable that may go out of scope before the thread runs. Use global, static, or heap-allocated (malloc) data.
Q26: Why is sigwaitinfo() preferred over a signal handler for SIGEV_SIGNAL?
Signal handlers must only call async-signal-safe functions (a very restrictive subset). With sigwaitinfo(), you block the signal and wait for it synchronously in the main thread, where any function can be called safely.
Q27: Where do POSIX MQ names appear on Linux?
Under /dev/mqueue/, via the mqueue virtual filesystem. You can inspect queues with cat /dev/mqueue/queuename and delete them with rm.
Q28: What five parameters are in /proc/sys/fs/mqueue/?
msg_default, msgsize_default (defaults when NULL attr passed),
msg_max, msgsize_max (hard ceilings for unprivileged processes),
queues_max (system-wide max number of queues).
Q29: What does RLIMIT_MSGQUEUE limit?
The total number of bytes that can be reserved for POSIX MQs by a single user. Checked at mq_open() time based on the queue’s maximum capacity, not current fill level.
Q30: Can you use epoll() with POSIX MQ on Linux? Why?
Yes, because on Linux mqd_t is a genuine file descriptor. You can add it to an epoll instance with epoll_ctl(EPOLL_CTL_ADD). EPOLLIN fires when messages are available; EPOLLOUT fires when the queue has space. This is a Linux-specific extension.
Q31: Why did Linux choose kernel-space over user-space for POSIX MQ implementation?
A user-space implementation (e.g., using shared memory + semaphores) cannot be secured: any process with access to the shared memory could corrupt the queue structure. Kernel implementation ensures all access goes through controlled system calls.
Q32: How does IPC namespace affect POSIX MQs?
Each IPC namespace gets its own mqueue VFS with an independent set of queues. Queues in different namespaces are completely isolated — the same queue name in two containers refers to two different queues. Used by Docker/LXC for container isolation.
Q33: What is MQ_PRIO_MAX on Linux and what does it mean?
32768 on Linux. Valid message priorities are 0 (lowest) to 32767 (highest). POSIX only guarantees this is at least 32.
Q34: Name three advantages of POSIX MQ over System V MQ.
1. Reference counting — the queue is cleaned up automatically when last descriptor closed after unlink.
2. Asynchronous notification via mq_notify() — System V has no equivalent.
3. Named by string (like a file path) rather than integer key — easier to share between unrelated processes.
(Bonus) On Linux, mqd_t is a real fd so you can use select/poll/epoll.
Q35: Name two advantages of System V MQ over POSIX MQ.
1. Better portability — System V MQs are available on more UNIX systems and older kernels.
2. Message selection by type — msgrcv() can selectively receive messages of a specific type, whereas POSIX MQ always gives you the highest priority first (less flexible for some use cases).
Q36: Both queue types are “kernel persistent” — what does that mean exactly?
The queue exists in the kernel until explicitly removed, regardless of whether any process has it open. A crash, exit, or close does not automatically clean up the queue. You must explicitly call mq_unlink() (POSIX) or msgctl(IPC_RMID) (System V).
Q37: Compare how System V and POSIX MQ select which message to deliver.
System V: msgrcv() has a msgtyp parameter — you can request a specific message type, the lowest type, or any message. Very flexible.
POSIX: always delivers the highest-priority message first. Among equal priorities, FIFO order. Less flexible but simpler and more real-time-friendly.
Q38: What is wrong with this code?
char buf[32];
mq_receive(mqd, buf, sizeof(buf), NULL); /* BUG? */
Potentially wrong if mq_msgsize > 32. mq_receive() will fail with EMSGSIZE. The buffer must be at least mq_msgsize bytes. Always call mq_getattr() to find the correct size.
Q39: What is wrong with this timeout code?
struct timespec ts = { .tv_sec = 5 }; /* BUG */
mq_timedreceive(mqd, buf, size, &prio, &ts);
The timeout is absolute, not relative. Setting tv_sec = 5 means “wait until 5 seconds past the Unix epoch” — which is in 1970 and will immediately expire. Correct: get current time with clock_gettime(CLOCK_REALTIME, &ts) and then add 5.
Q40: In Listing 52-7 (TLPI exercise), could buffer be made a global variable with memory allocated once in main? Why or why not?
No — not safely. The notification is delivered via a thread (SIGEV_THREAD). If the same buffer is global, the notification thread and main thread could race to use it simultaneously. Each invocation of the notification callback should allocate its own buffer, or you must protect access with a mutex. A global buffer would require synchronization, making it more complex than simply allocating per-call.
Q41: What does this sequence do and is it correct?
mqd_t mqd = mq_open("/q", O_CREAT|O_RDWR, 0600, &attr);
mq_send(mqd, "hello", 6, 1);
mq_close(mqd);
/* Later: */
mqd_t mqd2 = mq_open("/q", O_RDONLY);
char buf[64]; unsigned int p;
mq_receive(mqd2, buf, 64, &p);
mq_close(mqd2);
mq_unlink("/q");
Yes, this is correct. The message persists in the queue between the close and second open because the queue is kernel-persistent. The second open sees the message that was sent before the first close.
Q42: What output order would you get from sending A(prio=5), B(prio=3), C(prio=5), D(prio=1) and then receiving all?
A (prio=5), C (prio=5), B (prio=3), D (prio=1).
Reason: A and C are both priority 5. A was sent first so it comes first (FIFO within same priority). B (prio=3) comes before D (prio=1).
1. What POSIX MQs are: Kernel-maintained, named IPC channels for discrete messages with priority ordering. Include <mqueue.h>, link with -lrt.
2. Lifecycle: mq_open() → use → mq_close() → mq_unlink(). Queue persists until unlinked AND all descriptors closed (reference counting, like files).
3. Attributes: mq_maxmsg and mq_msgsize are fixed at creation. Only mq_flags (O_NONBLOCK) can be changed later via mq_setattr(). mq_curmsgs is read-only.
4. Priority ordering: mq_receive() always returns the highest-priority message. Equal-priority messages are FIFO. Priority is unsigned int, 0 = lowest.
5. Timed operations: Timeout is absolute (CLOCK_REALTIME), not relative. Use clock_gettime() + add duration.
6. Notification (mq_notify): One-shot. Must re-register after each notification. Only fires when message arrives on empty queue. SIGEV_SIGNAL or SIGEV_THREAD. Only one process registered per queue at a time (EBUSY otherwise).
7. Linux specifics: Queues appear in /dev/mqueue/. mqd_t is a real fd (usable with poll/epoll). Limits tunable via /proc/sys/fs/mqueue/. RLIMIT_MSGQUEUE per-user byte limit. IPC namespaces provide queue isolation between containers.
8. POSIX vs System V: POSIX has reference counting + async notify. System V has better portability + flexible type-based message selection.
Exercise 52-1: Modify pmsg_receive.c to accept a timeout in seconds on command line and use mq_timedreceive().
/* Key change: parse seconds from argv[2], build absolute timeout */
int timeout_secs = atoi(argv[2]);
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_secs;
ssize_t n = mq_timedreceive(mqd, buf, attr.mq_msgsize, &prio, &ts);
if (n == -1 && errno == ETIMEDOUT)
fprintf(stderr, "Timed out after %d seconds\n", timeout_secs);
Exercise 52-2: Recode sequence-number client-server (Section 44.8) using POSIX MQs. Key idea: server has a well-known request queue. Each client creates its own reply queue (using PID in the name), sends request + reply queue name, server responds on the client’s queue.
/* Client creates reply queue */
char reply_qname[64];
snprintf(reply_qname, sizeof(reply_qname), "/seqnum_reply_%d", getpid());
/* Request message includes the reply queue name */
struct request_msg { char reply_q[64]; int seqlen; };
Exercise 52-4: Simple chat using POSIX MQs. Two users each have their own queue. User A sends to User B’s queue, User B sends to User A’s queue. Use select() or two threads to simultaneously read and write.
/* Each user's queue */
// /chat_userA and /chat_userB
// User A: reads from /chat_userA, writes to /chat_userB
// User B: reads from /chat_userB, writes to /chat_userA
// Use two threads per process: one for reading, one for stdin+sending
Exercise 52-5: Demonstrate mq_notify() fires only once: remove the mq_notify() call inside the for loop in mq_notify_sig.c. After the first message triggers the handler, send more messages — they will NOT trigger the handler again because notification was not re-registered.
Exercise 52-6: Replace signal handler with sigwaitinfo(). Block the signal with sigprocmask(SIG_BLOCK) before calling mq_notify(), then call sigwaitinfo() in a loop. The siginfo_t.si_value field (specifically si_value.sival_int) will contain the mqd_t if you used SI_ASYNCIO or set it manually, or you can pass the mqd via sigev_value.
Exercise 52-7: Could buffer in Listing 52-7 be global? No — thread-safety issue. The SIGEV_THREAD callback can be invoked concurrently. A single global buffer would be a race condition. Each invocation needs its own buffer, or the buffer must be protected by a mutex. Allocating inside the callback (per-call) is safer.
Server creates a request queue. Client sends a request including its own reply queue name. Server processes and replies. Demonstrates real-world bidirectional communication.
/* === SERVER === (run first) */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#define SERVER_Q "/ep_server"
#define MSGSIZE 256
typedef struct {
char reply_q[64]; /* client's reply queue name */
int request_num; /* what number to square */
} Request;
typedef struct {
int result; /* computed result */
} Response;
int main(void) /* server */
{
struct mq_attr attr = { .mq_maxmsg=10, .mq_msgsize=MSGSIZE };
mq_unlink(SERVER_Q);
mqd_t srv = mq_open(SERVER_Q, O_CREAT|O_RDONLY, 0666, &attr);
if (srv == (mqd_t)-1) { perror("server mq_open"); return 1; }
printf("[server] listening on %s\n", SERVER_Q);
char buf[MSGSIZE];
unsigned int prio;
for (;;) {
if (mq_receive(srv, buf, MSGSIZE, &prio) == -1) {
perror("server mq_receive"); break;
}
Request *req = (Request *)buf;
printf("[server] request: square(%d), reply to %s\n",
req->request_num, req->reply_q);
Response resp;
resp.result = req->request_num * req->request_num;
mqd_t rq = mq_open(req->reply_q, O_WRONLY);
if (rq == (mqd_t)-1) { perror("open reply q"); continue; }
mq_send(rq, (char*)&resp, sizeof(resp), 1);
mq_close(rq);
}
mq_close(srv);
mq_unlink(SERVER_Q);
return 0;
}
/* === CLIENT === (run after server) */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#define SERVER_Q "/ep_server"
#define MSGSIZE 256
typedef struct {
char reply_q[64];
int request_num;
} Request;
typedef struct {
int result;
} Response;
int main(int argc, char *argv[])
{
if (argc < 2) { fprintf(stderr, "Usage: %s number\n", argv[0]); return 1; }
int num = atoi(argv[1]);
/* Create client's own reply queue */
char rqname[64];
snprintf(rqname, sizeof(rqname), "/ep_reply_%d", getpid());
struct mq_attr attr = { .mq_maxmsg=2, .mq_msgsize=MSGSIZE };
mqd_t rq = mq_open(rqname, O_CREAT|O_RDONLY, 0600, &attr);
if (rq == (mqd_t)-1) { perror("reply mq_open"); return 1; }
/* Build and send request */
Request req;
strncpy(req.reply_q, rqname, sizeof(req.reply_q)-1);
req.request_num = num;
mqd_t srv = mq_open(SERVER_Q, O_WRONLY);
if (srv == (mqd_t)-1) { perror("server mq_open"); goto cleanup; }
mq_send(srv, (char*)&req, sizeof(req), 1);
mq_close(srv);
printf("[client] sent request: square(%d)\n", num);
/* Wait for response */
char buf[MSGSIZE];
unsigned int prio;
if (mq_receive(rq, buf, MSGSIZE, &prio) == -1) {
perror("mq_receive reply"); goto cleanup;
}
Response *resp = (Response *)buf;
printf("[client] result: %d^2 = %d\n", num, resp->result);
cleanup:
mq_close(rq);
mq_unlink(rqname);
return 0;
}
You have covered all of Chapter 52 — POSIX Message Queues. Go back to any topic to review.
