System V Message Queues – sending Messages with msgsnd()

 

System V Message Queues
Part 2 — Sending Messages with msgsnd() | TLPI Chapter 46
46.2.1
Section
msgsnd()
System Call
3
Code Examples
15+
Interview Q&A

Key Concepts in This Tutorial
msgsnd() mtype mtext msgsz msgflg IPC_NOWAIT EAGAIN EINTR Message Structure Blocking Behavior Signal Interruption Write Permission

What Is msgsnd()?

msgsnd() is a Linux system call used to place a message onto a System V message queue. Think of a message queue like a post box — a sender drops a letter (message) into the box, and a receiver picks it up later. The sender and receiver do not need to be running at the same time. This is one key advantage of message queues over pipes.

To use msgsnd(), a process must already have a valid message queue identifier (msqid), which it gets from msgget(). However, as a shortcut, a process can also be given the msqid directly (for example, as a command-line argument) without calling msgget() itself.

The msgsnd() System Call — Signature

Here is the official function signature of msgsnd():

#include <sys/types.h>   /* For portability */
#include <sys/msg.h>

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

/* Returns: 0 on success, -1 on error */

Let’s understand every argument one by one:

Argument Type Meaning
msqid int The message queue identifier — returned by msgget(). Tells the kernel which queue to send the message to.
msgp const void * Pointer to the message buffer. This buffer must start with a long mtype field followed by the message body (mtext).
msgsz size_t The number of bytes in the mtext field only. Does NOT include the size of the mtype field.
msgflg int Control flags. Normally 0, or IPC_NOWAIT for non-blocking behavior.
Return Value: Returns 0 on success. Returns -1 on error and sets errno. Note: unlike write(), it does NOT return the number of bytes written — because there is no concept of partial write in message queues.

The Message Structure — How a Message Looks in Memory

A message in System V IPC is NOT just raw bytes. It must be structured as a C struct with a specific layout. The kernel requires this structure to carry a message type as the first field.

The standard template defined in <sys/msg.h> is:

struct msgbuf {
    long mtype;    /* Message type — MUST be > 0 */
    char mtext[1]; /* Message body — variable length */
};

In practice, you define your own structure with the same layout but a larger (or differently typed) body:

#define MAX_MTEXT 1024

struct my_msg {
    long mtype;             /* Message type — must be > 0 */
    char mtext[MAX_MTEXT];  /* Message body — your actual data */
};

Memory Layout of a System V Message
mtype
long (4 or 8 bytes)
Must be > 0
mtext[ ]
Variable length (up to MSGMAX bytes)
Your actual message data
← msgsz passed to msgsnd() covers ONLY the mtext[] part, not mtype →
Important: The mtype field MUST be greater than 0. Sending a message with mtype = 0 or a negative value is an error (errno = EINVAL). This is a very common mistake in exams and interviews.

Blocking Behavior of msgsnd() — What Happens When Queue Is Full?

A message queue has a maximum capacity — measured both in total bytes and total number of messages. These limits are controlled by kernel parameters (MSGMNB for max bytes per queue, MSGMNI for max number of queues system-wide).

When a process calls msgsnd() and the queue is already full (no space for the new message), the behavior depends on the msgflg argument:

msgsnd() — Blocking vs Non-Blocking Flow
Default (msgflg = 0)
1. Process calls msgsnd()
2. Queue is FULL
3. Process is put to SLEEP (blocked)
4. Kernel wakes it when space is freed
5. Message is placed, returns 0
IPC_NOWAIT flag
1. Process calls msgsnd()
2. Queue is FULL
3. msgsnd() returns IMMEDIATELY
4. Error: returns -1
5. errno = EAGAIN
Signal Interrupt (blocking only)
1. Process is blocked in msgsnd()
2. A signal arrives
3. Signal handler runs
4. msgsnd() returns -1
5. errno = EINTR (NOT restarted)
SA_RESTART Does NOT Help Here: Normally when a signal interrupts a blocking system call, setting the SA_RESTART flag in the signal handler causes the system call to be automatically restarted. However, msgsnd() is one of the few system calls that is never automatically restarted, even if SA_RESTART is set. The caller must check for EINTR and retry manually.

