[ English | 简体中文 ]
Signal API
openvela provides a complete POSIX signal mechanism for asynchronous communication and event notification between processes and threads.
Header: #include <signal.h>
openvela Implementation Notes
- Signal range: Standard signals 1~31, real-time signals
SIGRTMIN(32) ~SIGRTMAX(63) - Default actions: In openvela, the default action for most signals is to ignore (unlike Linux), unless the corresponding configuration is enabled
- Real-time signal features: Supports queuing, carrying additional data (
sigqueue), and FIFO delivery order SIGKILL/SIGSTOP: Cannot be caught, blocked, or ignored- Signal stack:
sigaltstack()does not currently supportSS_ONSTACK, onlySS_DISABLEis supported - Deprecated interfaces:
signal(),sighold(),sigrelse(),sigignore(),sigset(),sigpause()are legacy interfaces; it is recommended to usesigaction()andsigprocmask()instead
Signal Overview
A signal is a software interrupt mechanism that allows the kernel or other processes to send asynchronous notifications to a target process.
Signal Types
- Standard signals (1~31): Not queued, do not carry extra data, most have predefined default actions
- Real-time signals (
SIGRTMIN~SIGRTMAX): Support queuing, can carry additional data, delivered in FIFO order
Signal Handling Modes
- Ignore (
SIG_IGN): The signal is discarded - Default handling (
SIG_DFL): Execute the default action - Custom handling: Register a signal handler function
Signal Mask
Each thread has an independent signal mask; blocked signals remain pending until unblocked.
Signal Sending
kill
int kill(pid_t pid, int signo);
Send a signal to the specified process or process group. This is the most basic and commonly used signal-sending function, used for inter-process communication, process control, and event notification.
The name kill() is historical, but it is not only used to "kill" processes; it can also send any signal for communication. Signal 0 is a special "null signal" that is not actually delivered but performs error checking and can be used to test if a process exists.
Parameters:
pidTarget process or process group identifier. Values:> 0: Send the signal to the process with the specified PID.0: Send the signal to all processes in the calling process's process group (broadcast).-1: Send the signal to all processes to which the caller has permission (except init and itself); requires superuser privileges. This is a system-wide broadcast.< -1: Send the signal to all processes in the process group whose ID is|pid|(absolute value). For example,kill(-100, SIGTERM)sends SIGTERM to process group 100.
signoSignal number to send (1-63). Valid signals are defined in<signal.h>(such asSIGTERM,SIGKILL). The special value 0 means the "null signal"; no actual signal is sent, only error checking is performed (used to detect whether a process exists).
Returns:
Returns 0 on success, or -1 on failure with errno set:
EINVALInvalid signal number (less than 0 or greater thanMAX_SIGNO).ESRCHThe specified process or process group does not exist, or the process has terminated.EPERMThe calling process does not have permission to send the signal to the target process. Usually you can only send signals to processes of the same user or to child processes.
Note:
SIGKILL(signal 9) andSIGSTOP(signal 19) are special signals that cannot be caught, blocked, or ignored, guaranteeing that the target process can be terminated or stopped.- Sending a signal is only a request; the target process may ignore the signal (if the signal handler is set to
SIG_IGN) or block the signal. - Signals may not be delivered immediately; if the target process blocks the signal, it will remain pending until unblocked.
- In a multithreaded program, a signal is delivered to one of the threads in the process that has not blocked the signal (chosen by the system). To send a signal to a specific thread, use
pthread_kill()ortgkill(). - Using
kill(pid, 0)allows you to test whether a process exists: returns 0 if it exists; returns -1 witherrnoset toESRCHif it does not exist. - When sending a signal to a process group, if the group is empty or the caller has no access to any process,
ESRCHis returned. - Some signals have special semantics; for example,
SIGCHLDnotifies a parent process of a child's state change, andSIGPIPEis produced when writing to a closed pipe.
POSIX Compatibility: Compatible with the POSIX interface of the same name.
killpg
int killpg(pid_t pgrp, int signo);
Send a signal to all processes in the specified process group. Equivalent to kill(-pgrp, signo).
Parameters:
pgrpTarget process group ID. If 0, the signal is sent to the process group of the calling process.signoSignal number to send.
Returns:
Returns 0 on success, or -1 on failure with errno set:
EINVALInvalid signal number.ESRCHThe specified process group does not exist.EPERMNo permission to send the signal to the target process group.
POSIX Compatibility: Compatible with the POSIX interface of the same name.
tgkill
int tgkill(pid_t pid, pid_t tid, int signo);
Send a signal to a specific thread in the specified thread group (process). This is the safest way to send a signal to a specific thread in a multithreaded program.
Parameters:
pidTarget process (thread group) ID. If -1, this parameter is ignored and the target thread is located bytidalone.tidTarget thread ID.signoSignal number to send.
Returns:
Returns 0 on success, or -1 on failure with errno set:
EINVALInvalid signal number.ESRCHThe thread does not exist or does not belong to the specified process.EPERMNo permission to send the signal to the target thread.
Note:
- Safer than
pthread_kill()because it verifies that the thread belongs to the expected process. - Avoids sending a signal to a wrong thread whose thread ID has been recycled.
POSIX Compatibility: Compatible with the Linux extension interface.
raise
int raise(int signo);
Send a signal to the calling thread itself. This is the standard way for a process or thread to send a signal to itself, equivalent to:
- Single-threaded program:
kill(getpid(), signo) - Multithreaded program:
pthread_kill(pthread_self(), signo)
raise() is often used to trigger signal handling on purpose, such as self-termination, triggering a breakpoint, or testing a signal handler.
Parameters:
signoSignal number to send (1-63). Common values include:SIGABRT: Abnormal termination (for example, as used byabort())SIGTERM: Request terminationSIGUSR1/SIGUSR2: User-defined signalsSIGTRAP: Trigger a debugger breakpoint
Returns:
Returns 0 on success, or a non-zero value on failure.
Note:
- If the signal's handling action is to terminate the process (such as the default action of
SIGTERMwhen enabled),raise()does not return and the process terminates directly. - The signal is handled immediately (if not blocked) or pended (if blocked), after which the function returns.
- If the signal is blocked, it remains pending until unblocked, at which point
raise()has already returned. - Calling
raise()within a signal handler to send the current signal may cause recursion unless theSA_NODEFERflag is set. raise()is thread-safe; in a multithreaded program it only affects the calling thread.- Compared to
kill(getpid(), signo),raise()is more efficient and semantically clearer. - Common usage:
raise(SIGABRT)- Terminate the program abnormally (equivalent toabort())raise(SIGTERM)- Self-terminationraise(SIGUSR1)- Trigger user-defined signal handling
POSIX Compatibility: Compatible with the POSIX interface of the same name.
sigqueue
int sigqueue(int pid, int signo, const union sigval value);
Send a signal with data to the specified process. This is an enhanced version of kill() that supports passing additional data (an integer or pointer) along with the signal. It is mainly used for real-time signals and communication scenarios that need to carry data.
Key differences from kill():
- Carries data: An integer or pointer can be passed to the receiving process via
value. - Queuing supported: Real-time signals are queued; sending multiple times queues them all for delivery.
- More detailed information: The receiver can obtain the signal source and carried data through
siginfo_t.
sigqueue() is often used for real-time signal communication, event notifications, asynchronous I/O completion notifications, and other scenarios that need to carry additional information.
Parameters:
pidTarget process ID. Must be > 0; does not support process groups (0 or negative values cannot be used).signoSignal number to send (1-63). Although standard signals can be used, real-time signals (SIGRTMINtoSIGRTMAX) are recommended because:- Real-time signals support queuing (multiple sends are all delivered)
- Standard signals are not queued (multiple sends may only be delivered once)
- Real-time signals are delivered in FIFO order
valueData passed with the signal. Theunion sigvaltype contains two members (only one can be used):sival_int: Pass an integer value, such as an error code, sequence number, or counter.sival_ptr: Pass a pointer value. Note that a pointer may be invalid in the receiving process (unless it is a shared memory address).
Returns:
Returns 0 on success, or -1 on failure with errno set:
EINVALInvalid signal number (<= 0 or >MAX_SIGNO).ESRCHThe specified process does not exist or has terminated.EPERMThe calling process has no permission to send the signal to the target process (different user, different session, etc.).EAGAINSignal queue resources are exhausted. The system limits the number of pending signals per process (typically several hundred); exceeding the limit returns this error. This mainly affects real-time signals.
Note:
- Real-time signal queuing: Real-time signals (
SIGRTMIN-SIGRTMAX) support queuing; sending the same signal multiple times queues them for delivery without loss. Each signal instance carries its own independentvalue. - Standard signals do not queue: Standard signals (such as
SIGUSR1,SIGTERM) are not queued; multiple sends may only be delivered once, and subsequentvalues may be lost. - Receiving data: The receiver must use one of the following methods to obtain
value:- When setting a handler with
sigaction(), set theSA_SIGINFOflag and use a three-parameter handler:void handler(int sig, siginfo_t *info, void *context) { int data = info->si_value.sival_int; // or void *ptr = info->si_value.sival_ptr; } - Or use
sigwaitinfo()/sigtimedwait()to wait synchronously for a signal and obtainsiginfo_t.
- When setting a handler with
- Pointer parameter caveat:
sival_ptris usually invalid across processes because each process has its own address space. It is only meaningful when:- It is a shared-memory address
- It is passed to another thread in the same process (using
pthread_sigqueue(), if available) - The "pointer" is actually an encoded integer value rather than a real pointer
- Queuing limit: The system imposes a limit on pending signals (
SIGQUEUE_MAX), which can be queried viasysconf(_SC_SIGQUEUE_MAX). Exceeding the limit returnsEAGAIN. - Signal priority: Real-time signals have an implicit priority; signals with smaller numbers are delivered first.
SIGRTMINhas the highest priority andSIGRTMAXthe lowest. - Typical usage:
// Sender union sigval val; val.sival_int = 42; // or any data sigqueue(target_pid, SIGRTMIN, val); // Receiver struct sigaction sa; sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = handler; sigemptyset(&sa.sa_mask); sigaction(SIGRTMIN, &sa, NULL); - Choosing between
kill()andsigqueue():- If you don't need to carry data and are using standard signals,
kill()is simpler. - If you need to carry data or use real-time signals, use
sigqueue().
- If you don't need to carry data and are using standard signals,
sigqueue()setssi_codeinsiginfo_ttoSI_QUEUE, which can be used to distinguish the signal source.
POSIX Compatibility: Compatible with the POSIX interface of the same name.
Signal Handler Setup
sigaction
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
Set or query the handling action for a signal. This is the preferred method for setting a signal handler, offering more control and more predictable behavior than signal(). sigaction() is the POSIX-recommended interface for signal handling.
The struct sigaction structure allows precise control over signal handling behavior, including the handler function, signal mask, and various flags. It is more powerful and flexible than the simple signal() interface.
Parameters:
signoSignal number to configure (1-63). Cannot beSIGKILL(9) orSIGSTOP(19) because the handling of these two signals cannot be modified.actPointer to the new signal handling action structure. IfNULL, the current handling action is not modified; onlyoactis used for querying. Structure fields:sa_handlerorsa_sigaction: Signal handler function.SIG_DFL: Restore the default action.SIG_IGN: Ignore the signal.- Function pointer: Custom handler function.
sa_mask: Signal mask specifying signals to be additionally blocked during the handler's execution. The signal being handled is automatically blocked (unlessSA_NODEFERis set).sa_flags: Flags controlling signal handling behavior (see below).
oactPointer used to save the previous handling action. IfNULL, the previous action is not returned. Can be used to save and later restore the original handling action.
sa_flags description:
SA_SIGINFO(0x02): Use the extended three-parameter handlersa_sigaction(int sig, siginfo_t *info, void *context)instead of the simplesa_handler(int sig). This allows obtaining detailed signal information (sender PID, signal value, etc.).SA_RESTART(0x10): System calls interrupted by the signal are automatically restarted instead of returningEINTR. This simplifies error handling and avoids the need to manually retry interrupted system calls.SA_NODEFER(0x20): Do not automatically block the signal being handled. By default, signal X is blocked while handling signal X to prevent recursion. Setting this flag allows recursive invocation of the signal handler.SA_RESETHAND(0x40): Reset the handling action toSIG_DFLautomatically after the signal is delivered (one-shot handler). Similar to the old, unreliable signal semantics.SA_ONSTACK(0x08): Execute the handler on the alternate signal stack (first configured withsigaltstack()). Used to prevent the stack-overflow signal handler from itself overflowing the stack.SA_NOCLDSTOP(0x01): IfsignoisSIGCHLD, no signal is generated when a child stops (SIGSTOP) or resumes (SIGCONT); only termination produces a signal.SA_NOCLDWAIT(0x04): IfsignoisSIGCHLD, terminating child processes are reaped automatically, zombie processes are not created, and the parent does not need to callwait().
Returns:
Returns 0 on success, or -1 on failure with errno set:
EINVALsignois invalid, or an attempt is made to modify the handling action ofSIGKILL/SIGSTOP.EFAULTactoroactpoints to an invalid memory address (segfault).
Note:
- Signal handlers should be kept as short and efficient as possible and must avoid calling non-reentrant functions (such as
malloc()orprintf()). Only call async-signal-safe functions. sa_masktakes effect during handler execution and is automatically restored after the handler returns.- It is safe to call
sigaction()multiple times to modify the handling of the same signal; the new action replaces the old one. - To temporarily change and restore signal handling, the pattern is:
struct sigaction old_act; sigaction(SIGINT, &new_act, &old_act); // set new handler // ... do something ... sigaction(SIGINT, &old_act, NULL); // restore old handler - When modifying global variables in a signal handler, declare them as
volatile sig_atomic_tto ensure atomicity and visibility. - Using the
SA_SIGINFOflag allows obtaining detailed signal information, such as the sender PID and data carried by the signal, which is useful for debugging and advanced signal handling. sa_maskshould include all signals that need to be blocked during handler execution to prevent race conditions caused by the handler being interrupted by other signals.
POSIX Compatibility: Compatible with the POSIX interface of the same name.
signal
sighandler_t signal(int signo, sighandler_t handler);
Set the signal handler function. This is a simplified version of sigaction(), but its behavior may vary across systems. sigaction() is recommended.
Parameters:
signoSignal number. Cannot beSIGKILLorSIGSTOP.handlerSignal handler:SIG_IGNIgnore the signal.SIG_DFLUse the default action.- A user-defined function pointer with prototype
void handler(int signo).
Returns:
Returns the previous signal handler on success, or SIG_ERR on failure with errno set.
Note:
- The handler should be kept short and must only call async-signal-safe functions.
- In openvela,
signal()behaves consistently with BSD semantics: the handler is not reset to the default after handling, and interrupted system calls are automatically restarted. - For scenarios requiring precise control over signal behavior, use
sigaction().
POSIX Compatibility: Compatible with the POSIX interface of the same name.
sigset
sighandler_t sigset(int signo, sighandler_t handler);
Set the signal handler (similar to signal, but supports SIG_HOLD).
Parameters:
signoSignal number.handlerSignal handler; can beSIG_IGN,SIG_DFL,SIG_HOLD, or a user-defined function.
Returns:
Returns the previous signal handler on success, or SIG_ERR on failure.
POSIX Compatibility: Compatible with the POSIX interface of the same name (obsolete).
sigignore
int sigignore(int signo);
Set the handling of the specified signal to ignore.
Parameters:
signoSignal number to ignore.
Returns:
Returns 0 on success, or -1 on failure.
Note:
- Deprecated interface, equivalent to setting
SIG_IGNviasigaction(). Usesigaction()instead. SIGKILLandSIGSTOPcannot be ignored. POSIX Compatibility: Compatible with thePOSIXinterface of the same name (obsolete).
siginterrupt
int siginterrupt(int signo, int flag);
Set whether the signal interrupts system calls.
Parameters:
signoSignal number.flagIf non-zero, the signal interrupts system calls; otherwise, system calls are automatically restarted.
Returns:
Returns 0 on success, or -1 on failure.
Note:
- Non-zero
flag: ClearsSA_RESTART; interrupted system calls returnEINTR. - Zero
flag: SetsSA_RESTART; interrupted system calls are automatically restarted. POSIX Compatibility: Compatible with the BSD extension interface.
Signal Set Operations
sigemptyset
int sigemptyset(sigset_t *set);
Initialize a signal set to be empty (contains no signals). This initialization is required before using a signal set. The contents of an uninitialized signal set are indeterminate and using it directly leads to undefined behavior.
Once initialized as empty, individual signals can be added with sigaddset() to build a custom signal set.
Parameters:
setPointer to the signal set to initialize. Must be an allocatedsigset_tvariable (automatic, static, or dynamically allocated).
Returns:
Returns 0 on success. According to POSIX, this function always succeeds, but some implementations may return -1 if the argument is invalid.
Note:
- Must be initialized: Every signal set must be initialized with
sigemptyset()orsigfillset()before use. Do not assume that a newly allocated signal set is empty; the initial value of an automatic variable is indeterminate. - An initialized empty set contains no signals, and membership tests (
sigismember()) return 0 for every signal. - Typical usage pattern:
sigset_t set; sigemptyset(&set); // initialize to empty sigaddset(&set, SIGINT); // add SIGINT sigaddset(&set, SIGTERM); // add SIGTERM // set now contains SIGINT and SIGTERM - An empty set is commonly used with
sigprocmask(SIG_SETMASK, &empty_set, ...)to unblock all signals. - Even if a signal set has been initialized,
sigemptyset()can be called again to clear it. - Pairing with
sigfillset(): usesigemptyset()+sigaddset()to build sparse signal sets (containing a few signals); usesigfillset()+sigdelset()to build dense signal sets (excluding a few signals).
POSIX Compatibility: Compatible with the POSIX interface of the same name.
sigfillset
int sigfillset(sigset_t *set);
Initialize a signal set to be full (contains all valid signals). After initialization, the set contains every signal supported by the system (1 to MAX_SIGNO, i.e., 1-63).
A full set is often used when all signals need to be blocked, or as a starting point from which unwanted signals are removed with sigdelset().
Parameters:
setPointer to the signal set to initialize.
Returns:
Returns 0 on success. According to POSIX, this function always succeeds, but some implementations may return -1 if the argument is invalid.
Note:
- The full set contains all signals (including
SIGKILLandSIGSTOP), butSIGKILLandSIGSTOPcannot actually be blocked even if they are in a signal set. - The number of signals included in the full set depends on the system; in openvela it is 63 (
MIN_SIGNO=1toMAX_SIGNO=63). - Typical usage pattern:
sigset_t set; sigfillset(&set); // initialize to full sigdelset(&set, SIGUSR1); // remove SIGUSR1 sigdelset(&set, SIGUSR2); // remove SIGUSR2 sigprocmask(SIG_SETMASK, &set, NULL); // block all except SIGUSR1/SIGUSR2 - Common applications:
- Block all signals to protect a critical section:
sigfillset(&set); sigprocmask(SIG_BLOCK, &set, ...); - Use in
sa_maskofsigaction()to block all other signals while a signal is being handled - Create a "reverse" signal set: fill first, then remove unwanted signals
- Block all signals to protect a critical section:
- Choosing between
sigfillset()andsigemptyset():- Use
sigemptyset()+sigaddset()if only a few signals are needed. - Use
sigfillset()+sigdelset()if a few signals should be excluded.
- Use
- Even if a signal set has been initialized,
sigfillset()can be called again to refill it.
POSIX Compatibility: Compatible with the POSIX interface of the same name.
sigaddset
int sigaddset(sigset_t *set, int signo);
Add the specified signal to a signal set. If the signal is already in the set, the operation has no effect (idempotent).
This is the fundamental operation for building a custom signal set, typically used after sigemptyset() initialization to add needed signals one by one.
Parameters:
setPointer to the signal set to modify. Must have been initialized withsigemptyset()orsigfillset().signoSignal number to add (1-63). Must be a valid signal number; invalid values lead to undefined behavior or return an error.
Returns:
Returns 0 on success, or -1 on failure with errno set:
EINVALsignois not a valid signal number (<= 0 or >MAX_SIGNO).
Note:
- Must operate on an initialized signal set; otherwise behavior is undefined.
- Adding a signal that already exists produces no error or side effect.
- Multiple calls can be made to add multiple signals:
sigset_t set; sigemptyset(&set); sigaddset(&set, SIGINT); sigaddset(&set, SIGTERM); sigaddset(&set, SIGUSR1); SIGKILLandSIGSTOPcan be added to a signal set, but they are ignored when actually used (such as withsigprocmask()).- Common uses:
- Build a signal mask to block signals
- Specify the signal set for
sigwait()to wait on - Set
sa_maskinsigaction()
- Paired with
sigdelset()for flexible signal set manipulation. - Use
sigismember()to check whether a signal is in the set.
POSIX Compatibility: Compatible with the POSIX interface of the same name.
sigdelset
int sigdelset(sigset_t *set, int signo);
Remove the specified signal from a signal set. If the signal is not in the set, the operation has no effect (idempotent).
This is typically paired with sigfillset(): fill all signals first, then remove unwanted ones to build an "exclude-type" signal set.
Parameters:
setPointer to the signal set to modify. Must have been initialized withsigemptyset()orsigfillset().signoSignal number to remove (1-63). Must be a valid signal number; invalid values lead to undefined behavior or return an error.
Returns:
Returns 0 on success, or -1 on failure with errno set:
EINVALsignois not a valid signal number (<= 0 or >MAX_SIGNO).
Note:
- Must operate on an initialized signal set; otherwise behavior is undefined.
- Removing a signal that is not present produces no error or side effect.
- Multiple calls can be made to remove multiple signals:
sigset_t set; sigfillset(&set); // contains all signals sigdelset(&set, SIGINT); // remove SIGINT sigdelset(&set, SIGTERM); // remove SIGTERM // set now contains all signals except SIGINT and SIGTERM - Common uses:
- Exclude certain signals from a full set
- Modify an existing signal set to drop certain signals
- Fine-grained control of the signal mask
- The inverse of
sigaddset(); the two can be combined for flexible signal set manipulation. - Removing
SIGKILLorSIGSTOPhas no practical effect since they cannot be blocked anyway.
POSIX Compatibility: Compatible with the POSIX interface of the same name.
sigismember
int sigismember(const sigset_t *set, int signo);
Check whether the specified signal is in a signal set. This is the standard way to query signal set membership.
Parameters:
setPointer to the signal set to query. Must be initialized.signoSignal number to check (1-63).
Returns:
- Returns 1: The signal is in the set (is a member).
- Returns 0: The signal is not in the set (is not a member).
- Returns -1: Failure;
signois invalid.errnois set toEINVAL.
Note:
- The return value is 1 or 0, not non-zero or zero. This differs from some boolean functions; explicitly check for 1.
- Correct usage:
if (sigismember(&set, SIGINT) == 1) { // SIGINT is in the set } else if (sigismember(&set, SIGINT) == 0) { // SIGINT is not in the set } else { // error } - Simplified usage (as a boolean):
if (sigismember(&set, SIGINT)) { // SIGINT is in the set (returns 1, truthy) } - Must operate on an initialized signal set; results on an uninitialized signal set are indeterminate.
- Common uses:
- Check whether a signal is blocked (by querying the mask returned by
sigprocmask()) - Check whether a signal is pending (by querying the set returned by
sigpending()) - Verify the contents of a signal set
- Check whether a signal is blocked (by querying the mask returned by
- With set operations:
sigset_t set; sigemptyset(&set); sigaddset(&set, SIGINT); assert(sigismember(&set, SIGINT) == 1); assert(sigismember(&set, SIGTERM) == 0);
POSIX Compatibility: Compatible with the POSIX interface of the same name.
sigisemptyset
int sigisemptyset(sigset_t *set);
Check whether a signal set is empty.
Parameters:
setSignal set.
Returns:
Returns 1 if the signal set is empty, otherwise 0.
Note:
- glibc extension (not POSIX-standard); used for a quick check whether a signal set is empty. POSIX Compatibility: Compatible with the glibc extension interface.
sigandset
int sigandset(sigset_t *dest, const sigset_t *left, const sigset_t *right);
Compute the intersection of two signal sets.
Parameters:
destSignal set that stores the result.leftFirst signal set.rightSecond signal set.
Returns:
Returns 0 on success, or -1 on failure.
Note:
- glibc extension, used to compute the intersection of two signal sets.
destmay be the same asleftorright. POSIX Compatibility: Compatible with the glibc extension interface.
sigorset
int sigorset(sigset_t *dest, const sigset_t *left, const sigset_t *right);
Compute the union of two signal sets.
Parameters:
destSignal set that stores the result.leftFirst signal set.rightSecond signal set.
Returns:
Returns 0 on success, or -1 on failure.
Note:
- glibc extension, used to compute the union of two signal sets.
destmay be the same asleftorright. POSIX Compatibility: Compatible with the glibc extension interface.
Signal Waiting
sigwait
int sigwait(const sigset_t *set, int *sig);
Synchronously wait for any signal in set to arrive. This is the key function for handling signals synchronously, allowing them to be treated as ordinary events rather than asynchronous interrupts.
Unlike asynchronous signal handling, sigwait() turns signals into synchronous events: a thread actively waits for a signal, and when it arrives the function returns the signal number instead of invoking a handler. This greatly simplifies signal handling and avoids the complexities of asynchronous handling (such as reentrancy and race conditions).
A typical usage is to create a dedicated signal-handling thread that blocks signals of interest and then calls sigwait() in a loop to receive and handle them.
Parameters:
setSignal set to wait for. These signals should normally be blocked first withsigprocmask()orpthread_sigmask(); otherwise the signals may be caught by an asynchronous handler instead of being received bysigwait(). Must contain at least one valid signal.sigReturns the received signal number (output parameter). When waiting for multiple signals, it is impossible to predict which arrives first; dispatch on the returned number.
Returns:
Returns 0 on success, or an error code on failure (note: the error code is returned directly and errno is not set):
EINVALsetcontains an invalid signal number (<= 0 or >MAX_SIGNO).EINTRInterrupted by a signal not inset(in some implementations; openvela generally does not return this).
Note:
- Important: The signals being waited for must be blocked. Otherwise, signals may be caught by a handler before or during the
sigwait()call, causingsigwait()to miss them. Recommended pattern:sigset_t set; sigemptyset(&set); sigaddset(&set, SIGUSR1); sigaddset(&set, SIGUSR2); // block signals first pthread_sigmask(SIG_BLOCK, &set, NULL); // then wait int sig; while (1) { sigwait(&set, &sig); switch (sig) { case SIGUSR1: /* handle SIGUSR1 */ break; case SIGUSR2: /* handle SIGUSR2 */ break; } } sigwait()removes the signal from the pending queue and does not trigger a signal handler. Even if a handler is registered, a signal received viasigwait()does not invoke it.- If multiple threads wait on the same signal, only one thread receives it (the system chooses).
- If a signal is already pending before
sigwait()is called, the function returns immediately without blocking. sigwait()is a cancellation point: if the thread is cancelled (pthread_cancel()), the function returns immediately.- Advantages of synchronous waiting over asynchronous handling:
- No need to consider function reentrancy
- Ordinary C library functions can be used (malloc, printf, etc.)
- No need to use
volatile sig_atomic_tvariables - Easier to reason about and debug
- Often used to implement the signal-handling-thread pattern: main and worker threads block all signals, and a dedicated signal thread calls
sigwait()in a loop to process signals. - Standard signals are not queued; if the same signal is sent multiple times, only one instance may be received by
sigwait(). Real-time signals support queuing.
POSIX Compatibility: Compatible with the POSIX interface of the same name.
sigwaitinfo
int sigwaitinfo(const sigset_t *set, siginfo_t *info);
Wait for any signal in set and obtain detailed information about the signal. Similar to sigwait(), but provides more signal information.
Parameters:
setSignal set to wait for.infoIf non-NULL, receives detailed signal information, including:si_signoSignal number.si_codeSignal source code (such asSI_USER,SI_QUEUE,SI_TIMER).si_pidPID of the sending process.si_valueData passed with the signal (for signals sent viasigqueue()).
Returns:
Returns the signal number on success, or -1 on failure with errno set:
EINTRInterrupted by another signal.EINVALsetcontains an invalid signal number.
Note:
- Equivalent to
sigtimedwait(set, info, NULL), i.e., an infinite wait without timeout. - Differs from
sigwait()in that it returns more detailedsiginfo_tinformation.
Note:
- Equivalent to
sigtimedwait(set, info, NULL), i.e., an infinite wait without timeout. - Differs from
sigwait()in that it returns more detailedsiginfo_tinformation. - On failure,
errnomay beEINTR(interrupted) orEINVAL(invalid signal set). POSIX Compatibility: Compatible with thePOSIXinterface of the same name.
sigtimedwait
int sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout);
Wait for any signal in set with a timeout. If no signal arrives within the specified time, the function returns an error.
Parameters:
setSignal set to wait for.infoIf non-NULL, receives detailed signal information.timeoutTimeout:tv_secSeconds.tv_nsecNanoseconds.- If
NULL, wait indefinitely (equivalent tosigwaitinfo()). - If
{0, 0}, return immediately (polling mode).
Returns:
Returns the signal number on success, or -1 on failure with errno set:
EAGAINNo signal arrived within the timeout.EINTRInterrupted by another signal (not inset).EINVALInvalidtimeoutargument (such as a negative value).
Note:
- Commonly used to implement signal waiting logic with a timeout.
- Combined with real-time signals, it can implement a reliable event notification mechanism.
POSIX Compatibility: Compatible with the POSIX interface of the same name.
sigsuspend
int sigsuspend(const sigset_t *mask);
Temporarily replace the signal mask and suspend the task until an unblocked signal arrives. This is an atomic operation that avoids the race condition between separate calls to sigprocmask() and pause().
Parameters:
maskTemporary signal mask. During the wait, the process's signal mask is replaced with this value.
Returns:
Always returns -1, with errno set to EINTR (interrupted by a signal).
Note:
- After the function returns, the signal mask is automatically restored to the value before the call.
- Often used to wait for a specific signal while keeping it unblocked.
- Example: unblock
SIGUSR1and wait for it:sigset_t mask; sigemptyset(&mask); // unblock only SIGUSR1 sigsuspend(&mask); // wait for any signal
POSIX Compatibility: Compatible with the POSIX interface of the same name.
sigpause
int sigpause(int signo);
Remove the specified signal from the signal mask and suspend the task until the signal arrives.
Parameters:
signoSignal number to unblock.
Returns:
Always returns -1, with errno set to EINTR.
POSIX Compatibility: Compatible with the POSIX interface of the same name (obsolete).
Signal Mask
sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
Set or query the signal mask of the calling thread. The signal mask determines which signals are currently blocked. Blocked signals are not delivered to the process but remain pending until removed from the mask (unblocked).
The signal mask is thread-local; each thread maintains its own signal mask. A newly created thread inherits the creator's signal mask. Blocking signals can protect critical sections from being interrupted and is an important tool for writing robust signal-handling code.
Parameters:
howSpecifies how to modify the signal mask; must be one of:SIG_BLOCK(1): Add signals insetto the current mask. New mask = old mask ∪set. Used to block more signals.SIG_UNBLOCK(2): Remove signals insetfrom the current mask. New mask = old mask -set. Used to unblock signals.SIG_SETMASK(3): Replace the current mask entirely withset. New mask =set. Used to set a precise signal mask.
setSignal set to operate on. IfNULL, the current mask is not modified; only queried throughoset(in this casehowis ignored). The signal set should be initialized and configured withsigemptyset(),sigfillset(),sigaddset(), etc.osetIf non-NULL, returns the signal mask before the operation (old value). Can be used to save the current mask for later restoration. To only query without modifying, passset=NULL.
Returns:
Returns 0 on success, or -1 on failure with errno set:
EINVALThehowvalue is invalid (notSIG_BLOCK,SIG_UNBLOCK, orSIG_SETMASK).EFAULTsetorosetpoints to an invalid memory address.
Note:
SIGKILL(9) andSIGSTOP(19) cannot be blocked; attempts to block them are silently ignored (no error is returned). This guarantees that a process can always be terminated or stopped.- In a multithreaded program, each thread has its own signal mask;
sigprocmask()only affects the calling thread. For multithreaded programs, POSIX recommends usingpthread_sigmask()(which has the same functionality but returns an error code instead of settingerrno). - After a signal is unblocked, if that signal is pending, it is delivered immediately (before
sigprocmask()returns). - While a signal is blocked, sending the same signal multiple times queues only one instance (standard signals do not queue). Real-time signals (
SIGRTMIN-SIGRTMAX) support queuing. - Typical usage pattern - critical section protection:
sigset_t new_mask, old_mask; sigemptyset(&new_mask); sigaddset(&new_mask, SIGINT); sigaddset(&new_mask, SIGTERM); // enter critical section, block signals sigprocmask(SIG_BLOCK, &new_mask, &old_mask); // critical section code, not interrupted by SIGINT/SIGTERM // ... // leave critical section, restore signal mask sigprocmask(SIG_SETMASK, &old_mask, NULL); - The signal mask is inherited by child processes after
fork(), but reset to empty afterexec()(all signals unblocked). - The signal mask does not affect the signal handling action; only signal delivery. Even if a signal is blocked, its handling action can still be modified with
sigaction(). - Use
sigpending()to query which signals are currently blocked and pending.
POSIX Compatibility: Compatible with the POSIX interface of the same name.
sigpending
int sigpending(sigset_t *set);
Get the set of signals that are currently blocked and pending (waiting to be delivered). These signals have been sent to the process but have not been delivered because they are blocked.
Parameters:
setPointer used to return the pending signal set.
Returns:
Returns 0 on success, or -1 on failure with errno set.
Note:
- Can be used to check whether any signals are waiting before unblocking them.
- Combine with
sigismember()to check whether a specific signal is pending.
POSIX Compatibility: Compatible with the POSIX interface of the same name.
sighold
int sighold(int signo);
Add the specified signal to the signal mask (block the signal).
Parameters:
signoSignal number to block.
Returns:
Returns 0 on success, or -1 on failure.
Note:
- Deprecated interface, equivalent to
sigprocmask(SIG_BLOCK, ...). Usesigprocmask()instead. - On failure,
errnois set toEINVAL(invalid signal number). POSIX Compatibility: Compatible with thePOSIXinterface of the same name (obsolete).
sigrelse
int sigrelse(int signo);
Remove the specified signal from the signal mask (unblock).
Parameters:
signoSignal number to unblock.
Returns:
Returns 0 on success, or -1 on failure.
Note:
- Deprecated interface, equivalent to
sigprocmask(SIG_UNBLOCK, ...). Usesigprocmask()instead. - On failure,
errnois set toEINVAL(invalid signal number). POSIX Compatibility: Compatible with thePOSIXinterface of the same name (obsolete).
pthread_sigmask
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset);
Set or query the signal mask of the calling thread. Functionally identical to sigprocmask(), but this is the recommended interface for POSIX multithreaded programs.
The main difference is how errors are reported: pthread_sigmask() returns an error code directly (does not set errno), whereas sigprocmask() returns -1 and sets errno. This follows the general convention for pthread functions.
Parameters:
howSpecifies how to modify the signal mask:SIG_BLOCK(1): Block more signals. New mask = old mask ∪set.SIG_UNBLOCK(2): Unblock signals. New mask = old mask -set.SIG_SETMASK(3): Replace the mask. New mask =set.
setSignal set to operate on. IfNULL, the mask is not modified; only queried (returned throughoset).osetIf non-NULL, returns the signal mask before the operation.
Returns:
Returns 0 on success, or an error code on failure (does not set errno):
EINVALInvalidhowargument.EFAULTsetorosetpoints to invalid memory (in some implementations).
Note:
- Multithreading-dedicated: In multithreaded programs, use
pthread_sigmask()instead ofsigprocmask(). The functionality is the same, butpthread_sigmask()has clearer semantics. - Thread-local mask: Each thread has its own signal mask; modifications only affect the calling thread.
- New thread inheritance: Threads created via
pthread_create()inherit the creator's signal mask. SIGKILLandSIGSTOPcannot be blocked; attempts to block them are silently ignored.- After a signal is unblocked, pending signals are delivered immediately (before the function returns).
- Typical usage - dedicated signal-handling thread pattern:
// main thread: block all signals sigset_t mask; sigfillset(&mask); pthread_sigmask(SIG_SETMASK, &mask, NULL); // create worker threads (inherit the mask that blocks all signals) pthread_create(&worker, NULL, worker_func, NULL); // create signal-handling thread pthread_create(&sig_thread, NULL, signal_handler_thread, NULL); // signal handling thread: unblock and synchronously wait for signals void *signal_handler_thread(void *arg) { sigset_t wait_mask; sigemptyset(&wait_mask); sigaddset(&wait_mask, SIGINT); sigaddset(&wait_mask, SIGTERM); int sig; while (1) { sigwait(&wait_mask, &sig); // handle signal... } } - Error handling difference:
// sigprocmask() style if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) { perror("sigprocmask"); // errno already set } // pthread_sigmask() style int err = pthread_sigmask(SIG_BLOCK, &set, NULL); if (err != 0) { errno = err; perror("pthread_sigmask"); // must set errno manually } - Combined with
pthread_kill(), precise inter-thread signal communication can be implemented. - In single-threaded programs,
pthread_sigmask()andsigprocmask()are fully equivalent.
POSIX Compatibility: Compatible with the POSIX interface of the same name.
Signal Stack
sigaltstack
int sigaltstack(const stack_t *ss, stack_t *oss);
Set or get the alternate stack for signal handling.
Parameters:
ssIf non-NULL, points to the new alternate stack configuration:ss_spPointer to the stack memory.ss_sizeStack size.ss_flagsFlags (SS_DISABLEdisables the alternate stack).
ossIf non-NULL, returns the previous alternate stack configuration.
Returns:
Returns 0 on success, or -1 on failure with errno set.
Note:
- openvela does not currently support
SS_ONSTACK; setting a stack with flags other thanSS_DISABLEreturnsEINVAL. EINVALis also returned whenss->ss_sizeis less thanMINSIGSTKSZ(returnsENOMEM). POSIX Compatibility: Compatible with thePOSIXinterface of the same name.
Thread Signals
pthread_kill
int pthread_kill(pthread_t thread, int signo);
Send a signal to a specified thread. This is the standard way to send a signal to a specific thread in a multithreaded program. It is more precise than kill() and allows specifying which thread receives the signal.
In a multithreaded program, a signal can be delivered to any thread in the process that has not blocked the signal. Using pthread_kill() explicitly specifies the receiving thread, ensuring the signal is handled by the expected thread.
Parameters:
threadThread ID of the target thread (returned frompthread_create()or obtained viapthread_self()).signoSignal number to send (1-63). The special value 0 is the "null signal" and does not send an actual signal; it only checks whether the thread exists (liveness test).
Returns:
Returns 0 on success, or an error code on failure (note: the error code is returned directly and errno is not set):
EINVALInvalidsigno(<= 0 or >MAX_SIGNO).ESRCHThe thread does not exist or has terminated. The thread ID may have been recycled and refer to a new thread, causing the signal to be delivered to the wrong thread.
Note:
- Thread specificity: The signal is delivered to the specified thread; even if the thread blocks the signal, the signal remains pending on that thread (not on another thread).
- Liveness test: Use
pthread_kill(thread, 0)to test whether a thread exists:if (pthread_kill(thread, 0) == 0) { // thread exists } else { // thread does not exist (ESRCH) } - Signal handling: Even when a signal is sent to a specific thread, the handler may still execute in another thread (depending on signal masks). If only the target thread has the signal unblocked, the handler will run in that thread.
- Thread ID reuse: After a thread terminates, its ID may be reused. Sending a signal after termination may send it to a new thread. Use
tgkill()to avoid this (it verifies that the thread belongs to the expected process). - Difference from
kill():kill()sends to the entire process; any thread may handle the signal.pthread_kill()sends to a specific thread, which is more precise.
- Relationship to
raise(): In a multithreaded program,raise(sig)is equivalent topthread_kill(pthread_self(), sig). - Signal mask inheritance: A new thread inherits the creator's signal mask. If a different mask is needed, call
pthread_sigmask()when the thread starts. - Typical uses:
- Cancel or terminate a specific thread (sending
SIGTERM,SIGUSR1, etc.) - Send notification signals to worker threads
- Send signals to signal-handling threads
- Test thread liveness
- Cancel or terminate a specific thread (sending
- Thread safety:
pthread_kill()itself is thread-safe and can be called concurrently from multiple threads. - POSIX consistency: In openvela,
pthread_tis the same aspid_t; the thread ID is the task ID.
POSIX Compatibility: Compatible with the POSIX interface of the same name.
Debugging and Diagnostics
psignal
void psignal(int signo, const char *message);
Print the description of a signal.
Parameters:
signoSignal number.messagePrefix message.
Returns:
No return value.
Note:
- The output format is
message: signal_description, mainly used for debugging and error logging. POSIX Compatibility: Compatible with the BSD extension interface.
psiginfo
void psiginfo(const siginfo_t *info, const char *message);
Print the description of a signal information structure.
Parameters:
infoSignal information structure.messagePrefix message.
Returns:
No return value.
Note:
- Provides more detailed information than
psignal(), including the signal source code. POSIX Compatibility: Compatible with thePOSIXinterface of the same name.