Signals in Linux

Timeout

1.0 Signals

Signals are software interrupts that are delivered to a process by the kernel. A signal indicates that an event has occurred and the process must take note of it. A signal is delivered asynchronously to a process. Whatever the process was doing is suspended and the processing of the signal takes place immediately. Once a signal is received by a process, there are three possibilities. If the process has not made any arrangement for the signal, the default action for the signal happens, which is often to abort or terminate the process. There are other possibilities for the default action for a signal, viz., to ignore the signal, or to stop the process, or to continue after stopping the process. Second, some signals can be ignored and the process could have specified to ignore the signal. Thirdly, the process could have specified a handler for the signal. When that signal comes, the specified handler is executed and then the processing resumes, if the process had not been terminated by the signal handler.

2.0 List of Signals

The signals in Linux are #defined in <signal.h>. The list is

Linux signals
Signal Description Default action
SIGHUP Hangup Terminate
SIGINT Interrupt (generated from the keyboard, by pressing CTRL-C) Terminate
SIGQUIT Quit (generated from the keyboard, by pressing CTRL-\) Abort
SIGILL Illegal instruction Abort
SIGTRAP Trace/breakpoint trap Abort
SIGABRT Call to abort () function Abort
SIGIOT Synonym for SIGABRT Abort
SIGBUS Attempt to access to invalid memory address Abort
SIGFPE Fatal arithmetic error Abort
SIGKILL Kill generated synthetically, by the kill -9 pid command Terminate
SIGUSR1 User signal 1, generated synthetically Terminate
SIGSEGV Invalid virtual memory reference Abort
SIGUSR2 User signal 2, generated synthetically Terminate
SIGPIPE Broken pipe. Write to pipe when there is no process on the other end to read. Terminate
SIGALRM Alarm clock timer expired. terminate
SIGTERM Termination signal, sent synthetically by the kill pid command. Terminate
SIGSTKFLT Stack fault on coprocessor (unused) Terminate
SIGCHLD Child process stopped or terminated. Ignored
SIGCONT Continue executing (from keyboard) Continue
SIGSTOP Stop executing (synthetic) Stop
SIGTSTP Stop executing (from keyboard) Stop
SIGTTIN Background process, trying to read Stop
SIGTTOU Background process, trying to write Stop
SIGURG Urgent condition on socket. Ignore
SIGXCPU CPU time limit exceeded Abort
SIGXFSZ File size limit exceeded Abort
SIGVTALRM Virtual timer expired. Terminate
SIGPROF Profiling timer expired Terminate
SIGWINCH Window resize signal Terminate
SIGIO I/O is now possible Terminate
SIGPOLL Pollable event Terminate
SIGPWR Power failure Terminate
SIGSYS Bad arguments to system call Abort
SIGUNUSED Synonym of SIGSYS Abort

Signals are generated naturally whenever the associated event occurs. For example, when a process tries to access memory outside the physical memory, SIGBUS is generated. Alternatively, any signal can be generated synthetically by using one of the six system calls or library functions, kill, killpg, pthread_kill, raise, sigqueue or abort. The default actions terminate and abort are almost the same except that in case of abort, a core-dump file is written.

The signals, SIGKILL and SIGSTOP, cannot be caught, blocked or ignored. For these signals, the default action has to happen.

A signal can be delivered to an entire process or to a specific thread in a process. However, if a process has multiple threads and a signal is delivered to the process (and, not to a specific thread), it is not defined that which thread will receive that signal. The action for a signal (the default action, whether ignoring the signal or the signal handler) is for the entire process, even if the process has multiple threads.

3.0 Blocking signals

Signals are generated and, then, delivered. After generation, and, before delivery, a signal is pending. There are times when we do not want our process to be interrupted with a signal. The process may be in the middle of updating a critical data structure and a signal handler running at that time could result in making the data structure inconsistent. So, a thread can block signals. When a signal is blocked and is generated for a process, it is kept in the pending state and, once the signal is unblocked, it is delivered to the process.

3.1 Signal Mask

The signal mask for a thread is the set of its currently blocked signals. You can set the signal mask for a thread with the pthread_sigmask function. If there is only one thread in a process (the main thread), you can set the signal mask for it with the sigprocmask system call. It is important to note that the signal mask is always for a thread.

