System V Message Queues – linux system programming course

 

Chapter 46 · File 1 of 6

System V Message Queues

Introduction, Overview & Core Concepts

This is the first file in our Chapter 46 series. Before writing any code, let’s understand what a message queue is, why it was invented, and how it compares to pipes. If you are new to Linux IPC, start here.

What is IPC? (Quick Recap)

IPC stands for Inter-Process Communication. When two or more processes need to share data or coordinate their work, they need an IPC mechanism. Linux provides many:

Linux IPC Mechanisms — Family Tree
Pipes FIFOs Sockets
Sys V Shared Memory Sys V Semaphores Sys V Message Queues ← this chapter
POSIX Shared Memory POSIX Semaphores POSIX Message Queues

System V IPC (also called XSI IPC) is the older set. It was designed in the 1980s on System V Unix before POSIX existed. Message queues are one part of it.

What is a System V Message Queue?

Think of a message queue like a post box inside the kernel. Any process that knows the address (the queue ID) can drop a message in, and any authorised process can pick a message out. The kernel stores all messages safely until someone reads them.

Message Queue Concept
Process A (Writer)
msgsnd()

Kernel Message Queue
MSG type=1 | “Hello”
MSG type=2 | “World”
MSG type=1 | “Data”

Process B (Reader)
msgrcv()
The kernel holds the queue — no file on disk, no network involved

Key Characteristics

1. Identified by a Queue ID, not a File Descriptor

When you create a message queue, the kernel returns an integer called the queue identifier (msqid). This is not a file descriptor. You cannot use it with read(), write(), select(), or poll(). This is one of the biggest differences from pipes.

Key Point: Because message queues don’t use file descriptors, you cannot use I/O multiplexing (select/poll/epoll) to wait on a message queue along with other file-based I/O. This is a major limitation.

2. Message-Oriented (Not Byte-Stream)

Pipes give you a stream of raw bytes — the reader can read any number of bytes at a time. Message queues are different: each write sends one complete message, and each read receives one complete message. You cannot read half a message.

Pipe vs Message Queue — Data Model
Pipe (Byte Stream)
Writer writes: “HELLOWORLD”
Reader reads: “HELL” (4 bytes)
Reader reads: “OWOR” (4 bytes)
Reader reads: “LD” (2 bytes)
← reader decides how much
Message Queue (Message-Oriented)
Writer sends msg: “HELLO”
Writer sends msg: “WORLD”
Reader gets msg: “HELLO” (whole)
Reader gets msg: “WORLD” (whole)
← always whole messages

3. Every Message Has a Type (Integer)

Each message carries a type field (a positive long integer). The reader can either read the first message in line (FIFO order) or select messages by type. This is very powerful — for example, different processes can read only the message types meant for them from a single shared queue.

Reading by Type — Example
Queue Contents:
[type=1] Login request
[type=2] Logout request
[type=1] Login request
[type=3] Error report
msgrcv(type=1)
→ gets only Login requests
msgrcv(type=2)
→ gets only Logout requests
msgrcv(type=0)
→ gets first in queue (any type)

Comparison: Pipes vs FIFOs vs System V Queues

Feature Pipe FIFO (Named Pipe) System V Msg Queue
I/O model Byte stream Byte stream Messages (typed)
Identified by File descriptor Filename + FD Queue ID (msqid)
Works with select/poll? Yes Yes No
Unrelated processes? No (parent-child only) Yes (via filename) Yes (via key)
Message types? No No Yes
Kernel persistence? No No Yes — survives process exit!
Requires explicit delete? No No (unlink the file) Yes — msgctl(IPC_RMID)
Portable? Yes Yes Older API, less portable
⚠ Important — Kernel Persistence: Unlike pipes, a System V message queue is NOT automatically deleted when the creating process exits. If you don’t explicitly remove it with msgctl(IPC_RMID), it stays in the kernel forever (until reboot or manual removal with ipcrm). Forgetting to clean up is one of the most common bugs with System V IPC.

When to Use (and When to Avoid)

Use System V Message Queues when:

  • You are maintaining or extending existing legacy code that already uses them.
  • You need message-type-based filtering and do not want to use POSIX queues.
  • You are working on an older embedded system that only has System V APIs.

Avoid for new code — prefer instead:

  • POSIX message queues (mq_open) — modern, supports select/poll, better designed.
  • Unix domain sockets — work with all standard I/O APIs, bidirectional.
  • FIFOs + custom framing — simpler for many use cases.

Code Example 1 — Minimal Message Queue Flow

This is the simplest possible example showing the 4-step lifecycle of a message queue: create → send → receive → delete.

/* minimal_msgq.c — Minimal System V Message Queue Demo */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

/* Step 1: Define your message structure.
   MUST start with a long called mtype.
   The rest (mtext) can be anything you want. */
struct MyMessage {
    long mtype;        /* Message type — must be > 0 */
    char mtext[100];   /* Payload */
};

