System V Message Queues Exercises & Complete Interview Q&A

 

System V Message Queues
Part 3 — Summary, Exercises & Complete Interview Q&A | Chapter 46 · TLPI
46.10
Summary
46.11
Exercises
10+
Interview Q&A

Chapter 46 — Complete Summary (Section 46.10)

Here is everything covered across the entire Chapter 46 in simple language:

📬

What are SysV Message Queues? A kernel-maintained linked list of messages. Each message has a numeric type (mtype) and a body of arbitrary data. Processes communicate by putting messages into or taking messages out of these queues.
🔑

Creating / Opening: Use msgget(key, flags). The key is either a value from ftok() or IPC_PRIVATE (for a guaranteed unique queue). Flags include IPC_CREAT and IPC_EXCL (like O_CREAT | O_EXCL for files).
📤

Sending: msgsnd(mqid, &msg, datasize, flags). The msg structure must start with a long mtype field (must be > 0). Blocks if the queue is full, unless IPC_NOWAIT is set.
📥

Receiving: msgrcv(mqid, &buf, maxsize, msgtype, flags). msgtype=0 reads any message (FIFO). msgtype=N reads only type N. msgtype=-N reads the lowest type ≤ N (priority-like behavior).
🔧

Control: msgctl(mqid, cmd, &buf). Key commands: IPC_STAT (read info), IPC_SET (modify limits), IPC_RMID (delete the queue). Always delete when done — queues persist until explicitly removed or system reboot.
🎯

Distinctive feature: Message type field allows receivers to select specific messages out of order. This is unique to SysV MQ (and POSIX MQ with priorities) — pipes and sockets are always FIFO byte streams.
⚠️

Key limitations: Not file-descriptor based (no select()), identified by keys (not paths), connectionless (no auto-cleanup), fixed kernel limits. Prefer POSIX MQ or FIFOs/sockets for new code.

All SysV MQ System Calls at a Glance
msgget()
Create or open a queue
msgsnd()
Send a message
msgrcv()
Receive a message
msgctl()
Control / delete
ftok()
Generate IPC key
IPC_PRIVATE
Unique private queue

Exercises — Section 46.11 (Explained + Code Hints)

Exercise 46-1

Task: Experiment with the programs svmsg_create.c, svmsg_send.c, and svmsg_receive.c to understand msgget(), msgsnd(), and msgrcv().

What to explore:

  • Try sending messages of different types and verify that msgrcv() can selectively read by type.
  • Fill the queue and observe that msgsnd() blocks.
  • Try IPC_NOWAIT to see EAGAIN instead of blocking.
  • Use ipcs -q to view queue state.
/* Minimal experiment: send type-1 and type-2 messages, read only type-2 */
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>

struct msg { long mtype; char text[64]; };

int main(void)
{
    int mqid = msgget(IPC_PRIVATE, 0600);
    struct msg m;

    /* Send type 1 */
    m.mtype = 1; strcpy(m.text, "type-one");
    msgsnd(mqid, &m, sizeof(m.text), 0);

    /* Send type 2 */
    m.mtype = 2; strcpy(m.text, "type-two");
    msgsnd(mqid, &m, sizeof(m.text), 0);

    /* Receive only type 2 — skips type 1 in queue */
    msgrcv(mqid, &m, sizeof(m.text), 2, 0);
    printf("Got mtype=%ld text='%s'\n", m.mtype, m.text);
    /* Output: Got mtype=2 text='type-two' */

    /* Now receive type 1 */
    msgrcv(mqid, &m, sizeof(m.text), 1, 0);
    printf("Got mtype=%ld text='%s'\n", m.mtype, m.text);
    /* Output: Got mtype=1 text='type-one' */

    msgctl(mqid, IPC_RMID, NULL);
    return 0;
}

Exercise 46-2

Task: Recode the sequence-number client-server application (originally using FIFOs in Section 44.8) to use a single SysV message queue for both client→server and server→client communication, using message types to distinguish them.