The IPC_NOWAIT Flag — Non-Blocking Send

When you do not want your process to sleep waiting for queue space, you pass IPC_NOWAIT as the msgflg argument. This makes msgsnd() behave like a non-blocking write.

The name IPC_NOWAIT is analogous to O_NONBLOCK used with pipes and FIFOs, but the error code differs. With pipes, a non-blocking write that cannot proceed returns EAGAIN, and this is also the case with msgsnd() + IPC_NOWAIT.

Flag Value: IPC_NOWAIT is defined in <sys/ipc.h>. You can combine multiple flags using bitwise OR: msgflg = IPC_NOWAIT | some_other_flag.

Permissions Required to Send a Message

Sending a message to a queue requires write permission on the queue. Message queue permissions work like file permissions — they have owner, group, and other permission bits.

These permissions are set when the queue is created with msgget() by providing appropriate flags in the msgflg argument of msgget(). For example, 0666 gives read+write permission to owner, group, and others.

Access Check: If a process tries to msgsnd() to a queue without write permission, the call fails with errno = EACCES.

No Partial Writes — An Important Difference from write()

When you call write() on a file or pipe, it is possible for the system to write only part of the requested bytes and return the count of bytes written. You must then loop and write the rest.

With msgsnd(), this does NOT happen. A message is always sent as an atomic unit — either the entire message is placed on the queue, or nothing is placed (and an error is returned). This is why msgsnd() returns only 0 on success, not the number of bytes written. There is no need to loop.

write() vs msgsnd() — Atomicity Comparison
write() — Partial Write Possible
• Returns bytes actually written
• May write only part of buffer
• Caller must loop to write rest
• Not atomic for large writes
msgsnd() — Always Atomic
• Returns only 0 or -1
• Either full message OR nothing
• No looping needed
• Entire message as one unit

Coding Example 1 — Basic Message Send

This example creates a message queue, sends a simple text message of type 1, and prints the result. This is the minimal working example.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

/* Define our message structure */
#define MAX_MTEXT 256

struct my_msg {
    long mtype;           /* Message type — must be > 0 */
    char mtext[MAX_MTEXT]; /* Message body */
};

int main(void)
{
    int msqid;
    struct my_msg msg;
    key_t key;

    /* Step 1: Generate a unique key for the queue */
    key = ftok("/tmp", 'A');
    if (key == -1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }

    /* Step 2: Create or open the message queue */
    /* IPC_CREAT | 0666 — create if not exists, rw for all */
    msqid = msgget(key, IPC_CREAT | 0666);
    if (msqid == -1) {
        perror("msgget");
        exit(EXIT_FAILURE);
    }
    printf("Message queue created. ID = %d\n", msqid);

    /* Step 3: Build the message */
    msg.mtype = 1;   /* Type must be > 0 */
    strncpy(msg.mtext, "Hello from sender process!", MAX_MTEXT - 1);
    msg.mtext[MAX_MTEXT - 1] = '\0';

    /* Step 4: Send the message */
    /* msgsz = strlen + 1 for null terminator, msgflg = 0 (blocking) */
    if (msgsnd(msqid, &msg, strlen(msg.mtext) + 1, 0) == -1) {
        perror("msgsnd");
        exit(EXIT_FAILURE);
    }

    printf("Message sent successfully: \"%s\" (type=%ld)\n",
           msg.mtext, msg.mtype);

    return 0;
}

/* Compile: gcc -o basic_send basic_send.c
   Run:    ./basic_send
   Check queue: ipcs -q */

Coding Example 2 — Non-Blocking Send with IPC_NOWAIT

This example demonstrates how to use IPC_NOWAIT to avoid blocking when the queue is full. It sends multiple messages and handles the EAGAIN error gracefully.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

#define MAX_MTEXT 64

