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.
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. |
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.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 */
};
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.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:
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
2. Queue is FULL
3. msgsnd() returns IMMEDIATELY
4. Error: returns -1
5. errno = EAGAIN
2. A signal arrives
3. Signal handler runs
4. msgsnd() returns -1
5. errno = EINTR (NOT restarted)
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.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.
IPC_NOWAIT is defined in <sys/ipc.h>. You can combine multiple flags using bitwise OR: msgflg = IPC_NOWAIT | some_other_flag.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.
msgsnd() to a queue without write permission, the call fails with errno = EACCES.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.
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 */
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 */
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 */
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).
msgsnd(id, &msg, strlen(msg.mtext)+1, 0);
msgsnd(id, &msg, sizeof(msg), 0);
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);
These are common interview and exam questions for Linux system programming roles.
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.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.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.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.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.msgsnd() fails with errno = EACCES. The permissions are set when the queue is created using msgget().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.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.• 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
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.• 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/msgmaxOr with:
ipcs -lmsgsnd() 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
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.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.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.Learn how to selectively read messages by type, use priority queues, and handle all msgrcv() flags.
