Unix: Dealing with signals

Signals on Unix systems are critical to the way processes live and die. This article looks at how they're generated, how they work, and how processes receive or block them.

Unix: Dealing with signals
Caesararum (CC BY 2.0)

On Unix systems, there are several ways to send signals to processes—with a kill command, with a keyboard sequence (like control-C), or through your own program (e.g., using a kill command in C). Signals are also generated by hardware exceptions such as segmentation faults and illegal instructions, timers and child process termination.

But how do you know what signals a process will react to? After all, what a process is programmed to do and able to ignore is another issue.

Fortunately, the /proc file system makes information about how processes handle signals (and which they block or ignore) accessible with commands like the one shown below. In this command, we’re looking at information related to the login shell for the current user, the "$$" representing the current process.

$ cat /proc/$$/status | grep Sig
SigQ:   0/15432
SigPnd: 0000000000000000
SigBlk: 0000000000010000
SigIgn: 0000000000380004
SigCgt: 000000004b817efb

Interpreting the data is a little tricky, but not too much so. So, what does any of this mean? First, let’s look at the definitions of each of these fields.

  • SigQ – Two slash-separated numbers that relate to queued signals for the real user ID of this process
  • SigPnd – Number of pending signals for the thread and the process as a whole
  • SigBlk – Signals being blocked
  • SigIgn – Signals being ignored
  • SigCgt – Signals being caught

Next, let’s dig a little deeper. Looking at just the signals caught, the first thing we need to do is convert the hexadecimal reported value to binary. You can do that with the xxd command:

$ echo 4b817efb | xxd -r -p | xxd -b
00000000: 01001011 10000001 01111110 11111011

This binary interpretation of the value allows you to pick out the individual signals, especially if you’re reasonably comfortable with binary numbers. Let’s break it down:

01001011 10000001 01111110 11111011
 |  | || |      |  ||||||  ||||| ||
 |  | || |      |  ||||||  ||||| |+--  1 SIGHUP (hangup)
 |  | || |      |  ||||||  ||||| +---  2 SIGINT (interrupt)
 |  | || |      |  ||||||  ||||+-----  4 SIGILL (illegal instruction)
 |  | || |      |  ||||||  |||+------  5 SIGTRAP (trace/trap)
 |  | || |      |  ||||||  ||+-------  6 SIGABRT (abort)
 |  | || |      |  ||||||  |+--------  7 SIGEMT (emulation trap)
 |  | || |      |  ||||||  +---------  8 SIGFPE (floating point exception)
 |  | || |      |  |||||+------------ 10 SIGBUS (bus error)
 |  | || |      |  ||||+------------- 11 SIGSEGV (segmentation violation)
 |  | || |      |  |||+-------------- 12 SIGSYS (bad system call)
 |  | || |      |  ||+--------------- 13 SIGPIPE (broken pipe)
 |  | || |      |  |+---------------- 14 SIGALRM (alarm)
 |  | || |      |  +----------------- 15 SIGTERM (termination)
 |  | || |      +-------------------- 17 SIGUSR2 (user signal 2)
 |  | || +--------------------------- 24 SIGTSTP (stop -- can be ignored)
 |  | |+----------------------------- 25 SIGCONT (continue)
 |  | +------------------------------ 26 SIGTTIN (terminal input)
 |  +-------------------------------- 28 SIGVTALRM (timer expiration)
 +----------------------------------- 31 SIGXFSZ (file size exceeded)

Catching a signal requires that a signal handling function exists in the process to handle a given signal. The SIGKILL (9) and SIGSTOP (#) signals cannot be ignored or caught. For example, if you wanted to tell the kernel that ctrl-C's are to be ignored, you would include something like this in your source code:

signal(SIGINT, SIG_IGN);

To ensure that the default action for a signal is taken, you would do something like this instead:

signal(SIGSEGV, SIG_DFL);

The SigIgn (signal ignore) settings from the example above show that only four signals — 3 (SIGQUIT) and 20-22 (SIGWINCH, SIGURG, and SIGPOLL) are set to be ignored, while the SigBlk (signal block) settings block only the SIGPIPE

$ echo 380004 | xxd -r -p | xxd -b
00000000: 00111000 00000000 00000100 
$ echo 10000 | xxd -r -p | xxd -b
00000000: 00010000 00000000 

The same type of data for a watchdog process looks very different. Notice that it’s ignoring all signals.

# cat /proc/11/status | grep Sig
SigQ:   0/15432
SigPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: ffffffffffffffff
SigCgt: 0000000000000000

Sending signals from command line

On the command line, there are as many ways to send signals to Unix processes as there are ways to skin a cat (actually more). Remember that “kill” doesn’t always mean termination, as the signals can do many things.

Killing by process ID (sends a SIGTERM)
kill 1234
Killing by process name *sends a SIGTERM)
pkill someproc

Killing more forcefully (sends a SIGKILL) — all do the same thing.

kill -9 1234
kill -KILL 1234
kill -SIGKILL 1234
Sending a specific signal — in this case SIGHUP
kill -HUP 1234
kill -SIGHUP 1234

You can send any signal in this way. Refer to the signal list below.

Listing signals
$ 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

Wrap-Up

On Unix systems, signals are used to send all kinds of information to running processes, and they come from user commands, other processes, and the kernel itself. Through /proc, information about how processes are handling systems is now easily accessible and, with just a little manipulation of the data, easy to understand.

Join the Network World communities on Facebook and LinkedIn to comment on topics that are top of mind.
Related:
Now read: Getting grounded in IoT