Beej's Guide to Unix IPC — Chapter 7

Message Queues

System V typed messages with priority retrieval — like a mailbox where you can pick letters by category.

Prerequisites: FIFOs (Ch. 5) + ftok() keys. That's it.
10
Chapters
3+
Simulations
0
Assumed Knowledge

Chapter 0: Why Message Queues?

FIFOs let unrelated processes communicate, but they have a limitation: data is just a raw byte stream. There's no concept of discrete messages, no types, no priorities. If you write "ERROR:disk full" and then "INFO:backup ok", the reader gets a stream of bytes and has to figure out where one message ends and the next begins.

Message queues solve this. Each message is a discrete unit with a type field (a positive integer). The receiver can pull messages by type — "give me all the error messages first" — or just grab the next message regardless of type.

The mental model: Imagine a post office with numbered mailboxes. You can drop a letter into the general queue, or you can label it with a type number. The receiver can ask for "the next letter" (any type) or "give me all the type-2 letters." It's a FIFO with categories.

One more thing: System V message queues persist in the kernel until explicitly destroyed. Even if every process using the queue exits, the queue (and its messages) remain. Use ipcs to see them and ipcrm to delete them.

Message Queue vs FIFO

Click to send typed messages. Notice how you can retrieve by type, not just FIFO order.

What is the main advantage of message queues over FIFOs?

Chapter 1: ftok() — Generating Keys

Every System V IPC object (message queue, semaphore, shared memory) is identified by a key — a system-wide unique number. Two processes that want to use the same queue must agree on the same key.

You could just hardcode a number, but what if another unrelated program uses the same number? The solution is ftok():

c
#include <sys/ipc.h>

key_t key;
key = ftok("/home/beej/somefile", 'b');

ftok() takes two arguments: a path to an existing file that the process can read, and an arbitrary id character. It combines information about the file (like its inode number) with the id to generate a probably-unique key.

Key insight: Both the sender and receiver must call ftok() with the same path and id to get the same key. This is how unrelated processes agree on which queue to use — they both know the filename and the character.
Gotcha: If the file is deleted and recreated, its inode number may change, and ftok() will return a different key. Don't use temporary files as the path argument.
How do two unrelated processes agree on the same message queue key?

Chapter 2: msgget() — Creating or Connecting

Once you have a key, you create (or connect to) a message queue with msgget():

c
#include <sys/msg.h>

int msqid;
key_t key;

key = ftok("/home/beej/somefile", 'b');
msqid = msgget(key, 0666 | IPC_CREAT);

msgget() returns a message queue ID on success, or -1 on failure. The arguments:

ArgumentPurpose
keyThe system-wide unique key from ftok()
msgflgPermissions (like chmod) OR'd with IPC_CREAT to create if it doesn't exist

The 0666 sets rw-rw-rw- permissions. The IPC_CREAT flag means "create the queue if it doesn't exist; if it does, just connect to it." The receiver typically connects without IPC_CREAT, relying on the sender to create it first.

Note: Unlike pipes and FIFOs, message queues persist in the kernel. They survive process death. This is both powerful (messages wait for a receiver) and dangerous (forgotten queues waste resources). Always clean up.
What does IPC_CREAT do in the msgget() call?

Chapter 3: struct msgbuf — Message Format

Every message has the same structure: a long type field followed by the actual data. The template is in <sys/msg.h>:

c
struct msgbuf {
    long mtype;     /* message type, must be > 0 */
    char mtext[1];  /* message data */
};

But you're not stuck with a one-byte payload! You can define your own struct, as long as the first field is a long:

c
struct my_msgbuf {
    long mtype;            /* must be first, must be positive */
    char mtext[200];       /* the actual message data */
};

/* Or get fancy: */
struct pirate_msgbuf {
    long mtype;
    struct pirate_info {
        char name[30];
        char ship_type;
        int notoriety;
        int cruelty;
        int booty_value;
    } info;
};
Critical rule: The mtype field must be a positive number. It cannot be zero or negative. This is the field used for selective retrieval with msgrcv(). Think of it as a category label for the message.
What must always be the first field in a message buffer struct?

Chapter 4: msgsnd() — Sending Messages

To put a message onto the queue, you use msgsnd():

c
int msgsnd(int msqid, const void *msgp,
          size_t msgsz, int msgflg);
ArgumentPurpose
msqidQueue ID from msgget()
msgpPointer to your message struct
msgszSize of the data portion (NOT including mtype)
msgflgFlags (0 is fine for now)

The tricky part is msgsz. It's the size of the data only — you subtract the size of the long mtype field:

c
struct my_msgbuf buf;
buf.mtype = 1;
strcpy(buf.mtext, "Hello, queue!");

/* size = total struct - sizeof(long) */
msgsnd(msqid, &buf,
       sizeof(struct my_msgbuf) - sizeof(long), 0);
Key insight: You can also just use the string length as the size for simple text messages: msgsnd(msqid, &buf, strlen(buf.mtext)+1, 0). The +1 accounts for the null terminator.
What does the msgsz argument to msgsnd() represent?

