Search code examples
csignalssig-atomic-t

Exactly which variables need to be sig_atomic_t in the context of signal handling?


Here is a simple toy program that uses volatile sig_atomic_t.

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

#define UNUSED(x) (void) (x)

volatile sig_atomic_t quit;

void sigusr1_handler(int sig)
{
    UNUSED(sig);
    write(1, "handler\n", 8);
    quit = 1;
}

int main()
{
    struct sigaction sa;

    sa.sa_handler = sigusr1_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);

    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    quit = 0;
    while (!quit)
        ;

    printf("Exiting ...\n");
    return 0;
}

I think I know why volatile sig_atomic_t is necessary for the quit variable in this particular program.

  1. Without volatile, the compiler may optimize while (!quit) ; to an infinite loop. It does not find the loop modifying quit, so it assumes that quit always remains 0.
  2. An update to quit or a read of quit should happen in a single machine instruction. If it takes multiple machine instructions to update or read quit, then if a signal handler is invoked while an update was going on, a read in the signal handler may see inconsistent value in quit.

Am I correct so far? If not, please correct me in your answer.

Now I want to learn a generalized rule for when sig_atomic_t is necessary in the context of signal handling. Jonathan Leffler has explained in the comment that it is not easy to provide a generalization.

Can you provide a list of known scenarios where a variable needs to be defined as sig_atomic_t from C standard perspective? It need not be an exhaustive list. It could be a list a less experienced developer can refer to while writing C software with signal handling code.


Solution

  • Can you provide a list of known scenarios where a variable needs to be defined as sig_atomic_t from C standard perspective?

    There are 2 relevant sections from the c99 spec:

    (§7.14 p2)
    [The sig_atomic_t type] is the (possibly volatile-qualified) integer type of an object that can be accessed as an atomic entity, even in the presence of asynchronous interrupts

    (§7.14.1.1 p5)
    If the signal occurs other than as the result of calling the abort or raise function, the behavior is undefined if the signal handler refers to any object with static storage duration other than by assigning a value to an object declared as volatile sig_atomic_t, ...

    "Static storage duration" is defined as:

    (§6.2.4 p3)
    An object whose identifier is declared with external or internal linkage, or with the storage-class specifier static has static storage duration. Its lifetime is the entire execution of the program and its stored value is initialized only once, prior to program startup.

    In a nutshell, you are required to use volatile sig_atomic_t if the variable may be accessed asynchronously (i.e., the variable is accessed both inside and outside of the signal handler). In addition, it is undefined behavior to access a non-volatile sig_atomic_t variable which has static storage duration. Undefined behavior means that not only could the value of the variable be inconsistent, the program could do something else entirely (like segfault).