Design hint: Use mtype=1 for client requests. For responses, use the client’s PID as the mtype — this ensures only the correct client reads its own response.

/* Key idea: using PID as message type for routing responses */
#include <sys/msg.h>
#include <unistd.h>
#include <stdio.h>

#define REQ_MTYPE  1    /* all client requests use mtype=1 */
#define SERVER_KEY 0xABCD1234

struct request  { long mtype; pid_t pid; int seqlen; };
struct response { long mtype; int   seqnum; };   /* mtype = client's PID */

/* CLIENT side: */
void client_main(void)
{
    int mqid = msgget(SERVER_KEY, 0);
    struct request req;
    struct response resp;

    req.mtype  = REQ_MTYPE;
    req.pid    = getpid();
    req.seqlen = 3;
    msgsnd(mqid, &req, sizeof(req) - sizeof(long), 0);

    /* Wait for response addressed to my PID */
    msgrcv(mqid, &resp, sizeof(resp) - sizeof(long), getpid(), 0);
    printf("Sequence starts at: %d\n", resp.seqnum);
}

/* SERVER side: */
void server_main(void)
{
    int mqid = msgget(SERVER_KEY, IPC_CREAT | 0600);
    struct request req;
    struct response resp;
    int seq = 0;

    for (;;) {
        /* Read any request (mtype=1) */
        msgrcv(mqid, &req, sizeof(req) - sizeof(long), REQ_MTYPE, 0);

        /* Send reply addressed to client's PID */
        resp.mtype  = req.pid;
        resp.seqnum = seq;
        seq += req.seqlen;
        msgsnd(mqid, &resp, sizeof(resp) - sizeof(long), 0);
    }
}
Why PID as mtype? If you used mtype=1 for all responses, any client could accidentally receive another client’s response. Using the client’s own PID as the response mtype means only that specific client (calling msgrcv(..., getpid(), 0)) will receive it.

Exercise 46-3

Task: Explain why the client passes its queue identifier in the body of the message rather than in mtype.

Answer:

  • mtype must be a positive long integer (the kernel rejects zero or negative values — EINVAL). A queue ID from msgget(IPC_PRIVATE) could theoretically be 0.
  • The server uses msgrcv(..., REQ_MT_OPEN, 0) to read all client requests with the same type. If mtype were the clientId, every client would need a unique type and the server couldn’t simply read “all requests of type X”.
  • Putting clientId in the body is just data — safe for any integer value, and flexible.

Exercise 46-4

Task: Multiple improvements to the client-server app:

  • (a) Use IPC_PRIVATE + write queue ID to a well-known file
  • (b) Log errors with syslog()
  • (c) Daemonize the server
  • (d) Handle SIGTERM/SIGINT cleanly — delete queue before exit
  • (e) Add timeout for stale client (client disappeared)
/* Part (a): Server writes its queue ID to a file */
int write_server_id(int mqid)
{
    FILE *fp = fopen("/var/run/svmsg_server.id", "w");
    if (!fp) return -1;
    fprintf(fp, "%d\n", mqid);
    fclose(fp);
    return 0;
}

int read_server_id(void)
{
    int mqid;
    FILE *fp = fopen("/var/run/svmsg_server.id", "r");
    if (!fp) return -1;
    fscanf(fp, "%d", &mqid);
    fclose(fp);
    return mqid;
}

/* Part (d): Signal handler for clean exit */
static int g_serverId = -1;

void sig_handler(int sig)
{
    if (g_serverId != -1)
        msgctl(g_serverId, IPC_RMID, NULL);
    unlink("/var/run/svmsg_server.id");
    signal(sig, SIG_DFL);
    raise(sig);
}

/* Register in main(): */
/*   signal(SIGTERM, sig_handler); */
/*   signal(SIGINT,  sig_handler); */

/* Part (e): Timeout for disappeared client using alarm() */
#include <setjmp.h>
#include <signal.h>