Chapter 5: msgrcv() — Receiving Messages

The real magic happens on the receiving end. msgrcv() can pull messages selectively by type:

c
int msgrcv(int msqid, void *msgp, size_t msgsz,
           long msgtyp, int msgflg);

The msgtyp argument is the key to selective retrieval. Its behavior depends on its value:

msgtyp ValueEffect
0 (zero)Retrieve the next message, regardless of type (pure FIFO)
PositiveGet the next message with exactly that mtype
NegativeGet the first message whose mtype is ≤ |msgtyp|
The negative trick: If you send messages with types 1, 2, and 3, and call msgrcv() with msgtyp = -3, it retrieves the first message with mtype ≤ 3. Since lower types are checked first, this creates a priority system: type 1 = highest priority, type 3 = lowest.
c
struct my_msgbuf buf;

/* Get the next type-2 message */
msgrcv(msqid, &buf,
       sizeof(buf.mtext), 2, 0);

/* Get ANY next message (type 0 = any) */
msgrcv(msqid, &buf,
       sizeof(buf.mtext), 0, 0);
What does passing msgtyp = 0 to msgrcv() do?

Chapter 6: Destroying a Message Queue

Message queues don't disappear when processes exit. You must explicitly destroy them. There are two ways:

Command line: Use ipcs to list all IPC objects, then ipcrm -q <msqid> to remove a specific queue.
Programmatically: Call msgctl(msqid, IPC_RMID, NULL) to destroy the queue from your code.
c
#include <sys/msg.h>

/* Destroy the message queue */
msgctl(msqid, IPC_RMID, NULL);

The msgctl() function does many things, but IPC_RMID is the most common use: it removes the message queue immediately. Any blocked msgrcv() calls on other processes will return with an error.

Best practice: Always have the process that created the queue destroy it when done. In kirk.c, the queue is destroyed right after kirk finishes sending. If your program crashes, use ipcrm from the command line to clean up.
What happens to a System V message queue when all processes using it exit?

Chapter 7: kirk.c & spock.c — The Full Example

Beej's classic example uses two programs: kirk (the sender) adds messages to the queue, and spock (the receiver) pulls them out.

kirk.c (sender)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct my_msgbuf {
    long mtype;
    char mtext[200];
};

int main(void) {
    struct my_msgbuf buf;
    int msqid;
    key_t key;

    key = ftok("kirk.c", 'B');
    msqid = msgget(key, 0644 | IPC_CREAT);

    printf("Enter lines of text, ^D to quit:\n");
    buf.mtype = 1;

    while (fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL) {
        int len = strlen(buf.mtext);
        if (buf.mtext[len-1] == '\n')
            buf.mtext[len-1] = '\0';
        msgsnd(msqid, &buf, len+1, 0);
    }

    msgctl(msqid, IPC_RMID, NULL);
    return 0;
}
spock.c (receiver)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct my_msgbuf {
    long mtype;
    char mtext[200];
};

int main(void) {
    struct my_msgbuf buf;
    int msqid;
    key_t key;

    key = ftok("kirk.c", 'B');   /* same key as kirk */
    msqid = msgget(key, 0644);   /* no IPC_CREAT */

    printf("spock: ready to receive messages, captain.\n");
    for(;;) {
        msgrcv(msqid, &buf,
               sizeof(buf.mtext), 0, 0);
        printf("spock: \"%s\"\n", buf.mtext);
    }
    return 0;
}
How it works: kirk creates the queue and sends every line you type. spock connects to the same queue (same ftok() arguments) and loops forever, printing each message. When kirk exits (^D), it destroys the queue, and spock's msgrcv() will error out.
kirk (sender)
ftok("kirk.c",'B') → msgget(IPC_CREAT) → msgsnd()
↓ messages sit in the kernel queue
spock (receiver)
ftok("kirk.c",'B') → msgget() → msgrcv()
Why does spock use the same ftok() arguments as kirk?

Chapter 8: Message Queue Simulator

Watch typed messages flow through a System V message queue. Send messages with different types, then retrieve them — either in FIFO order (type 0) or selectively by type.

Typed Message Queue

Send messages with types 1-3, then receive. "Recv Any" gets the next message; "Recv Type N" gets the first message of that type.

Queue empty.
If a queue has messages with types [3, 1, 2, 1] and you call msgrcv() with msgtyp = -2, which message is retrieved?

Chapter 9: Beyond Message Queues

Message queues are powerful but have some quirks:

FeaturePipes/FIFOsMessage QueuesShared Memory
Data modelByte streamDiscrete messagesRaw memory
Message typesNoYes (mtype)N/A
PersistenceDie with processKernel-persistentKernel-persistent
SpeedFastMediumFastest
Needs sync?Built-inBuilt-inYes (semaphores)
Coming up: Semaphores for general-purpose synchronization, and shared memory segments for the fastest IPC of all — direct pointer access to shared data.

"spock: ready to receive messages, captain." — Beej

What is the main disadvantage of System V message queues compared to pipes?