Signals

Signals can be seen as a limited form of communication among processes (process to process) or among the OS and processes. They are considered a Inter Process Communication (IPC) mechanism, implemented in Unix since its creation. Some people describe them as software interrupts.

Signals are very short messages among processes. In fact, the only given information given to the signal receiving process is the number of the signal.

There are 31 “Unix standard” signals, ranging their ID number 1 to 31 (0 is ignored). Not surprisingly, all of them fit in a 32-bit word, useful when masking signals (see below). The “newer” (end of 80’s - but mainly during 90’s) POSIX standards introduced a new set of 31 signals (from number 32 to 63) for real-time signaling. This post will cover the standard signals.

All the literal names of signals commences with “SIG”, and is followed by a mnemonic name of the signal function. For instance, signal “SIGFPE” is the name for the signal with id “8”. FPE stands for “Floating-Point Exception”. This signal is sent to a process when the executed instructions produce an erroneous arithmetic operation (like divide by “0”).

The set-up of the pre-defined signals is one of the many tasks that the kernel does while initializing. Signals are generated on certain events and are delivered to the receiving process by the kernel. Some of the signals are usually only managed by the kernel as the SIGFPE explained before. Other signals can be emitted by a process to signal a receiving process. A clear example is the bash KILL command, used to send signals to processes. In the next capture, Mico Maco sends signals SIGQUIT(3) and SIGHUP(1) with ‘kill’. The program signal_test has a handler for signal “3”, but not for signal “1”

mycomputer ~ $ ./signal_test &
[1] 18292
mycomputer ~ $ kill -3 18292
Process got signal 3
mycomputer ~ $ kill -1 18292
[1]+  Hangup                  ./signal_test
mycomputer ~ $

As you will see below, signal SIGQUIT(3) sends and “order” to quit the program, but as signal_test has a signal handler for it, it ignores the real order and does what it is coded in the handle (in this case, echo Process got signal 3). Except the SIGKILL and SIGSTOP signals, a program can set a function to handle a given signal, as well as masking (blocking) signals (see Signal handling and masking below)

Unix signals are asynchronous. This means that the sender process does not know at which execution point the receiving process will receive the signal. This is due the way signals are “dispatched” by the kernel. Find here an old but thorough explanation on how Linux handles signals. To cut a long story short, signals are passed among processes (or by the kernel to a process) when processes are re-scheduled and enter again user mode. The kernel makes sure that the signals recorded in the signal structure in the Process Control Block (PCB) (or Task structure in Linux…) are delivered to the awakened process.

So the delivery of a signal happens when a “dormant” process is given again a CPU slice to run again.

Review the “init_task.c” to see these structures created for the very first process when Linux is started.

Around line 57 you will see the struct task_struct init_task. Inside this strucuture you will find the member [.signal = &init_signals](https://github.com/torvalds/linux/blob/master/init/init_task.c#L109). init_signals is another structure that you can find aroung line 18

In Linux the PCB is named Task structure in Linux and is defined in the scheduler code

See below the list of signals

Signals

You can a list of signals executing ‘kill -l’ in the terminal:

mycomputer ~ $ kill -l
 1) SIGHUP        2) SIGINT        3) SIGQUIT       4) SIGILL        5) SIGTRAP
 6) SIGABRT       7) SIGBUS        8) SIGFPE        9) SIGKILL      10) SIGUSR1
11) SIGSEGV      12) SIGUSR2      13) SIGPIPE      14) SIGALRM      15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD      18) SIGCONT      19) SIGSTOP      20) SIGTSTP
21) SIGTTIN      22) SIGTTOU      23) SIGURG       24) SIGXCPU      25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF      28) SIGWINCH     29) SIGIO        30) SIGPWR
31) SIGSYS       34) SIGRTMIN     35) SIGRTMIN+1   36) SIGRTMIN+2   37) SIGRTMIN+3
38) SIGRTMIN+4   39) SIGRTMIN+5   40) SIGRTMIN+6   41) SIGRTMIN+7   42) SIGRTMIN+8
43) SIGRTMIN+9   44) SIGRTMIN+10  45) SIGRTMIN+11  46) SIGRTMIN+12  47) SIGRTMIN+13
48) SIGRTMIN+14  49) SIGRTMIN+15  50) SIGRTMAX-14  51) SIGRTMAX-13  52) SIGRTMAX-12
53) SIGRTMAX-11  54) SIGRTMAX-10  55) SIGRTMAX-9   56) SIGRTMAX-8   57) SIGRTMAX-7
58) SIGRTMAX-6   59) SIGRTMAX-5   60) SIGRTMAX-4   61) SIGRTMAX-3   62) SIGRTMAX-2
63) SIGRTMAX-1   64) SIGRTMAX

