Signals in Linux. A signal is one of the basic things you... | by Piyush Deshmukh | Towards Data Science

I am writing this post to be a reference for my notes. I hope sharing this post would be beneficial to some of you who are interested in understanding Linux. If you find any mistakes or typos, comment them and I will correct it asap. Thanks and keep learning.
A signal is one of the basic things you would have frequently encountered once you start getting familiar with a Linux system. Getting to know about how they work is important since signals play a big role in processes.
As usual, we will start off with some basics and then move on to some advanced topics.
A signal is a notification to a process that an event has occurred. Signals are sometimes described as software interrupts, because in most cases, they interrupt the normal flow of execution of a program and their arrival is unpredictable.
A signal can be sent by Kernel or a Process.
For the rest of the article, we will focus on signals sent by the Kernel and not by processes.
A Kernel may send a signal to a process in the following scenarios:
When a hardware exception has occurred and that exception needs to be notified to the process. For eg. Attempting division by zero, or referencing the part of memory that is inaccessible.Some software event occurred outside the process’s control but effects the process. For instance, input became available on a file descriptor, the terminal window got resized, process’s CPU time limit exceeded, etc.User typed some terminal special characters like interrupt(Ctrl+C) or suspend character(Ctrl+Z).
When a hardware exception has occurred and that exception needs to be notified to the process. For eg. Attempting division by zero, or referencing the part of memory that is inaccessible.
Some software event occurred outside the process’s control but effects the process. For instance, input became available on a file descriptor, the terminal window got resized, process’s CPU time limit exceeded, etc.
User typed some terminal special characters like interrupt(Ctrl+C) or suspend character(Ctrl+Z).
The signals sent by the Kernel to notify processes about events are called traditional signals.
After a signal is generated due to some event, it does not directly get delivered to a process, and the signal remains in an intermediate state called to a pending state. This is required in scenarios such as:
Process is not scheduled to have CPU right now. In such a case, a pending signal is delivered as soon as the process is next scheduled to run.
From the process’s point of view, to ensure that a signal does not arrive during execution of some critical section, a process can add signal to its process’s signal mask, which is a set of signals whose delivery is currently blocked. Process’s signal mask is a per process attribute. If a signal is generated when it is blocked, it remains in pending state until later it is unblocked. There are various system calls that allow a process to add and remove signals from its signal mask.
When a signal is about to get delivered, one of the following default actions take place depending on the signal:
The signal is ignored, i.e., it is discarded by the kernel and has no effect on the process. (The process remains unaware that the event had even occurred.)The process is terminated, a.k.a. abnormal process termination, as opposed to normal process termination that occurs when the program terminates using exit().A core dump file is generated and the process is terminated.The execution of the process is suspended or resumed.
The signal is ignored, i.e., it is discarded by the kernel and has no effect on the process. (The process remains unaware that the event had even occurred.)
The process is terminated, a.k.a. abnormal process termination, as opposed to normal process termination that occurs when the program terminates using exit().
A core dump file is generated and the process is terminated.
The execution of the process is suspended or resumed.
Instead of accepting the default actions of a particular signal, a process can set the disposition of the signal by changing the action that occurs when the signal is delivered. A program can set one of the following dispositions:
The default action should occur. This useful to undo the earlier change of the disposition of the signal to something other than its default.The signal is ignored instead of the default action that would be to terminate the process.An established signal handler is executed. A signal handler is a custom-tailored function that performs appropriate tasks in response to the delivery of a signal. Notifying the kernel that a handler function should be invoked when signal arrives, is known as establishing or installing a signal handler. It is not possible to set the disposition of a signal to terminate or dump core unless one of these is the default disposition of the signal.
The default action should occur. This useful to undo the earlier change of the disposition of the signal to something other than its default.
The signal is ignored instead of the default action that would be to terminate the process.
An established signal handler is executed. A signal handler is a custom-tailored function that performs appropriate tasks in response to the delivery of a signal. Notifying the kernel that a handler function should be invoked when signal arrives, is known as establishing or installing a signal handler. It is not possible to set the disposition of a signal to terminate or dump core unless one of these is the default disposition of the signal.
The signals SIGKILL and SIGSTOP cannot be caught, blocked or ignored.
A signal can be sent using either the kill system call, or the kill command, and specifying the desired pid of the process.
We use the term ‘kill’ because the default action of most of the signals to terminate a process.
The process pid passed to kill is interpreted according to the following cases:
If pid > 0, then the signal is sent to a particular process with the specified pid.If pid = 0, then the signal is sent to every process in the same process group.If pid < -1, then the signal is sent to every process in the process group whose process group id is modulus of |pid| specified.If pid = -1, then the signal is sent to all the processes for which the calling process has permission to send a signal, except init and the calling process itself. Signals sent in this fashion are called Broadcast Signals.
If pid > 0, then the signal is sent to a particular process with the specified pid.
If pid = 0, then the signal is sent to every process in the same process group.
If pid < -1, then the signal is sent to every process in the process group whose process group id is modulus of |pid| specified.
If pid = -1, then the signal is sent to all the processes for which the calling process has permission to send a signal, except init and the calling process itself. Signals sent in this fashion are called Broadcast Signals.
There also exists another function raise() that sends a signal to the calling process itself. The signal is sent as such that the signal is received by the process even before the raise function returns. Consider this as a local loopback analogy from IP networks.
For single-threaded implementations, int raise(int sig) is implemented as kill(getpid(), sig) .
For multi-threaded implementations, int raise(int sig) is implemented as pthread_kill(pthread_self(), sig) .
One interesting use case of sending a signal is to check the existence of a process. If the kill() system call is called with signal argument as ‘0’, a.k.a. null signal, then no signal is sent, but it merely performs error checking to see if the process can be signaled.
It means that we can use this mechanism to check the existence of a process. When a null signal is attempt to be sent, one of the following responses is possible:
If error ESRCH occurs, then this means the target process does not exist.If error EPERM occurs, then this means the target process exists, but you (the caller process) don’t have enough permissions to send a signal to the process.If the call succeeds, then this means that the target process exists and the caller has permissions to send it signals.
If error ESRCH occurs, then this means the target process does not exist.
If error EPERM occurs, then this means the target process exists, but you (the caller process) don’t have enough permissions to send a signal to the process.
If the call succeeds, then this means that the target process exists and the caller has permissions to send it signals.
To get signal description by corresponding to a particular signal number, we can use strsignal function. Besides this, you can check the signal’s man page.
The signals described in the original POSIX.1–1990 standard are listed below.
   Signal     Value     Action   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
A group of signals is represented using a signal set. Signal sets can be used by APIs like sigaction() and sigprocmask() which allow a program to specify a group of signals.
If a process receives a signal that is currently blocking, then it is added to Pending Signal set of that process. To determine which signals are pending for a process, a process can use int sigpending(sigset_t* set); system call, which returns the set of signals that pending for the calling process by modifying the set structure.
Standard signals are not queued, so when a signal arrives multiple times, it is added to pending signal set (which, of course, has just one copy of it), and this is later delivered only once when the signal is unblocked.
A signal is a notification to a process that an event has occurred, and can be sent by the kernel, another process, or by itself. The delivery of a signal is asynchronous. You can send a signal to a process using the kill system call. The behavior of a process when a signal arrives, can be modified by establishing a signal handler using signal() or sigaction() .
Standard Signals are not queued, that is, multiple signals sent to a process will be retrieved only as one.
A signal set is used to work on multiple signals together. Many system calls accept signal set.
When a signal arrives, a process can choose to block it, and at later time, can fetch the set of pending signals using the sigpending() system call, that will give a set of signals in the pending state. Using the pause() system call, a process can suspend the execution until a signal arrives.