struct task_msg {
    long mtype;
    char mtext[MAX_MTEXT];
};

int main(void)
{
    int msqid;
    struct task_msg msg;
    key_t key;
    int i, sent = 0, failed = 0;

    key = ftok("/tmp", 'B');
    if (key == -1) { perror("ftok"); exit(EXIT_FAILURE); }

    msqid = msgget(key, IPC_CREAT | 0666);
    if (msqid == -1) { perror("msgget"); exit(EXIT_FAILURE); }

    printf("Queue ID: %d\n", msqid);
    printf("Sending messages with IPC_NOWAIT...\n");

    for (i = 1; i <= 20; i++) {
        msg.mtype = (i % 3) + 1;  /* Types 1, 2, 3 in rotation */
        snprintf(msg.mtext, MAX_MTEXT, "Task-%d", i);

        if (msgsnd(msqid, &msg, strlen(msg.mtext) + 1, IPC_NOWAIT) == -1) {
            if (errno == EAGAIN) {
                /* Queue is full — don't block, just skip */
                printf("  [SKIP] Queue full, cannot send Task-%d (EAGAIN)\n", i);
                failed++;
            } else {
                perror("msgsnd");
                break;
            }
        } else {
            printf("  [OK]   Sent Task-%d (type=%ld)\n", i, msg.mtype);
            sent++;
        }
    }

    printf("\nSummary: %d sent, %d skipped due to full queue\n", sent, failed);

    /* Clean up the queue */
    if (msgctl(msqid, IPC_RMID, NULL) == -1)
        perror("msgctl IPC_RMID");

    return 0;
}

/* Compile: gcc -o nowait_send nowait_send.c
   Run:    ./nowait_send */

Coding Example 3 — Handling EINTR When Signal Interrupts msgsnd()

Since msgsnd() is never automatically restarted after a signal, we must manually retry on EINTR. This example shows a robust wrapper function that does exactly that.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

#define MAX_MTEXT 128

struct my_msg {
    long mtype;
    char mtext[MAX_MTEXT];
};

/* Signal handler — just sets a flag */
static volatile sig_atomic_t got_signal = 0;

void sig_handler(int sig)
{
    got_signal = 1;
    printf("\n[Signal received: %d]\n", sig);
}

/*
 * msgsnd_robust() — wraps msgsnd() with EINTR retry logic.
 * msgsnd() is NOT auto-restarted even with SA_RESTART set.
 * So we must retry manually when interrupted by a signal.
 */
int msgsnd_robust(int msqid, const void *msgp, size_t msgsz, int msgflg)
{
    int ret;
    while (1) {
        ret = msgsnd(msqid, msgp, msgsz, msgflg);
        if (ret == -1 && errno == EINTR) {
            /* Signal interrupted us — check our flag and retry */
            printf("[msgsnd_robust] Interrupted by signal, retrying...\n");
            if (got_signal) {
                printf("[msgsnd_robust] Signal was handled, continuing\n");
                got_signal = 0;
            }
            /* Loop again to retry */
            continue;
        }
        break; /* Success or a real error */
    }
    return ret;
}

int main(void)
{
    int msqid;
    struct my_msg msg;
    key_t key;
    struct sigaction sa;

    /* Install signal handler for SIGINT (Ctrl+C) */
    sa.sa_handler = sig_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0; /* No SA_RESTART — msgsnd won't restart anyway */
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    key = ftok("/tmp", 'C');
    if (key == -1) { perror("ftok"); exit(EXIT_FAILURE); }

    msqid = msgget(key, IPC_CREAT | 0666);
    if (msqid == -1) { perror("msgget"); exit(EXIT_FAILURE); }

    msg.mtype = 5;
    strncpy(msg.mtext, "Important message — retry on EINTR", MAX_MTEXT - 1);

    printf("Sending message (try Ctrl+C to send SIGINT)...\n");

    /* Use our robust wrapper */
    if (msgsnd_robust(msqid, &msg, strlen(msg.mtext) + 1, 0) == -1) {
        perror("msgsnd_robust");
    } else {
        printf("Message sent successfully!\n");
    }

    /* Cleanup */
    msgctl(msqid, IPC_RMID, NULL);
    return 0;
}

