I have read that only a limited set of operations is allowed in signal handlers, for example you cannot update a global variable unless it is volatile sig_atomic_t
.
This means that only atomic operations should be done in signal handlers.
For example posting a semaphore (with sem_post
) is atomic,so it can be done within a signal handler, right?
I have read that only a limited set of operations is allowed in signal handlers, for example you cannot update a global variable unless it is volatile
sig_atomic_t
.
According to the C language spec,
If the signal occurs as the result of calling the
abort
orraise
function, the signal handler shall not call theraise
function.If the signal occurs other than as the result of calling the
abort
orraise
function, the behavior is undefined if the signal handler refers to any object with static or thread storage duration that is not a lock-free atomic object and that is not declared with theconstexpr
storage-class specifier other than by assigning a value to an object declared asvolatile sig_atomic_t
, or the signal handler calls any function in the standard library other than
- the
abort
function,- the
_Exit
function,- the
quick_exit
function,- the functions in
<stdatomic.h>
(except where explicitly stated otherwise) when the atomic arguments are lock-free,- the
atomic_is_lock_free
function with any atomic argument, or- the
signal
function with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler. Furthermore, if such a call to thesignal
function results in aSIG_ERR
return, the object designated byerrno
has an indeterminate representation.
(C23 7.14.2.1/4-5)
If you happen to be using a POSIX-conforming C implementation then you can rely on somewhat more lenience. POSIX defines behavior for more kinds of object accesses by signal handlers, and it specifies a longer list of functions that a signal handler may definedly call, which POSIX classifies as "async-signal-safe". Even though that list is relatively long, there are still many notable omissions, such as all the stdio functions.
This means that only atomic operations should be done in signal handlers.
No, that's not an accurate reading:
signal handlers may perform non-atomic operations on objects with automatic storage duration (i.e. their local variables) without special concern.
Moreover, the type sig_atomic_t
is not necessarily an atomic type, in the language's sense of that term, its name notwithstanding. Operations on objects of that type are not necessarily atomic operations.
The atomic operations that don't elicit undefined behavior are limited to those on lock-free atomic objects, which is typically a subset of atomic objects.
For example posting a semaphore (with
sem_post
) is atomic,so it can be done within a signal handler, right?
Posting a semaphore via POSIX sem_post
is not an atomic operation in the relevant sense. However, sem_post()
is among POSIX's async-signal-safe functions, so in a POSIX environment, signal handlers do not elicit undefined behavior by calling that function.
In contrast, few pthreads functions are async-signal-safe, and none of the pthreads mutex or condition variable functions are among those.
If you want to stick to defined behavior in your signal handlers -- and you should, because undefined signal handler behavior can bite you hard and is difficult to debug -- then you need to learn specifically what the specs say about it. It is not sufficient to rely on hearsay and vague ideas about what is safe and what isn't. Asking questions such as this one is a good way to start.