Without notification, a process that wants to receive messages from a POSIX message queue has two choices: block on mq_receive() (which ties up a thread) or poll repeatedly (wasteful CPU usage). Both approaches are inefficient when messages arrive infrequently.
mq_notify() solves this. You register once, then go about other work. The kernel notifies you asynchronously the moment a message arrives on a previously empty queue — either via a signal or by spawning a thread.
This is the same philosophy as epoll for file descriptors, but for message queues.
#include <mqueue.h>
int mq_notify(mqd_t mqdes, const struct sigevent *notification);
/* Returns: 0 on success, -1 on error */
mqdes is the message queue descriptor (returned by mq_open()). notification is a pointer to a struct sigevent that describes how to notify the process. Passing NULL removes any existing registration.
| Rule | Details | Why It Matters |
|---|---|---|
| One process at a time | Only ONE process can be registered for notification on a given queue at any moment | Trying to register when another is already registered fails with EBUSY |
| Auto-deregistration | Registration is automatically removed after ONE notification is delivered | You MUST call mq_notify() again after each notification to keep receiving them |
| Blocked receiver wins | If another process is blocked in mq_receive(), it gets the message instead |
Notification is only for queues that were empty — a blocked receiver is prioritized |
| Trigger: empty → nonempty | Notification fires only when queue transitions from empty to having at least one message | If the queue already has messages when you register, no notification is sent |
| NULL deregisters | Passing NULL as the second argument cancels any existing registration |
Useful for cleanup before exiting |
mq_notify()
process for queue
other work
(queue was empty)
process (deregisters)
and re-registers
This structure tells the kernel how to notify the process. You fill in the relevant fields and pass a pointer to mq_notify().
/* Defined in <signal.h> */
struct sigevent {
int sigev_notify; /* Notification method: SIGEV_NONE,
SIGEV_SIGNAL, or SIGEV_THREAD */
int sigev_signo; /* Signal number (used with SIGEV_SIGNAL) */
union sigval sigev_value; /* Data passed to signal handler or
thread function */
void (*sigev_notify_function)(union sigval);
/* Thread function (used with SIGEV_THREAD) */
void *sigev_notify_attributes; /* Thread attributes — cast to pthread_attr_t*
or pass NULL for defaults */
};
union sigval {
int sival_int; /* Integer value — use for simple numeric data */
void *sival_ptr; /* Pointer value — use to pass a struct/object */
};
You choose one member. For thread notification, sival_ptr is commonly used to pass a pointer to the message queue descriptor or a context struct. For signal notification with realtime signals, this value is accessible in the signal handler via siginfo_t.si_value.
| Value | What Happens | Fields Used |
|---|---|---|
SIGEV_NONE |
Process is registered but kernel does nothing when a message arrives. The registration is still consumed (removed) on message arrival. | None — only sigev_notify |
SIGEV_SIGNAL |
Kernel sends the signal specified in sigev_signo to the process. If it is a realtime signal, the value in sigev_value is also delivered. |
sigev_signo, optionally sigev_value |
SIGEV_THREAD |
Kernel creates a new thread and calls the function pointed to by sigev_notify_function as if it were the thread’s start function. |
sigev_notify_function, sigev_value, optionally sigev_notify_attributes |
When SIGEV_SIGNAL is used with a realtime signal and a SA_SIGINFO handler, the kernel fills in the following fields of the siginfo_t structure passed to the handler:
| siginfo_t Field | Value Set By Kernel |
|---|---|
si_code |
SI_MESGQ — identifies this as a message queue notification |
si_signo |
The signal number |
si_value |
The value from sigev_value — programmer-defined data |
si_pid |
PID of the process that sent (wrote) the message |
si_uid |
Real user ID of the sending process |
si_pid and si_uid fields are filled in on Linux but are not guaranteed on all POSIX implementations. Do not rely on them for portability.How to fill the sigevent structure for signal-based notification:
#include <signal.h>
#include <mqueue.h>
int main(void)
{
mqd_t mqd;
struct sigevent sev;
/* Open an existing queue in read-only mode */
mqd = mq_open("/myqueue", O_RDONLY);
if (mqd == (mqd_t)-1) {
perror("mq_open");
return 1;
}
/* Fill in sigevent for signal notification */
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGUSR1; /* Which signal to send */
sev.sigev_value.sival_int = 42; /* Optional data (only useful with
realtime signals) */
/* Register for notification */
if (mq_notify(mqd, &sev) == -1) {
perror("mq_notify");
return 1;
}
/* ... process does other work ...
When a message arrives on an empty queue, SIGUSR1 is sent.
Registration is removed after first notification — must call
mq_notify() again inside the signal handler / loop. */
return 0;
}
SIGEV_NONE is used when you want to block other processes from registering but you don’t actually want a signal or thread. You might do this while switching notification methods.
#include <signal.h>
#include <mqueue.h>
#include <stdio.h>
void register_placeholder(mqd_t mqd)
{
struct sigevent sev;
sev.sigev_notify = SIGEV_NONE;
/* sigev_signo and other fields are ignored for SIGEV_NONE */
if (mq_notify(mqd, &sev) == -1)
perror("mq_notify SIGEV_NONE");
else
printf("Placeholder registration set — no other process can register\n");
}
void deregister(mqd_t mqd)
{
/* Pass NULL to remove registration entirely */
if (mq_notify(mqd, NULL) == -1)
perror("mq_notify deregister");
else
printf("Registration removed\n");
}
For SIGEV_THREAD, you provide a function pointer. The kernel spawns a thread and calls it.
#include <signal.h>
#include <mqueue.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
/* This function is called in a new thread when notification arrives */
void notify_handler(union sigval sv)
{
mqd_t *mqdp = (mqd_t *)sv.sival_ptr;
printf("Notification received! Queue descriptor: %d\n", *mqdp);
/* Read messages from *mqdp here */
pthread_exit(NULL);
}
int main(void)
{
mqd_t mqd;
struct sigevent sev;
mqd = mq_open("/myqueue", O_RDONLY | O_NONBLOCK);
if (mqd == (mqd_t)-1) { perror("mq_open"); return 1; }
/* Pass a pointer to mqd as the argument to the thread function */
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = notify_handler;
sev.sigev_notify_attributes = NULL; /* Default thread attrs */
sev.sigev_value.sival_ptr = &mqd; /* Passed to handler */
if (mq_notify(mqd, &sev) == -1) {
perror("mq_notify");
return 1;
}
/* Main thread sleeps; notification thread will wake up */
pause();
return 0;
}
| Error | Meaning | Fix |
|---|---|---|
EBUSY |
Another process is already registered | Wait or use a different queue |
EBADF |
Invalid queue descriptor | Check mq_open() return value |
EINVAL |
Invalid sigev_notify value or bad signal number |
Use valid SIGEV_* constants and valid signal numbers |
ENOMEM |
Out of memory for notification data | Check system resources |
mq_notify() lets a process register for asynchronous notification when a message arrives on a previously empty POSIX message queue. It is useful when a process should not block waiting for messages — it can do other work and be notified only when needed. This avoids busy-polling and saves CPU time.mq_notify() again before the next notification can be received. Failure to re-register means the process becomes “deaf” to future messages after the first one.sigev_signo to the process. SIGEV_THREAD — Spawn a new thread and call the function in sigev_notify_function.errno set to EBUSY. Only one process at a time can be registered for notification on a given queue.union sigval has two members: sival_int (integer) and sival_ptr (pointer). With SIGEV_SIGNAL and a realtime signal, this value is passed into the signal handler via siginfo_t.si_value. With SIGEV_THREAD, it is passed as the argument to the notification thread function. It lets you carry context (like a queue descriptor pointer) into the handler.mq_notify(mqdes, NULL) to remove the current registration. This is useful for cleanup. Only the process that originally registered can deregister.mq_receive() gets the message. The notification mechanism is bypassed. Notification only fires when no process is actively waiting on the queue.