/* Compile: gcc -o eintr_send eintr_send.c
   Run:    ./eintr_send
   Press Ctrl+C quickly after starting to test EINTR handling */

Deep Theory — How to Calculate msgsz Correctly

The msgsz argument is a critical parameter and many programmers get it wrong. Here is the exact rule:

msgsz = number of bytes in the mtext field ONLY

It does NOT include the size of the mtype field (which is a long).

Correct vs Wrong msgsz Calculation
✓ CORRECT


msgsnd(id, &msg, strlen(msg.mtext)+1, 0);

Passes only the body size (string + null terminator)
✗ WRONG


msgsnd(id, &msg, sizeof(msg), 0);

Passes entire struct size including mtype — incorrect!
If sending non-string data (struct or fixed buffer), use: sizeof(msg.mtext) or the exact data size.

However, if your mtext is a fixed-size struct (not a string), you use sizeof(msg.mtext):

struct sensor_data {
    long mtype;
    struct {
        int temperature;
        int pressure;
        float voltage;
    } mtext;
};

struct sensor_data m;
m.mtype = 2;
m.mtext.temperature = 35;
m.mtext.pressure = 1013;
m.mtext.voltage = 3.3f;

/* msgsz = sizeof the mtext struct, NOT sizeof the whole message */
msgsnd(msqid, &m, sizeof(m.mtext), 0);

🎓 Interview Questions — msgsnd() and Sending Messages

These are common interview and exam questions for Linux system programming roles.

