What Are Realtime Signals?
Standard Unix signals (SIGINT, SIGTERM, etc.) have several limitations: if the same signal is sent multiple times while it is already pending, only one copy is delivered. They carry no data. Their delivery order when multiple signals are pending is unpredictable.
POSIX.1b introduced realtime signals to fix all of these problems. They are designed for application-defined use in real-time and embedded systems where reliable, ordered, data-carrying signals are needed.
Standard vs Realtime Signals — Comparison
| Property | Standard Signals | Realtime Signals |
|---|---|---|
| Queuing | NOT queued — duplicates are dropped, only one delivered | QUEUED — every sent instance is delivered |
| Delivery order (multiple pending) | Implementation-defined (Linux: ascending number) | GUARANTEED: lowest-numbered signal first; same-number signals in send order |
| Carry data | No data beyond signal number | Can carry an int OR a pointer (via sigval union) |
| Application-defined use | Only SIGUSR1 and SIGUSR2 available | 32 signals (SIGRTMIN to SIGRTMAX) all available |
| How to send | kill(), raise() | sigqueue() (recommended), kill() also works |
Realtime Signal Numbers
On Linux there are 32 realtime signals, numbered 32 to 63. Use the constants SIGRTMIN and SIGRTMAX — never hardcode the numbers, as they vary across implementations.
/* rt_signal_range.c - Print realtime signal range */
#include <stdio.h>
#include <signal.h>
int main(void) {
printf("SIGRTMIN = %d\n", SIGRTMIN);
printf("SIGRTMAX = %d\n", SIGRTMAX);
printf("Number of realtime signals: %d\n", SIGRTMAX - SIGRTMIN + 1);
/* Reference realtime signals as offsets from SIGRTMIN */
printf("First realtime signal: SIGRTMIN+0 = %d\n", SIGRTMIN + 0);
printf("Second realtime signal: SIGRTMIN+1 = %d\n", SIGRTMIN + 1);
printf("Last realtime signal: SIGRTMAX = %d\n", SIGRTMAX);
return 0;
}
$ ./rt_signal_range
SIGRTMIN = 34 (34 on NPTL threading systems; 35 on LinuxThreads)
SIGRTMAX = 64
Number of realtime signals: 31
First realtime signal: SIGRTMIN+0 = 34
Second realtime signal: SIGRTMIN+1 = 35
Last realtime signal: SIGRTMAX = 64
Sending Realtime Signals with sigqueue()
sigqueue() is the correct way to send a realtime signal with attached data.
#define _POSIX_C_SOURCE 199309
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
/* Returns 0 on success, -1 on error */
union sigval {
int sival_int; /* Integer value */
void *sival_ptr; /* Pointer value (rarely useful cross-process) */
};
/* rt_sender.c - Send realtime signals with data */
#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <target-pid> <integer-data>\n", argv[0]);
exit(EXIT_FAILURE);
}
pid_t target_pid = (pid_t)atoi(argv[1]);
int data = atoi(argv[2]);
union sigval sv;
sv.sival_int = data;
printf("Sender PID=%d: sending SIGRTMIN with data=%d to PID=%d\n",
getpid(), data, target_pid);
/* Send 3 copies to demonstrate queueing */
for (int i = 0; i < 3; i++) {
sv.sival_int = data + i;
if (sigqueue(target_pid, SIGRTMIN, sv) == -1) {
perror("sigqueue");
exit(EXIT_FAILURE);
}
printf(" Sent copy %d with data=%d\n", i+1, sv.sival_int);
}
return 0;
}
Receiving Realtime Signals with SA_SIGINFO
To receive the data attached to a realtime signal, you must install a handler using SA_SIGINFO flag. The handler gets three arguments instead of one.
/* rt_receiver.c - Receive and process realtime signals with data */
#define _POSIX_C_SOURCE 199309
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
static volatile int done = 0;
/* Three-argument handler -- only used when SA_SIGINFO is set */
void rt_handler(int sig, siginfo_t *si, void *ucontext) {
printf("Received signal %d\n", sig);
printf(" si_signo = %d\n", si->si_signo);
printf(" si_code = %d (%s)\n", si->si_code,
(si->si_code == SI_QUEUE) ? "SI_QUEUE (sent via sigqueue)" :
(si->si_code == SI_USER) ? "SI_USER (sent via kill)" :
"other");
printf(" si_value = %d\n", si->si_value.sival_int);
printf(" si_pid = %d\n", si->si_pid); /* sender's PID */
printf(" si_uid = %d\n\n", si->si_uid); /* sender's UID */
done = 1;
}
void term_handler(int sig) {
printf("Terminating (got signal %d)\n", sig);
exit(0);
}
int main(void) {
struct sigaction sa;
/* Install realtime signal handler with SA_SIGINFO */
sa.sa_sigaction = rt_handler; /* Note: sa_sigaction, NOT sa_handler */
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO | SA_RESTART;
/* Install for a range of realtime signals */
for (int sig = SIGRTMIN; sig <= SIGRTMIN + 4; sig++) {
if (sigaction(sig, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
}
/* Install cleanup handler for SIGTERM/SIGINT */
signal(SIGTERM, term_handler);
signal(SIGINT, term_handler);
printf("Receiver PID=%d: waiting for realtime signals\n", getpid());
printf("Run: ./rt_sender %d 100\n\n", getpid());
/* Wait for signals */
while (!done)
pause();
printf("Done.\n");
return 0;
}
## Terminal 1:
$ ./rt_receiver
Receiver PID=12345: waiting for realtime signals
## Terminal 2:
$ ./rt_sender 12345 100
## Back in Terminal 1:
Sender PID=12346: sending SIGRTMIN with data=100 to PID=12345
Sent copy 1 with data=100
Sent copy 2 with data=101
Sent copy 3 with data=102
Received signal 34
si_signo = 34
si_code = -1 (SI_QUEUE (sent via sigqueue))
si_value = 100
si_pid = 12346
si_uid = 1000
Received signal 34
si_value = 101
...
Received signal 34
si_value = 102 <-- All 3 copies delivered (queued!)
Example 3: Demonstrating Delivery Order
Multiple different realtime signals are delivered lowest-numbered first.
/* rt_order_demo.c - Prove ordered delivery of realtime signals */
#define _POSIX_C_SOURCE 199309
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
static int received_count = 0;
void rt_handler(int sig, siginfo_t *si, void *ctx) {
received_count++;
printf("[%d] signal=%d data=%d from pid=%d\n",
received_count, sig, si->si_value.sival_int, si->si_pid);
}
int main(void) {
struct sigaction sa;
sa.sa_sigaction = rt_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
/* Install handlers for SIGRTMIN, SIGRTMIN+1, SIGRTMIN+2 */
for (int i = 0; i <= 2; i++)
sigaction(SIGRTMIN + i, &sa, NULL);
/* Block all three signals */
sigset_t block;
sigemptyset(&block);
for (int i = 0; i <= 2; i++)
sigaddset(&block, SIGRTMIN + i);
sigprocmask(SIG_BLOCK, &block, NULL);
/* Queue signals in REVERSE order: SIGRTMIN+2 first, SIGRTMIN last */
union sigval sv;
sv.sival_int = 300;
sigqueue(getpid(), SIGRTMIN + 2, sv); /* sent first */
sv.sival_int = 200;
sigqueue(getpid(), SIGRTMIN + 1, sv); /* sent second */
sv.sival_int = 100;
sigqueue(getpid(), SIGRTMIN + 0, sv); /* sent last */
printf("Signals queued in order: SIGRTMIN+2, SIGRTMIN+1, SIGRTMIN+0\n");
printf("Unblocking — expect delivery in ASCENDING number order:\n\n");
/* Unblock -- all three delivered, lowest number first */
sigprocmask(SIG_UNBLOCK, &block, NULL);
return 0;
}
$ ./rt_order_demo
Signals queued in order: SIGRTMIN+2, SIGRTMIN+1, SIGRTMIN+0
Unblocking — expect delivery in ASCENDING number order:
[1] signal=34 data=100 from pid=... <-- SIGRTMIN+0 delivered FIRST
[2] signal=35 data=200 from pid=... <-- SIGRTMIN+1 second
[3] signal=36 data=300 from pid=... <-- SIGRTMIN+2 last
Limits on Queued Realtime Signals
Queuing requires kernel memory. There is a limit on how many realtime signals can be queued per user.
/* rt_limits.c - Check and demonstrate realtime signal queue limits */
#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main(void) {
/* Method 1: sysconf */
long max_q = sysconf(_SC_SIGQUEUE_MAX);
printf("_SC_SIGQUEUE_MAX = %ld\n", max_q);
/* Method 2: check /proc/sys/kernel/rlimit or RLIMIT_SIGPENDING */
/* Since 2.6.8, RLIMIT_SIGPENDING controls this per real user ID */
/* Try to queue signals until we hit the limit */
union sigval sv;
sv.sival_int = 0;
/* Block SIGRTMIN so they queue up */
sigset_t block;
sigemptyset(&block);
sigaddset(&block, SIGRTMIN);
sigprocmask(SIG_BLOCK, &block, NULL);
int sent = 0;
while (1) {
sv.sival_int = sent;
if (sigqueue(getpid(), SIGRTMIN, sv) == -1) {
if (errno == EAGAIN) {
printf("Queue full after %d signals (EAGAIN)\n", sent);
break;
}
perror("sigqueue");
break;
}
sent++;
if (sent > 10000) { /* safety limit for demo */
printf("Sent %d signals, stopping demo\n", sent);
break;
}
}
return 0;
}
Interview Questions — Realtime Signals
Three main advantages: (1) Queuing — multiple instances of the same realtime signal are all delivered, not dropped. (2) Guaranteed delivery order — multiple pending realtime signals are delivered lowest-numbered first; same-numbered signals in the order sent. (3) Data attachment — a word of data (int or pointer) can be sent with each signal using sigqueue() and retrieved in the SA_SIGINFO handler via siginfo_t.si_value.
Use sigqueue(pid, SIGRTMIN+N, sv) where sv is a union sigval with sv.sival_int set to your integer. Example: union sigval sv; sv.sival_int = 42; sigqueue(target_pid, SIGRTMIN, sv);. You need _POSIX_C_SOURCE 199309 defined. The receiving process must install a handler with SA_SIGINFO flag to access the data via siginfo_t.si_value.sival_int.
The range of realtime signals varies between UNIX implementations. On Linux with NPTL threading, SIGRTMIN is 34 (the threading library uses the first two signals 32-33 internally). On systems with LinuxThreads, SIGRTMIN is 35. On other UNIX systems it may differ entirely. Always use SIGRTMIN+N notation. Also note: SUSv3 says SIGRTMIN and SIGRTMAX may be defined as functions, not integer constants, so you cannot use them in preprocessor #if conditions.
SA_SIGINFO changes the signal handler signature from void handler(int sig) to void handler(int sig, siginfo_t *si, void *ucontext). The siginfo_t structure provides extended information: si_pid (sender’s PID), si_uid (sender’s UID), si_value (the data sent with sigqueue()), si_code (SI_QUEUE if sent via sigqueue, SI_USER if via kill). Without SA_SIGINFO you can still catch realtime signals, but you cannot access the attached data.
sigqueue() returns -1 with errno set to EAGAIN. The signal is not sent. This means the sender must retry later. The queue limit is controlled by the RLIMIT_SIGPENDING resource limit (since kernel 2.6.8), which applies per real user ID across all processes owned by that user. The minimum required by SUSv3 is _POSIX_SIGQUEUE_MAX (32). On Linux you can check the limit with getrlimit(RLIMIT_SIGPENDING, &rl).
Next Topic
Learn how to wait for a signal safely — without a race condition — using sigsuspend().