3.2 SIGSETOPS - signal set operations functions

#include <signal.h> int sigemptyset (sigset_t *set); int sigfillset (sigset_t *set); int sigaddset (sigset_t *set, int signum); int sigdelset (sigset_t *set, int signum); int sigismember (const sigset_t *set, int signum);

sigemptyset sets the signal set pointed by set to an empty set. sigfillset adds all the signals to the set. sigaddset adds the signal signum to the set whereas sigdelset deletes the signum from it. sigismember checks whether signum is in the set. Next, we look at the system calls for setting the signal mask.

#include <signal.h> int pthread_sigmask (int how, const sigset_t *set, sigset_t *oldset); // linking with -pthread is required int sigprocmask (int how, const sigset_t *set, sigset_t *oldset);

pthread_sigmask is for changing the signal mask for a thread. The first parameter tells how the signal mask is to be changed. The how parameter could be SIG_BLOCK, which means the new signal mask should include the signals in the set. Or, how could be SIG_UNBLOCK, which means that the new signal mask should exclude the signals in the set. The third possible value for how is SIG_SETMASK, which results in the set becoming the new signal mask, replacing the earlier one. pthread_sigmask returns 0 on success and error number on failure. Like all pthread functions, errno is not set on failure.

sigprocmask is the older call for changing the signal mask for the singleton thread of a single threaded process. The parameters are the same as that of pthread_sigmask. However, there is a difference in the return value. sigprocmaskreturns 0 on success and -1 on failure and errno is set on failure. Use of pthread_sigmask requires linking the program with the pthread library. So, if you are not using threads and not linking with the pthread library, sigprocmask is the call for changing the signal mask.

4.0 Setting the signal action

You can set the action for a signal for your process with the sigaction system call.

#include <signal.h> struct sigaction { void (*sa_handler) (int); void (*sa_sigaction) (int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer) (void); }; int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);

The sigaction system call installs a signal handler for the signal signum for the calling process. signum can be any valid signal except SIGKILL and SIGSTOP. If oldact is not NULL, the previous action is returned in oldact. If act is not NULL, the signal handler is installed from act. Looking at the struct sigaction, the first two parameters are pointers, sa_handler and sa_sigaction, which can be used for specifying the signal handler. These two are mutually exclusive; only one has to be used. Also, the actual implementation might be using a union of two; so we must not make the other zeros. If SA_SIGINFO is specified in sa_flags, then sa_sigaction is being used, else sa_handler is used. sa_mask is the set of signals that should be blocked in the thread in which the signal handler is called. These signals and the signal for which the signal handler is called are added to the signal mask of the thread executing the signal handler. After the signal handler exits, the original signal mask for the thread is restored. sa_flags modify the behavior of the signal. It is bitwise OR of zero or more flags. As mentioned before, the flag SA_SIGINFO indicates that sa_sigaction field is being used in struct sigaction instead of the sa_handler field. SA_NODEFER flag means that the signal for which this handler will be invoked should not be masked during the execution of the handler. Flag SA_NOCLDSTOP is meaningful for the SIGCHILD signal and means that SIGCHILD signal should not be delivered when a child stops or starts. Similarly, SA_NOCLDWAIT flag is also for the SIGCHILD signal and means that terminating children should not be turned into zombies. Flag SA_RESTART implies that if the signal comes when the process is blocked in a system call like read, the call should be restarted after the signal handler returns. The sa_restorer field is not meant to be used by application programs.

The parameter sa_handler may have the value SIG_DFL or SIG_IGN or it may be a pointer to the signal handler function. If the value is SIG_DFL, it means that the default action for the signal is to be restored. Similarly, if the value is SIG_IGN, the signal will, henceforth, be ignored. If a function pointer is passed, that function is installed as the handler for the signum signal. The signal handler function has a prototype like,

void signal_handler_fcn (int signum);

If SA_SIGINFO is specified in sa_flags, then sa_sigaction is to be used instead of the sa_handler. sa_sigaction is the pointer to the signal handler to be installed for the signum signal. This signal handling function has a prototype like,

void signal_handler_fn (int signum, siginfo_t *siginfo, void *context);