Q1. What is the return value of msgsnd() on success and why is it not the number of bytes written?
msgsnd() returns 0 on success, not the number of bytes written. This is because message queues do not support partial writes. A message is either sent completely or not at all (atomic operation). Since you always know the message was fully sent if the return value is 0, there is no need to report byte count. Compare this with write() which CAN do partial writes and therefore must return the byte count.
Q2. What value must the mtype field have, and what happens if it is 0 or negative?
The mtype field must be greater than 0 (strictly positive). If mtype = 0 or a negative value, msgsnd() fails with errno = EINVAL. This constraint exists because the msgrcv() system call uses special meanings for msgtyp = 0 (receive any message) and msgtyp < 0 (priority queue behavior), so a message cannot itself have type 0 or negative.
Q3. What is IPC_NOWAIT and what error does msgsnd() return when the queue is full and this flag is used?
IPC_NOWAIT makes msgsnd() non-blocking. Normally when the queue is full, msgsnd() blocks the calling process until space becomes available. With IPC_NOWAIT, it returns immediately with -1 and errno = EAGAIN instead of blocking. This is useful when the sender cannot afford to wait — for example, in real-time systems or event loops.
Q4. Does SA_RESTART cause msgsnd() to be automatically restarted after a signal interruption?
No. This is a critical distinction. msgsnd() is one of the few Linux system calls that is never automatically restarted after a signal, even if the signal handler was installed with the SA_RESTART flag. When a signal interrupts a blocked msgsnd(), it always returns -1 with errno = EINTR. The programmer must handle EINTR explicitly and retry the call manually.
Q5. What does the msgsz argument to msgsnd() represent? What is a common mistake?
msgsz specifies the number of bytes in the mtext field only. It does NOT include the mtype field (a long). A very common mistake is passing sizeof(msg) which includes both mtype and mtext. The correct approach is strlen(msg.mtext) + 1 for strings, or sizeof(msg.mtext) for fixed-size binary data.
Q6. What permission is required to call msgsnd() on a queue?
Write permission on the message queue. Message queues have a permission model similar to files (owner/group/other with read/write bits). If the process lacks write permission, msgsnd() fails with errno = EACCES. The permissions are set when the queue is created using msgget().
Q7. Why does a process not always need to call msgget() before using msgsnd()?
msgget() is only needed to obtain the message queue identifier (msqid). If the msqid is already known (e.g., passed as a command-line argument, shared via a file, or obtained through some other inter-process communication), the process can directly call msgsnd() with that msqid. The kernel does not require that a process “owns” or “opened” the queue — it only checks permissions.
Q8. Compare msgsnd() with write() in terms of atomicity and partial operations.
write() can perform partial writes — it may write fewer bytes than requested and return the count of bytes actually written. The caller must loop to write the remaining bytes. msgsnd() is always atomic with respect to the message — either the entire message is enqueued or nothing is enqueued. There is no partial send. This is why msgsnd() returns only 0 (success) or -1 (error), not a byte count.
Q9. What are the conditions under which msgsnd() can fail? List all important errno values.
Key failure conditions:
EINVAL — msqid invalid, or mtype ≤ 0, or msgsz negative/too large
EACCES — caller lacks write permission on the queue
EAGAIN — queue full and IPC_NOWAIT was specified
EINTR — blocked call was interrupted by a signal
EIDRM — the message queue was deleted while the process was blocked waiting
EFAULT — msgp points to an invalid memory address
ENOMEM — kernel memory exhausted
Q10. What happens to a process blocked in msgsnd() if the message queue is deleted by another process?
If another process deletes the message queue (using msgctl(msqid, IPC_RMID, NULL)) while the first process is blocked in msgsnd(), the blocked msgsnd() call wakes up and fails with errno = EIDRM (identifier removed). The process must handle this error case.
Q11. What kernel limits control message queue capacity, and how can you view them?
Key kernel limits for message queues:
MSGMNB — maximum bytes allowed in a single queue (default 16384 bytes)
MSGMNI — maximum number of queues system-wide
MSGMAX — maximum size of a single message body (mtext)
These can be viewed with: cat /proc/sys/kernel/msgmnb, cat /proc/sys/kernel/msgmni, cat /proc/sys/kernel/msgmax
Or with: ipcs -l
Q12. How is a System V message queue different from a POSIX message queue for sending messages?
System V uses msgsnd() while POSIX uses mq_send(). Key differences:
• System V uses integer identifiers (msqid); POSIX uses file-descriptor-like handles (mqd_t)
• System V messages have a type (long mtype); POSIX messages have a priority (unsigned int)
• POSIX queues support mq_notify() for asynchronous notification; System V has no equivalent
• POSIX queues are better integrated with file I/O (select/poll/epoll capable); System V are not
• System V is older (1980s UNIX); POSIX is newer and considered cleaner API
Q13. Can two processes send messages to the same queue simultaneously? Is it safe?
Yes, and it is safe. The kernel serializes concurrent msgsnd() calls internally using locking mechanisms. Each message is placed atomically. There is no risk of two messages being interleaved or corrupted due to concurrent sends. However, the order in which concurrent senders succeed depends on scheduling.
Q14. What is the purpose of ftok() and how is it used before msgsnd()?
ftok() converts a filesystem path and a project identifier byte into a System V IPC key (key_t). This key is used with msgget() to either create a new message queue or access an existing one. Both sender and receiver must use the same key to communicate via the same queue. The key is computed as: key = ftok("/some/path", 'X'). The path must exist on disk.
Q15. In a producer-consumer scenario using message queues, what precautions must the producer take regarding queue full conditions?
The producer must handle two scenarios:
1. Blocking (default): If the producer can afford to wait, it calls msgsnd() without IPC_NOWAIT. It will block until the consumer reads messages and frees space. But it must also handle EINTR (signal interruption) by retrying.
2. Non-blocking: If the producer cannot block, it uses IPC_NOWAIT and handles EAGAIN — typically by dropping the message, buffering it locally, or signaling an overflow condition. The producer must also handle EIDRM in case the queue is removed unexpectedly.

Next: Receiving Messages with msgrcv()

Learn how to selectively read messages by type, use priority queues, and handle all msgrcv() flags.

Next Tutorial → Back to EmbeddedPathashala

Leave a Reply

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