static sigjmp_buf jump_env;

void alarm_handler(int sig) { siglongjmp(jump_env, 1); }

int msgsnd_with_timeout(int mqid, void *msg, size_t sz, int secs)
{
    signal(SIGALRM, alarm_handler);
    if (sigsetjmp(jump_env, 1) != 0) {
        /* alarm fired — client has disappeared */
        return -1;
    }
    alarm(secs);
    int ret = msgsnd(mqid, msg, sz, 0);
    alarm(0);   /* cancel alarm */
    return ret;
}

Comprehensive Interview Questions — Full Chapter 46
Q1. What is the structure of a System V message and what are the constraints on mtype?
A SysV message structure must begin with a long mtype field, followed by application-defined data. The mtype field must be a positive long integer (greater than 0). The kernel returns EINVAL if you try to send a message with mtype <= 0. The data portion can be any structure or array, up to the limit in /proc/sys/kernel/msgmax.
Q2. Explain the three modes of msgrcv() type filtering.
msgtype = 0: Read the first message in the queue regardless of type (strict FIFO).
msgtype = N (positive): Read the first message with exactly type N, skipping all others. If none exists, block (or return ENOMSG with IPC_NOWAIT).
msgtype = -N (negative): Read the first message whose type is the lowest value that is <= |N|. This allows priority-style reading — lower numeric types are treated as higher priority.
Q3. What flags can be passed to msgsnd() and what do they do?
The main flag is IPC_NOWAIT. Without it, msgsnd() blocks when the queue is full (total bytes exceed msgmnb or message count exceeds msgmni). With IPC_NOWAIT, it returns immediately with EAGAIN when the queue is full, allowing the program to do other work or retry later.
Q4. What does msgctl(IPC_STAT) return and how is it useful?
msgctl(mqid, IPC_STAT, &buf) fills a struct msqid_ds with information about the queue: current number of messages (msg_qnum), maximum queue capacity in bytes (msg_qbytes), PID of last sender/receiver, and timestamps of last send/receive. This is useful for monitoring queue health, debugging stuck consumers, or checking if messages are piling up.
Q5. Compare SysV MQ, POSIX MQ, and UNIX domain datagram sockets for IPC.
SysV MQ: Numeric key, no fd, no select/poll, numeric type filter, persists until deleted, complex to use.
POSIX MQ: Named path, returns fd on Linux (usable with poll/select), message priorities, async notification via mq_notify(), cleaner API. Preferred for new code.
UNIX domain datagram socket: Filesystem path (easy ls/rm), file descriptor, full select/poll/epoll support, no automatic message ordering by priority, but supports bidirectional and multicast patterns. Best when you need non-blocking event-driven IPC alongside network sockets.
Q6. How do you handle the case where msgsnd() may block indefinitely because the receiver has crashed?
Two approaches: (1) Use IPC_NOWAIT with a retry loop and sleep — non-blocking, but busy-waits. (2) Better approach: set up an alarm() before calling msgsnd(). The SIGALRM handler uses siglongjmp() to escape the blocked call. After returning to the sigsetjmp() point, the server can conclude the client is gone, delete the client’s queue, and exit the child. This is the technique described in Exercise 46-4(e) and Section 23.3 of TLPI.
Q7. What is IPC_EXCL and when would you use it with msgget()?
IPC_EXCL combined with IPC_CREAT in the flags argument to msgget() ensures that if a queue with the given key already exists, the call fails with EEXIST. This is the SysV IPC equivalent of O_CREAT | O_EXCL for files. Use it when your server starts up and must guarantee it creates a fresh queue — not accidentally reuse a leftover queue from a previous run that crashed without cleaning up.
Q8. Can a SysV message queue be used between processes on different machines?
No. System V IPC (message queues, semaphores, shared memory) is strictly local to one machine. The queue lives in the kernel of a single host. For cross-machine IPC, you need network-based mechanisms: sockets (TCP/UDP), message brokers (MQTT, ZeroMQ, RabbitMQ), or RPC frameworks. This is another reason to prefer UNIX domain sockets when designing new IPC on a single machine — it’s easy to later replace them with TCP sockets for distributed operation.
Q9. What is the MSG_NOERROR flag in msgrcv() and when is it useful?
Normally, if the message in the queue is larger than maxsize specified in msgrcv(), the call fails with E2BIG. When you add MSG_NOERROR to the flags, the message is silently truncated to maxsize bytes and the call succeeds. The truncated part is lost. This is useful when you know message sizes vary and you only care about the first N bytes (e.g., a fixed header), but it’s risky if truncation can cause logic errors.
Q10. Why is System V IPC generally considered “legacy” in modern Linux development?
SysV IPC (MQs, semaphores, shared memory) dates from the 1980s and has several design decisions that conflict with the UNIX “everything is a file” philosophy: it doesn’t use file descriptors, uses opaque integer keys instead of paths, has no reference counting, and requires separate tools (ipcs/ipcrm) for inspection and cleanup. Modern equivalents — POSIX MQ, POSIX semaphores, POSIX shared memory (via shm_open()), and UNIX domain sockets — fix all these issues while providing equivalent or superior functionality. Most Linux style guides and embedded Linux experts (including the TLPI author) recommend avoiding SysV IPC in new code unless you specifically need backward compatibility.

