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.