Find a short explanation as well as the id number and the default action in this table:

Signal Value (1) Action (2) Comment
SIGHUP 1 Term Hangup detected on controlling terminal or death of controlling process
SIGINT 2 Term Interrupt from keyboard
SIGQUIT 3 Core Quit from keyboard
SIGILL 4 Core Illegal Instruction
SIGABRT 6 Core Abort signal from abort(3)
SIGFPE 8 Core Floating point exception
SIGKILL 9 Term Kill signal
SIGSEGV 11 Core Invalid memory reference
SIGPIPE 13 Term Broken pipe: write to pipe with no readers
SIGALRM 14 Term Timer signal from alarm(2)
SIGTERM 15 Term Termination signal
SIGUSR1 30,10,16 Term User-defined signal 1
SIGUSR2 31,12,17 Term User-defined signal 2
SIGCHLD 20,17,18 Ign Child stopped or terminated
SIGCONT 19,18,25 Cont Continue if stopped
SIGSTOP 17,19,23 Stop Stop process
SIGTSTP 18,20,24 Stop Stop typed at terminal
SIGTTIN 21,21,26 Stop Terminal input for background process
SIGTTOU 22,22,27 Stop Terminal output for background process

(1) - Value from 16 onward varies upon architecture. In bold, Linux defaults.
(2) - <Term>ination, <Core> dump, <Ign>ore, Stop.

Check Wikipedia for an extended explanation for each signal.

Also, find them defined in the (architecture dependent) signal.h header file in the Linux kernel code

Signal handling and masking

When a process receives a signal, it can handle it in 3 different ways:

      1) Let the default handler (column “Action” in previous table).
      2) Ignore the signal
      3) Set up a handler to do any desired action.

In bash, signals can be handled withtrap:


trap "echo caught signal SIGTSTP" SIGTSTP
trap "echo caught signal SIGINT " SIGINT
trap "echo caught signal SIGSTOP" SIGSTOP           -->  SIGSTOP can not be "trapped" !!!!
trap "" SIGTERM                                     -->  Will ignore the signal (very ugly for SIGTERM !!)
trap "echo lets do something before exiting" EXIT   -->  Exit is also "trapped" by trap

Ignoring the signal in ‘C’ can be done with the “SIG_IGN” handler in the signal() system call


signal (SIGINT, SIG_IGN);

or with the more modern and informative sigaction() system call:


struct sigaction our_action;

our_action.sa_handler = SIG_IGN;
our_action.sa_flags = SA_RESTART;
sigaction (SIGINT, &our_action, NULL);

Setting our own handler implies defining a handling function and pass it ti signal() or sigaction()


/* Our signal handler */
void our_handler (int sig) {
    printf ("Reveived SIGINT (%d)\n", sig);
    exit(0);
}

For signal():


signal (SIGINT, our_handler);

For sigaction():


struct sigaction our_action;

our_action.sa_handler = our_handler;
our_action.sa_flags = SA_RESTART;
sigaction (SIGINT, &our_action, NULL);

When using sigaction(), the process has to create and inform a sigaction structure:


struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
}

In order to obtain information about the sending process (in ‘siginfo_t’), then the handling function has to be informed in ‘sa_sigaction’.

Exceptions

As commented before for the SIGFPE signal, when a process executes code that the kernel can not handle, it will send a signal to the program. This is also true when there are certain types of a hardware exceptions.

Find how some signals are mapped to hardware exceptions in the “Interrupts and exceptions” section.

A word of warning

Signals can be problematic as they can create race conditions in a single threaded process (a second signal of the same type is sent to the process while is “handling” the first signal). A remediation could be to code the handler to just put the received signal in a queue and return immediately. The program can then check the queue during normal code execution (i.e. in any main loop). In any case, only functions that are async-signal safe should be used inside any signal handler.

Also, if a process has more than one thread, signal handling can be tricky. Standard signals send to a multi-threaded process will be attended by the main (root) thread or, if masked, to only one thread from all threads available. The final behavior can be even harder to predict if threads all also masked.