All about Linux signals
Table of Contents:
- All about Linux signals
- Introduction
- What is signaled, signal handlers
- Handling specific signals: SIGCHLD, SIGBUS, SIGSEGV, SIGABRT
- What happens when a process receives a signal, system call interruption
- Blocking signals
- Waiting for a signal
- Sending signals
- Real-time signals
- Signals and fork()
- Signals and threads
- Other uses of signals
- That's not everything!
What is signaled in Linux
Your process may receive a signal when:
- From user space from some other process when someone calls a function like kill(2).
- When you send the signal from the process itself using a function like abort(3).
- When a child process exits the operating system sends the
SIGCHLDsignal. - When the parent process dies or hangup is detected on the controlling terminal
SIGHUPis sent. - When user interrupts program from the keyboard
SIGINTis sent. - When the program behaves incorrectly one of
SIGILL, SIGFPE, SIGSEGVis delivered. - When a program accesses memory that is mapped using mmap(2) but is not available (for example when the file was truncated by another process) - really nasty situation when using mmap() to access files. There is no good way to handle this case.
- When a profiler like gprof is used the program occasionally receives
SIGPROF. This is sometimes problematic when you forgot to handle interrupting system functions like read(2) properly (errno == EINTR). - When you use the write(2) or similar data sending functions and there is nobody to receive your data
SIGPIPEis delivered. This is a very common case and you must remember that those functions may not only exit with error and setting the errno variable but also cause theSIGPIPEto be delivered to the program. An example is the case when you write to the standard output and the user uses the pipeline sequence to redirect your output to another program. If the program exits while you are trying to send dataSIGPIPEis sent to your process. A signal is used in addition to the normal function return with error because this event is asynchronous and you can't actually tell how much data has been successfully sent. This can also happen when you are sending data to a socket. This is because data are buffered and/or send over a wire so are not delivered to the target immediately and the OS can realize that can't be delivered after the sending function exits.
For a complete list of signals see the signal(7) manual page.
Signal handlers
Traditional signal() is deprecated
The signal(2) function is the oldest and simplest way to install a signal handler but it's deprecated. There are few reasons and most important is that the original Unix implementation would reset the signal handler to it's default value after signal is received. If you need to handle every signal delivered to your program separately like handling
SIGCHLD to catch a dying process there is a race here. To do so you would need to set to signal handler again in the signal handler itself and another signal may arrive before you cal the signal(2) function. This behavior varies across different systems. Moreover, it lacks features present in sigaction(2) you will sometimes need.
The recommended way of setting signal actions: sigaction
The sigaction(2) function is a better way to set the signal action. It has the prototype:
int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);
As you can see you don't pass the pointer to the signal handler directly, but instead a struct sigaction object. It's defined as:
struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); };
For a detailed description of this structure's fields see the sigaction(2) manual page. Most important fields are:
-
sa_handler- This is the pointer to your handler function that has the same prototype as a handler for signal(2). -
sa_sigaction- This is an alternative way to run the signal handler. It has two additional arguments beside the signal number where thesiginfo_t *is the more interesting. It provides more information about the received signal, I will describe it later. -
sa_maskallows you to explicitly set signals that are blocked during the execution of the handler. In addition if you don't use theSA_NODEFERflag the signal which triggered will be also blocked. -
sa_flagsallow to modify the behavior of the signal handling process. For the detailed description of this field, see the manual page. To use thesa_sigactionhandler you must useSA_SIGINFOflag here.
What is the difference between signal(2) and sigaction(2) if you don't use any additional feature the later one provides? The answer is: portability and no race conditions. The issue with resetting the signal handler after it's called doesn't affect sigaction(2), because the default behavior is not to reset the handler and blocking the signal during it's execution. So there is no race and this behavior is documented in the POSIX specification. Another difference is that with signal(2) some system calls are automatically restarted and with sigaction(2) they're not by default.
Example use of sigaction()
See example of using sigaction() to set a signal handler with additional parameters.
In this example we use the three arguments version of signal handler for SIGTERM. Without setting the SA_SIGINFO flag we would use a traditional one argument version of the handler and pass the pointer to it by the sa_handler field. It would be a replacement for signal(2). You can try to run it and do kill PID to see what happens.
In the signal handler we read two fields from the siginfo_t *siginfo parameter to read the sender's PID and UID. This structure has more fields, I'll describe them later.
The sleep(3) function is used in a loop because it's interrupted when the signal arrives and must be called again.
SA_SIGINFO handler
In the previous example
SA_SIGINFO is used to pass more information to the signal handler as arguments. We've seen that the siginfo_t structure contains si_pid and si_uid fields (PID and UID of the process that sends the signal), but there are many more. They are all described in sigaction(2) manual page. On Linux only si_signo (signal number) and si_code (signal code) are available for all signals. Presence of other fields depends on the signal type. Some other fields are:-
si_code- Reason why the signal was sent. It may beSI_USERif it was delivered due to kill(2) or raise(3),SI_KERNELif kernel sent it and few more. For some signals there are special values likeILL_ILLADRtelling you thatSIGILLwas sent due to illegal addressing mode. - For
SIGCHLDfieldssi_status, si_utime, si_stimeare filled and contain information about the exit status or the signal of the dying process, user and system time consumed. - In case of
SIGILL, SIGFPE, SIGSEGV, SIGBUSsi_addrcontains the memory address that caused the fault.
We'll see more examples of use of siginfo_t later.
Compiler optimization and data in signal handler
Let's see the following example:
#include <stdio.h> #include <unistd.h> #include <signal.h> #include <string.h> static int exit_flag = 0; static void hdl (int sig) { exit_flag = 1; } int main (int argc, char *argv[]) { struct sigaction act; memset (&act, '\0', sizeof(act)); act.sa_handler = &hdl; if (sigaction(SIGTERM, &act, NULL) < 0) { perror ("sigaction"); return 1; } while (!exit_flag) ; return 0; }
What it does? It depends on compiler optimization settings. Without optimization it executes a loop that ends when the process receives SIGTERM or other sgnal that terminates the process and was not handler. When you compile it with the -O3 gcc flag it will not exit after receiving SIGTERM. Why? because whe while loop is optimized in such way that the exit_flag variable is loaded into a processor register once and not read from the memory in the loop. The compiler isn't aware that the loop is not the only place where the program accesses this variable while running the loop. In such cases - modifying a variable in a signal handler that is also accessed in some other parts of the program you must remember to instruct the compiler to always access this variable in memory when reading or writing them. You should use the volatile keyword in the variable declaration:
static volatile int exit_flag = 0;
After this change everything works as expected.
Atomic Type
There is one data type defined that is guaranteed to be atomically read and written both in signal handlers and code that uses it:
sig_atomic_t. The size of this type is undefined, but it's an integer type. In theory this is the only type you can safely assign and read if it's also accessed in signal handlers. Keep in mind that:
- It doesn't work like a mutex: it's guaranteed that read or write of this type translates into an uninterruptible operation but code such as:
sig_atomic_t i = 0; void sig_handler (int sig) { if (i++ == 5) { // ... } }
Isn't safe: there is read and update in the if operation but only single reads and single writes are atomic.
- Don't try to use this type in a multi-threaded program as a type that can be used without a mutex. It's only intended for signal handlers and has nothing to do with mutexes!
- You don't need to worry if data are modified or read in a signal handler are also modified or read in the program if it happens only in parts where the signal is blocked. Later I'll show how to block signals. But you will still need the
volatilekeyword.
Signal-safe functions
You can't just do anything in a signal handler. Remember that your program is interrupted, you don't know at which point, which data objects are in the middle of being modified. It may be not only your code, but a library you are using or the standard C library. In fact there is a quite short list of function you can safely call from a signal handler in signal(7). You can for example open a file with open(2), remove a file with unlink(2), call _exit(2) (but not exit(3)!) and more. In practice this list is so limited that the best you can do is to just set some global flag to notify the process to do something like exiting. On the other hand the wait(2) and waitpid(2) functions can be used, so you can cleanup dead processes in
SIGCHLD, unlink(2) is available, so you can delete a pid file etc.
Alternative method of handling signals: signalfd()
signalfd(2) is a quite new Linux specific call available from the 2.6.22 kernel that allows to receive signals using a file descriptor. This allows to handle signals in a synchronous way, without providing handler functions. Let's see an example of signalfd() use
First we must block the signals we want to handle with signalfd(2) using sigprocmask(2). This function will be described later. Then we call signalfd(2) to create a file descriptor that will be used to read incoming signals. At this point in case of SIGTERM or SIGINT delivered to your program it will not be interrupted, no handler will be called. It will be queued and you can read information about it from the sfd descriptor. You must supply a buffer large enough to read the struct signalfd_siginfo object that will be filled with information similar to the previously described siginfo_t. The difference is that the fields are named a bit different (like ssi_signo instead of si_signo). What is interesting is that the sfd descriptor behaves and can be used just like any other file descriptor, in particular you can:
- Use it in select(2), poll(2) and similar functions.
- Make it non-blocking.
- Create many of them, each handling different signals to return different descriptors as ready by select(2) for every different signal.
- After fork() the file descriptor is not closed, so the child process can read signals that were send to the parent.
This is perfect to be used in a single-process server with the main loop executes a function like poll(2) to handle many connections. It simplifies signal handling because the signal descriptor can be added to the poll's array of descriptors and handled like any other of them, without asynchronous actions. You handle the signal when you are ready for that because your program is not interrupted.


Hi, typo jerk here
Thanks. I don't consider it
I was looking for linux
Great write-up
Signals - That's not everything