Quick Reference Cheat Sheet
/* ============================================================
   System V Message Queue — Quick Reference Cheat Sheet
   ============================================================ */

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

/* 1. Generate a key from a file + project number */
key_t key = ftok("/path/to/file", 'A');   /* can collide after file recreate */

/* 2. Create or open a queue */
int mqid = msgget(key,     IPC_CREAT | 0660);       /* named queue */
int mqid = msgget(IPC_PRIVATE, 0600);               /* private queue */
int mqid = msgget(key,     IPC_CREAT | IPC_EXCL | 0660); /* fail if exists */

/* 3. Define a message structure — MUST start with long mtype > 0 */
struct mymsg {
    long mtype;     /* REQUIRED: positive integer */
    char data[512]; /* your payload here           */
};

/* 4. Send a message */
struct mymsg m = { .mtype = 1, .data = "hello" };
msgsnd(mqid, &m, strlen(m.data) + 1, 0);           /* blocking  */
msgsnd(mqid, &m, strlen(m.data) + 1, IPC_NOWAIT);  /* non-block */

/* 5. Receive a message */
msgrcv(mqid, &m, sizeof(m.data), 0,  0);   /* any type (FIFO)        */
msgrcv(mqid, &m, sizeof(m.data), 2,  0);   /* only type 2            */
msgrcv(mqid, &m, sizeof(m.data), -5, 0);   /* lowest type <= 5       */
msgrcv(mqid, &m, sizeof(m.data), 0,  IPC_NOWAIT);   /* non-blocking  */
msgrcv(mqid, &m, sizeof(m.data), 0,  MSG_NOERROR);  /* truncate ok   */

/* 6. Control */
struct msqid_ds info;
msgctl(mqid, IPC_STAT, &info);   /* get queue info                   */
msgctl(mqid, IPC_SET,  &info);   /* change limits (root only)        */
msgctl(mqid, IPC_RMID, NULL);    /* DELETE the queue (always do this) */

/* 7. Shell tools */
/* ipcs -q              — list all message queues                     */
/* ipcrm -q <msqid>    — delete a queue by ID                        */
/* cat /proc/sys/kernel/msgmax   — max message size                   */
/* cat /proc/sys/kernel/msgmnb   — max queue capacity                 */
/* cat /proc/sys/kernel/msgmni   — max number of queues               */

Chapter 46 Complete!
You’ve covered the full chapter — client-server design, all disadvantages, and exercises.

← Part 1: Client-Server ← Part 2: Disadvantages Back to Course Index →

Leave a Reply

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