What Is msgrcv()?
msgrcv() is the system call used to read a message from a System V message queue. When a message is read, it is automatically removed from the queue. This is different from reading a shared memory segment where data persists after reading.
What makes msgrcv() powerful is selective receive — you can choose which message to read based on its type. You don’t have to read messages in the order they were sent. This gives message queues an advantage over pipes, where you must always read bytes in FIFO (first-in, first-out) order.
For example, in a server that handles both urgent and normal requests, it can always read urgent messages first, regardless of when they were sent. This is not possible with pipes.
Here is the official function signature:
#include <sys/types.h> /* For portability */
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t maxmsgsz, long msgtyp, int msgflg);
/* Returns: number of bytes in mtext on success, -1 on error */
Every argument explained:
| Argument | Type | Meaning |
|---|---|---|
| msqid | int | Message queue identifier from msgget(). |
| msgp | void * | Pointer to a buffer where the received message will be stored. Must start with a long mtype field. |
| maxmsgsz | size_t | Maximum number of bytes that can fit in the mtext field of your buffer. Protects against buffer overflow. |
| msgtyp | long | Controls which message to receive (0 = any, >0 = specific type, <0 = priority queue). See details below. |
| msgflg | int | Flags: 0 (blocking), IPC_NOWAIT (non-blocking), MSG_NOERROR (truncate), MSG_EXCEPT (invert type match). |
mtext field. On error, returns -1 and sets errno. Note: the return value does NOT include the mtype field.This is the most important and conceptually rich part of msgrcv(). The msgtyp argument gives you three different ways to select which message to receive:
This is simple FIFO order — just like a pipe.
Skips all messages of different types. Blocks if no matching message exists.
Lower type number = higher priority.
This is the trickiest part. Let’s trace through a concrete example from the textbook. Suppose the message queue contains these messages in this order:
| Queue Position | mtype | mtext |
|---|---|---|
| 1 (oldest) | 300 | Message A |
| 2 | 100 | Message B |
| 3 | 200 | Message C |
| 4 | 400 | Message D |
| 5 (newest) | 100 | Message E |
This means: “give me the message with the lowest mtype that is ≤ 300”
Retrieval order:
Call 1 → mtype 100 (position 2, Message B) — lowest mtype ≤ 300
Call 2 → mtype 100 (position 5, Message E) — next lowest mtype ≤ 300
Call 3 → mtype 200 (position 3, Message C) — next lowest mtype ≤ 300
Call 4 → mtype 300 (position 1, Message A) — next lowest mtype ≤ 300
Call 5 → BLOCKS — only Message D (type 400) remains, and 400 > 300
msgtyp < 0, the queue behaves like a priority queue where lower mtype value = higher priority. This allows you to implement priority-based message processing without any additional data structures — the kernel handles the ordering for you.The msgflg argument to msgrcv() supports three flags. These can be ORed together:
By default, if no message matching msgtyp is in the queue, msgrcv() blocks the calling process until such a message arrives. IPC_NOWAIT changes this: instead of blocking, the call returns immediately with -1 and errno = ENOMSG.
If the message body (mtext) is larger than maxmsgsz bytes, the default behavior is to fail with errno = E2BIG and leave the message in the queue. When MSG_NOERROR is specified, the excess bytes are silently truncated and the (truncated) message IS removed from the queue. The truncated data is permanently lost.
This flag is Linux-specific (not in POSIX). It only has effect when msgtyp > 0. It inverts the usual type match — instead of receiving the first message whose mtype == msgtyp, it receives the first message whose mtype is NOT equal to msgtyp.
To use this flag, you must define _GNU_SOURCE before including <sys/msg.h>:
#define _GNU_SOURCE
#include <sys/msg.h>
msgrcv(id, &msg, maxsz, 100, MSG_EXCEPT) repeatedly would retrieve messages in the order: type 300 (pos 1), type 200 (pos 3), type 400 (pos 4) — then block. Messages of type 100 are excluded.| Flag | Effect | Error When Condition Not Met | Portability |
|---|---|---|---|
| 0 (no flag) | Block until matching message arrives | EINTR (signal) | All UNIX |
| IPC_NOWAIT | Return immediately if no match | ENOMSG | POSIX |
| MSG_NOERROR | Truncate if message too large | — (data silently lost) | POSIX |
| MSG_EXCEPT | Receive first message NOT matching msgtyp | ENOMSG (with IPC_NOWAIT) | Linux only |
When you call msgrcv(), you provide a maxmsgsz argument telling the kernel how many bytes your buffer can hold. If the queued message’s mtext is larger than this:
• errno = E2BIG
• Message STAYS in the queue
• No data is copied to your buffer
• You must use a larger buffer to read it
• Message IS removed from queue
• Only maxmsgsz bytes are copied
• Excess bytes are DISCARDED forever
• Returns maxmsgsz (truncated count)
Just like msgsnd(), a blocked msgrcv() call can be interrupted by a signal. When this happens, the call returns -1 with errno = EINTR, regardless of whether SA_RESTART was set in the signal handler.
This is consistent with msgsnd(). Both System V message queue calls are never auto-restarted. The programmer must always check for EINTR and retry.
msgrcv() fails with errno = EACCES.This example receives any message from the queue (msgtyp = 0) and prints its content. Must be run after the sender from the previous tutorial has placed messages.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#define MAX_MTEXT 256
struct my_msg {
long mtype;
char mtext[MAX_MTEXT];
};
int main(void)
{
int msqid;
struct my_msg msg;
key_t key;
ssize_t bytes_received;
/* Get the same key as the sender */
key = ftok("/tmp", 'A');
if (key == -1) { perror("ftok"); exit(EXIT_FAILURE); }
/* Access the existing queue — no IPC_CREAT since sender created it */
msqid = msgget(key, 0666);
if (msqid == -1) {
perror("msgget — make sure sender created the queue first");
exit(EXIT_FAILURE);
}
printf("Connected to queue ID: %d\n", msqid);
/*
* msgtyp = 0 means receive the FIRST message regardless of type.
* This is simple FIFO order, just like a pipe.
* msgflg = 0 means blocking — wait if queue is empty.
*/
printf("Waiting for message (msgtyp=0, blocking)...\n");
bytes_received = msgrcv(msqid, &msg, MAX_MTEXT, 0, 0);
if (bytes_received == -1) {
perror("msgrcv");
exit(EXIT_FAILURE);
}
printf("Received %zd bytes\n", bytes_received);
printf(" mtype = %ld\n", msg.mtype);
printf(" mtext = \"%s\"\n", msg.mtext);
return 0;
}
/* Compile: gcc -o basic_recv basic_recv.c
Run sender first, then: ./basic_recv */
This example demonstrates all three msgtyp behaviors in one program — receiving specific types, any type, and priority-based reception.
#define _GNU_SOURCE /* Required for MSG_EXCEPT */
#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 128
struct my_msg {
long mtype;
char mtext[MAX_MTEXT];
};
/* Helper: send a message */
void send_msg(int msqid, long type, const char *text)
{
struct my_msg m;
m.mtype = type;
strncpy(m.mtext, text, MAX_MTEXT - 1);
if (msgsnd(msqid, &m, strlen(m.mtext) + 1, 0) == -1)
perror("msgsnd");
else
printf(" [SENT] type=%ld text=\"%s\"\n", type, text);
}
/* Helper: receive a message with given msgtyp and flags */
void recv_msg(int msqid, long msgtyp, int flags, const char *label)
{
struct my_msg m;
ssize_t n;
n = msgrcv(msqid, &m, MAX_MTEXT, msgtyp, flags | IPC_NOWAIT);
if (n == -1) {
if (errno == ENOMSG)
printf(" [%s] No matching message (ENOMSG)\n", label);
else
perror("msgrcv");
} else {
printf(" [%s] Got: type=%ld text=\"%s\" (%zd bytes)\n",
label, m.mtype, m.mtext, n);
}
}
int main(void)
{
int msqid;
key_t key;
key = ftok("/tmp", 'D');
if (key == -1) { perror("ftok"); exit(EXIT_FAILURE); }
msqid = msgget(key, IPC_CREAT | 0666);
if (msqid == -1) { perror("msgget"); exit(EXIT_FAILURE); }
printf("=== Populating queue with messages ===\n");
send_msg(msqid, 300, "Priority-LOW");
send_msg(msqid, 100, "Priority-HIGH (first)");
send_msg(msqid, 200, "Priority-MED");
send_msg(msqid, 400, "Priority-VERY-LOW");
send_msg(msqid, 100, "Priority-HIGH (second)");
printf("\n=== Test 1: msgtyp=0 (receive first in queue, FIFO) ===\n");
recv_msg(msqid, 0, 0, "FIFO");
printf("\n=== Rebuild queue ===\n");
send_msg(msqid, 300, "Priority-LOW");
send_msg(msqid, 100, "Priority-HIGH (A)");
send_msg(msqid, 200, "Priority-MED");
send_msg(msqid, 400, "Priority-VERY-LOW");
send_msg(msqid, 100, "Priority-HIGH (B)");
printf("\n=== Test 2: msgtyp=100 (receive only type 100) ===\n");
recv_msg(msqid, 100, 0, "TYPE=100");
recv_msg(msqid, 100, 0, "TYPE=100");
recv_msg(msqid, 100, 0, "TYPE=100"); /* Should get ENOMSG */
printf("\n=== Test 3: msgtyp=-300 (priority queue, lowest type ≤ 300) ===\n");
/* Remaining: type 300, 200, 400 */
recv_msg(msqid, -300, 0, "PRIO -300 #1");
recv_msg(msqid, -300, 0, "PRIO -300 #2");
recv_msg(msqid, -300, 0, "PRIO -300 #3"); /* 400 > 300, so ENOMSG */
printf("\n=== Test 4: MSG_EXCEPT with msgtyp=400 (all except type 400) ===\n");
recv_msg(msqid, 400, MSG_EXCEPT, "EXCEPT-400");
printf("\n=== Cleanup ===\n");
msgctl(msqid, IPC_RMID, NULL);
printf("Queue removed.\n");
return 0;
}
/* Compile: gcc -o selective_recv selective_recv.c
Run: ./selective_recv */
This example sends a large message and demonstrates the difference between the default behavior (E2BIG, message stays in queue) and MSG_NOERROR (truncate and remove).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
/* Sender uses a large buffer */
#define SEND_SIZE 200
/* Receiver uses a small buffer — intentionally smaller */
#define RECV_SIZE 50
struct large_msg {
long mtype;
char mtext[SEND_SIZE];
};
struct small_buf {
long mtype;
char mtext[RECV_SIZE];
};
int main(void)
{
int msqid;
key_t key;
struct large_msg sm;
struct small_buf rb;
ssize_t n;
key = ftok("/tmp", 'E');
if (key == -1) { perror("ftok"); exit(EXIT_FAILURE); }
msqid = msgget(key, IPC_CREAT | 0666);
if (msqid == -1) { perror("msgget"); exit(EXIT_FAILURE); }
/* Send a message larger than our receive buffer */
sm.mtype = 1;
memset(sm.mtext, 'X', SEND_SIZE - 1);
sm.mtext[SEND_SIZE - 1] = '\0';
printf("Sending %d-byte message: \"%.*s...\"\n",
SEND_SIZE, 20, sm.mtext);
if (msgsnd(msqid, &sm, SEND_SIZE, 0) == -1) {
perror("msgsnd"); exit(EXIT_FAILURE);
}
/* --- Attempt 1: Default (no MSG_NOERROR) --- */
printf("\n--- Receive attempt 1: small buffer (%d bytes), no MSG_NOERROR ---\n",
RECV_SIZE);
n = msgrcv(msqid, &rb, RECV_SIZE, 0, IPC_NOWAIT);
if (n == -1) {
if (errno == E2BIG) {
printf("ERROR: E2BIG — message too large for buffer!\n");
printf("Message is still in the queue (not removed).\n");
} else {
perror("msgrcv");
}
}
/* --- Attempt 2: With MSG_NOERROR — truncate and remove --- */
printf("\n--- Receive attempt 2: same small buffer, WITH MSG_NOERROR ---\n");
n = msgrcv(msqid, &rb, RECV_SIZE, 0, IPC_NOWAIT | MSG_NOERROR);
if (n == -1) {
perror("msgrcv");
} else {
printf("Success: received %zd bytes (truncated from %d)\n", n, SEND_SIZE);
printf("mtype = %ld\n", rb.mtype);
printf("mtext (first %d bytes) = \"%.*s\"\n",
RECV_SIZE, RECV_SIZE - 1, rb.mtext);
printf("Remaining %d bytes were DISCARDED.\n",
SEND_SIZE - RECV_SIZE);
}
/* Verify queue is now empty */
n = msgrcv(msqid, &rb, RECV_SIZE, 0, IPC_NOWAIT);
if (n == -1 && errno == ENOMSG)
printf("\nQueue is now empty — message was removed by MSG_NOERROR call.\n");
msgctl(msqid, IPC_RMID, NULL);
return 0;
}
/* Compile: gcc -o noerror_recv noerror_recv.c
Run: ./noerror_recv */
These cover the most frequently tested concepts in interviews and university exams.
msgrcv() returns the number of bytes copied into the mtext field of the message buffer on success. It returns -1 on error. Importantly, the return value does NOT include the size of the mtype field — it counts only the message body bytes. This is symmetric with msgsz in msgsnd().msgtyp > 0: Receive the first message whose mtype field equals msgtyp. Messages of other types are skipped.
msgtyp < 0: Priority queue mode. Receive the message with the lowest mtype value that is ≤ |msgtyp|. If multiple messages have the same lowest type, the earliest one is returned. This allows implementing message priorities where lower type number = higher priority.
errno = ENOMSG (“no message of desired type”). This is surprising because the analogous non-blocking operation — msgsnd() with a full queue and IPC_NOWAIT — returns EAGAIN. Similarly, non-blocking reads from pipes and FIFOs return EAGAIN. Using ENOMSG for msgrcv() is historical Unix behavior preserved for compatibility by SUSv3, not a logical design choice.E2BIG occurs when the message body (mtext) is larger than the receiver’s maxmsgsz buffer. By default (without MSG_NOERROR), msgrcv() fails with E2BIG and the message remains in the queue — nothing is removed or copied.
With MSG_NOERROR, the message IS removed from the queue, but only maxmsgsz bytes are copied to the buffer. The excess bytes beyond maxmsgsz are permanently discarded. The call succeeds and returns maxmsgsz.
MSG_EXCEPT is a Linux-specific flag (not in POSIX). It inverts the type matching when msgtyp > 0: instead of receiving the first message with mtype == msgtyp, it receives the first message with mtype ≠ msgtyp. To use it, you must define _GNU_SOURCE before including <sys/msg.h>. It has no effect when msgtyp == 0 or msgtyp < 0.msgrcv() with a different msgtyp value. For example, process A only reads type 1 messages, process B only reads type 2 messages. The sender assigns the appropriate mtype when calling msgsnd(). A useful pattern is to use the receiver’s process ID as the message type — the receiver calls msgrcv(msqid, &msg, maxsz, getpid(), 0) to receive only messages addressed specifically to it.msgtyp > 0 or msgtyp < 0, allows selective reading. Messages can be retrieved out of order — the receiver can skip messages of the wrong type and read a later-arriving message first. This is one of the key advantages of message queues over pipes for inter-process communication.msgrcv() fails with errno = EACCES. Permissions are set during queue creation with msgget() using standard Unix-style permission bits (owner/group/other, read/write).msgsnd(), a blocked msgrcv() that is interrupted by a signal always fails with errno = EINTR, regardless of whether the signal handler was set up with SA_RESTART. Both System V message queue system calls are excluded from auto-restart. The programmer must explicitly check for EINTR and retry in a loop.Call 1: Lowest mtype ≤ 300 is 100. First occurrence at pos2 → returns type 100, Message B.
Call 2: Lowest mtype ≤ 300 is still 100. Next at pos5 → returns type 100, Message E.
Call 3: Lowest mtype ≤ 300 is now 200 (pos3) → returns type 200, Message C.
Call 4: Lowest mtype ≤ 300 is now 300 (pos1) → returns type 300, Message A.
Call 5: Only type 400 remains. 400 > 300 → BLOCKS (or ENOMSG with IPC_NOWAIT).
msgctl(msqid, IPC_RMID, NULL)) while a process is blocked in msgrcv() waiting for a message, the blocked call wakes up immediately and returns -1 with errno = EIDRM (identifier removed). The process should handle this error and not attempt further operations on the queue.maxmsgsz specifies the maximum number of bytes that the receiver’s mtext buffer can hold. It protects against buffer overflow — if the queued message is larger, the kernel does NOT copy it into your buffer. Instead it either returns E2BIG (default) or truncates with MSG_NOERROR. This is a safety mechanism: unlike pipes where raw bytes flow in, message queues give you a length check before any copy happens.mtype = server_pid or a command code. In the reply, the server sends back with mtype = client_pid (obtained from the request). The client then calls msgrcv(msqid, &reply, maxsz, getpid(), 0) to receive only replies addressed to it. Multiple clients can share the same queue without receiving each other’s replies, because each client filters by its own PID as the type.msgrcv() with type-based selection; POSIX uses mq_receive() with priority-based selection (highest priority first).POSIX queues support
mq_notify() for asynchronous notification when a message arrives — no need to block or poll. System V has no equivalent.POSIX queues work with
select()/poll()/epoll() because they expose a file descriptor. System V queues do not.System V queues persist until explicitly deleted or system reboot. POSIX queues have similar persistence but are identified by name (like files) and can be unlinked.
msgrcv(msqid, &reply, maxsz, getpid(), 0). The server includes the client’s PID in the reply mtype field. This way, each client only wakes up when its own reply arrives — no client accidentally reads another client’s reply. This is a classic and elegant use of mtype for process-level addressing using a single shared queue, avoiding the need for per-client queues.msgget(key, IPC_CREAT | perms) — creates or opens queue, returns msqid.Send:
msgsnd(msqid, &msg, msgsz, flags) — places message on queue.Receive:
msgrcv(msqid, &msg, maxsz, msgtyp, flags) — reads and removes message.Control/Inspect:
msgctl(msqid, IPC_STAT, &msqid_ds) — get queue info.Delete:
msgctl(msqid, IPC_RMID, NULL) — remove queue permanently.Queues persist across process restarts until explicitly deleted or system reboot. Use
ipcs -q and ipcrm -q msqid for manual management.With a message queue,
msgrcv() on an empty queue (or a queue with no matching type) blocks until a matching message arrives. With IPC_NOWAIT, it returns immediately with ENOMSG. The key difference: message queues have no EOF concept — they can remain empty indefinitely without signaling “end of data” to the reader.You now understand how to send and receive messages using System V IPC with full theory, examples, and interview readiness.