The first parameter, signum is the signal number for which this function is to be installed as a handler. The last parameter, pointer, is not to be used by the handler function. The second parameter is a pointer to a structure like,

siginfo_t { int si_signo; /* Signal number */ int si_errno; /* An errno value */ int si_code; /* Signal code */ int si_trapno; /* Trap number that caused hardware-generated signal (unused on most architectures) */ pid_t si_pid; /* Sending process ID */ uid_t si_uid; /* Real user ID of sending process */ int si_status; /* Exit value or signal */ clock_t si_utime; /* User time consumed */ clock_t si_stime; /* System time consumed */ sigval_t si_value; /* Signal value */ int si_int; /* POSIX.1b signal */ void *si_ptr; /* POSIX.1b signal */ int si_overrun; /* Timer overrun count; POSIX.1b timers */ int si_timerid; /* Timer ID; POSIX.1b timers */ void *si_addr; /* Memory location which caused fault */ long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */ int si_fd; /* File descriptor */ short si_addr_lsb; /* Least significant bit of address (since Linux 2.6.32) */ void *si_call_addr; /* Address of system call instruction (since Linux 3.5) */ int si_syscall; /* Number of attempted system call (since Linux 3.5) */ unsigned int si_arch; /* Architecture of attempted system call (since Linux 3.5) */ }

The first three members, si_signo, si_errno and si_code are there for all signals. si_signo is the signal number which has been delivered and has triggered this signal handler, si_errno is the error number associated with the signal and is not used in Linux. si_code is a value indicating why this signal was sent. About the rest of the fields, certain subsets are used for particular signals. It is important to use only the relevant fields for a signal because, in reality, the above structure might be having unions as members and the space for others fields might not really be there.

5.0 Generating signals synthetically

Signals are generated due to natural causes like a process might be trying to access non-existent memory, or there might be a divide by zero error, etc. But we can also generate signals synthetically by using certain system calls (or, the commands built using these system calls), and send these signals to relevant processes. Let's look at these system calls.

5.1 kill

#include <sys/types.h> #include <signal.h> int kill (pid_t pid, int sig);

The kill system call is for sending the signal sig to the process identified by pid. A process can send a signal to another if it is a privileged process or its effective user id is equal to the real user id of the receiving process. If the pid is positive, the signal is sent to the process with that pid. If pid is zero, the signal is sent to every process in the process group of the calling process. If pid is -1, the signal is sent to every process to which the calling process has permission to send signals, except the init process (pid 1). If pid is less than -1, the signal is sent to every process in the process group whose id is -pid.

5.2 killpg

#include <signal.h> int killpg (int pgrp, int sig);

killpg sends the sig signal to the process group identified by pgrp. If pgrp is zero, killpg sends the signal to the calling processes's process group.

5.3 pthread_kill

#include <signal.h> int pthread_kill (pthread_t thread, int sig);

pthread_kill sends the signal sig to the thread identified by the first parameter. The receiving thread must be a part of the calling process. Now, if the signal is for terminate, it will terminate the calling process and not just the thread because the effect of signals is for the entire process. If the intention is to terminate a thread, pthread_cancel should be used instead.

5.4 raise

#include <signal.h> int raise (int sig);

raise is a C function for sending a signal to the calling thread. That is, the calling thread sends the signal to itself.

5.5 sigqueue

#include <signal.h> union sigval { int sival_int; void *sival_ptr; }; int sigqueue (pid_t pid, int sig, const union sigval value);

The sigqueue function sends the signal sig to the process identified by pid. It also sends data, value, along with the signal. The value is a union of an integer and a pointer. If the receiving process has installed a signal handler using the SA_SIGINFO flag, it can get the value using the si_value field of the siginfo_t structure.

5.6 abort

#include <stdlib.h> void abort (void);

The abort function generates the SIGABRT signal for the calling process and causes its own abnormal termination. abort never returns. It first unblocks the SIGABRT signal, then raises the signal for the calling process. If SIGABRT signal is being ignored, it first resets the default action for SIGABRT and raises the SIGABRT signal the second time. All streams are closed and flushed and the process terminates.

6.0 sigwait system call

The sigwait system call waits for one of the signals specified in the first parameter, set.

#include <signal.h> int sigwait (const sigset_t *set, int *sig);