int main(void)
{
    int msqid;
    struct MyMessage msg;

    /* Step 2: Create a private message queue.
       IPC_PRIVATE means only this process (and its children) can use it.
       0666 = read+write permissions for owner, group, others */
    msqid = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
    if (msqid == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }
    printf("Queue created, ID = %d\n", msqid);

    /* Step 3: Send a message — fill in type and text */
    msg.mtype = 1;   /* type 1 = some kind of 'request' */
    strncpy(msg.mtext, "Hello from sender!", sizeof(msg.mtext) - 1);

    /* msgsnd(queue_id, message_pointer, size_of_payload, flags)
       Note: size is sizeof(mtext) — NOT sizeof(whole struct) */
    if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
        perror("msgsnd");
        exit(EXIT_FAILURE);
    }
    printf("Message sent: '%s' (type=%ld)\n", msg.mtext, msg.mtype);

    /* Step 4: Receive the message */
    struct MyMessage received;
    /* msgrcv(queue_id, buffer, max_size, type_to_receive, flags)
       type=0 means: give me the first message regardless of type */
    ssize_t bytes = msgrcv(msqid, &received, sizeof(received.mtext), 0, 0);
    if (bytes == -1) {
        perror("msgrcv");
        exit(EXIT_FAILURE);
    }
    printf("Message received: '%s' (type=%ld)\n", received.mtext, received.mtype);

    /* Step 5: Delete the queue — VERY IMPORTANT, don't forget! */
    if (msgctl(msqid, IPC_RMID, NULL) == -1) {
        perror("msgctl");
        exit(EXIT_FAILURE);
    }
    printf("Queue deleted successfully.\n");

    return 0;
}
Compile and run:
gcc minimal_msgq.c -o minimal_msgq && ./minimal_msgq

Expected output:
Queue created, ID = 65536
Message sent: 'Hello from sender!' (type=1)
Message received: 'Hello from sender!' (type=1)
Queue deleted successfully.

Code Example 2 — Multiple Message Types

This example sends 3 messages with different types, then reads them in a specific type order to show how type-based selection works.

/* typed_messages.c — Demonstrating message type selection */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct Msg {
    long mtype;
    char mtext[80];
};

int send_msg(int qid, long type, const char *text) {
    struct Msg m;
    m.mtype = type;
    strncpy(m.mtext, text, sizeof(m.mtext) - 1);
    m.mtext[sizeof(m.mtext)-1] = '\0';
    return msgsnd(qid, &m, sizeof(m.mtext), 0);
}

int main(void)
{
    int qid = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
    if (qid == -1) { perror("msgget"); exit(1); }

    /* Send three messages with different types — they go into queue in this order */
    send_msg(qid, 3, "I am type 3 — ERROR");
    send_msg(qid, 1, "I am type 1 — INFO");
    send_msg(qid, 2, "I am type 2 — WARNING");

    printf("Sent 3 messages. Queue has: type3, type1, type2\n\n");

    struct Msg rcv;

    /* Read type 1 first — even though it's second in the queue */
    msgrcv(qid, &rcv, sizeof(rcv.mtext), 1, 0);
    printf("Read with type=1: '%s'\n", rcv.mtext);

    /* Read type 2 */
    msgrcv(qid, &rcv, sizeof(rcv.mtext), 2, 0);
    printf("Read with type=2: '%s'\n", rcv.mtext);

    /* Read type 0 = first remaining (type 3) */
    msgrcv(qid, &rcv, sizeof(rcv.mtext), 0, 0);
    printf("Read with type=0: '%s'\n", rcv.mtext);

    msgctl(qid, IPC_RMID, NULL);
    return 0;
}
Expected output:
Sent 3 messages. Queue has: type3, type1, type2
Read with type=1: 'I am type 1 — INFO'
Read with type=2: 'I am type 2 — WARNING'
Read with type=0: 'I am type 3 — ERROR'

Notice: even though type=3 was sent first, it was read last because we asked for type=1 and type=2 first.

Interview Questions — Introduction & Concepts

Q1. What is a System V Message Queue? A kernel-maintained linked list of messages. Processes use msgget/msgsnd/msgrcv/msgctl system calls to create, send, receive, and control them. Each message has a type and a data payload.
Q2. How is a message queue different from a pipe? Pipes provide an undifferentiated byte stream; message queues are message-oriented (whole messages only). Pipes use file descriptors; queues use a queue ID. Queues support typed messages; pipes do not. Queue data persists in the kernel after the process exits; pipe data does not.
Q3. What is the significance of the message type (mtype)? mtype allows selective reading. A reader can request messages of a specific type (e.g., type=2) and the kernel will skip over messages of other types to deliver only the matching one. This enables priority-like filtering without multiple queues.
Q4. Can message queues be used with select() or poll()? No. Because they do not use file descriptors, System V message queues cannot be monitored with select, poll, or epoll. This is one of their key limitations compared to pipes and POSIX message queues.
Q5. What happens to a message queue when the process that created it exits? Nothing — the queue persists in the kernel. Unlike pipes, System V queues are not automatically removed. They must be explicitly deleted with msgctl(IPC_RMID) or the ipcrm command, or they will remain until the system reboots.
Q6. Name three IPC mechanisms that should be preferred over System V message queues in new code. POSIX message queues (mq_open), Unix domain sockets, and FIFOs (named pipes).
Q7. What does mtype=0 mean when calling msgrcv()? It means “receive the first message in the queue regardless of its type” — FIFO order with no type filtering.

Leave a Reply

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