sigwait suspends the execution of the calling thread until one of the signals specified in set becomes pending. When that happens, that signal is accepted and sigwait returns. The accepted signal is pointed by sig. On success, sigwait returns zero. In case of error, a positive integer indicating the error is returned.

7.0 Signal handlers

The difficulty in programming signal handlers emanates from the fact that a signal can come at any time and the programmer does know about the state of the process at the time of its execution. A process might be in the middle of some library routine and if, by chance, you use the same function in signal handler, something erroneous might happen as the code might not be re-entrant. So, there are restrictions on what functions can be used in a signal handler. You cannot use printf, exit, etc. The complete list of safe functions that can be used in a signal handler is given in the manual (printed by the man 7 signal command). Also, you cannot safely use a global variable unless it is the type volatile sig_atomic_t.

7.1 Signal handler - an example

/* basic-signals.c: basic signal handling */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <errno.h> #include <unistd.h> #include <signal.h> void syserror (const char * const str); void sig_handler (int signum); int main (int argc, char **argv) { /* handle signals */ sigset_t set; if (sigfillset (&set) == -1) syserror ("sigfillset"); // block all signals for a while if (sigprocmask (SIG_SETMASK, &set, NULL) == -1) syserror ("sigprocmask"); struct sigaction act; memset (&act, 0, sizeof (act)); // ignore these signals act.sa_handler = SIG_IGN; if (sigaction (SIGHUP, &act, NULL) == -1) syserror ("sigaction"); if (sigaction (SIGPIPE, &act, NULL) == -1) syserror ("sigaction"); // set signal handler for following signals act.sa_handler = sig_handler; if (sigaction (SIGINT, &act, NULL) == -1) syserror ("sigaction"); if (sigaction (SIGQUIT, &act, NULL) == -1) syserror ("sigaction"); if (sigaction (SIGILL, &act, NULL) == -1) syserror ("sigaction"); if (sigaction (SIGBUS, &act, NULL) == -1) syserror ("sigaction"); if (sigaction (SIGFPE, &act, NULL) == -1) syserror ("sigaction"); if (sigaction (SIGSEGV, &act, NULL) == -1) syserror ("sigaction"); if (sigaction (SIGTERM, &act, NULL) == -1) syserror ("sigaction"); if (sigaction (SIGXCPU, &act, NULL) == -1) syserror ("sigaction"); if (sigaction (SIGXFSZ, &act, NULL) == -1) syserror ("sigaction"); if (sigaction (SIGSYS, &act, NULL) == -1) syserror ("sigaction"); // reset the signal mask if (sigemptyset (&set) == -1) syserror ("sigemptyset"); if (sigprocmask (SIG_SETMASK, &set, NULL) == -1) syserror ("sigprocmask"); // rest of the code while (1) sleep (10); } void sig_handler (int signum) { switch (signum) { case SIGINT: write (2, "User pressed CTRL-C.\n", 21); return; case SIGQUIT: write (2, "User pressed CTRL-\\.\n", 21); return; case SIGILL: write (2, "Illegal instruction. Terminating.\n", 34); _exit (EXIT_FAILURE); case SIGBUS: write (2, "Bus error. Terminating.\n", 24); _exit (EXIT_FAILURE); case SIGFPE: write (2, "Illegal arithmetic operation. Terminating.\n", 43); _exit (EXIT_FAILURE); case SIGSEGV: write (2, "Segment violation. Terminating.\n", 32); _exit (EXIT_FAILURE); case SIGTERM: write (2, "Termination signal received. Bye.\n", 34); _exit (EXIT_FAILURE); case SIGXCPU: write (2, "CPU time limit exceeded. Terminating.\n", 38); _exit (EXIT_FAILURE); case SIGXFSZ: write (2, "File size limit exceeded. Terminating.\n", 39); _exit (EXIT_FAILURE); case SIGSYS: write (2, "Bad system call. Terminating.\n", 30); _exit (EXIT_FAILURE); default: write (2, "Unexpected signal caught. Terminating.\n", 39); _exit (EXIT_FAILURE); } } void syserror (const char * const str) { perror (str); exit (EXIT_FAILURE); }

In the above example, the write and _exit system calls are used in the signal handler. These calls are a part of the list of functions that can be safely used in a signal handler.

Software: