dcsimg

A Walk Through Some of the Most Popular Linux Signals

My last two columns talked about the Linux (and POSIX) signal API and how to use it to write programs that handle signals. Now that you understand what signals are and how they can be manipulated by your application, I want to talk about some of the most commonly used signals. For each signal, I'll describe when the signal is sent, what the default action is, and how most programs handle it. Remember that the type of action a particular signal should invoke is mostly a convention. Users will expect a certain behavior, but programs can do pretty much whatever they like.

My last two columns talked about the Linux (and POSIX) signal API and how to use it to write programs that handle signals. Now that you understand what signals are and how they can be manipulated by your application, I want to talk about some of the most commonly used signals. For each signal, I’ll describe when the signal is sent, what the default action is, and how most programs handle it. Remember that the type of action a particular signal should invoke is mostly a convention. Users will expect a certain behavior, but programs can do pretty much whatever they like.

Terminating Processes

SIGABRT: The abort signal is normally sent by a program when it wants to terminate itself and leave a core dump around. This can be handy during debugging and when you want to check assertions in code. The easiest way for a process to send a SIGABRT to itself is via the (ANSI-C) abort() function, which does nothing more than a (also ANSI-C) raise(SIGABRT), which, in turn, does a (not ANSI-C) kill(getpid(), SIGABRT). Programs normally drop a core file and exit when receiving this signal.

C’s built-in assertion facility, cleverly named assert(), calls abort() when an assertion fails, as do many memory-debugging libraries. Doing this rather then a simple exit() gives the developer a core file to work with and also allows debuggers to catch the signal and let the developer poke around.

SIGQUIT: Like SIGABRT, SIGQUIT normally causes a program to quit and dump core. The kernel sends a process SIGQUIT whenever the user presses the quit key sequence, normally “Ctrl-\” on the program’s controlling terminal. Like SIGABRT, this can be handy during debugging.

SIGTERM:This signal is sent when the user wants a program to terminate gracefully. By default, the program exits immediately, but this signal is often caught, and the program performs a graceful exit as quickly as possible. The kill command sends this signal by default, and many users know it as “signal 15.”

SIGKILL: This is one of the few signals that can’t be caught or blocked; when a process is sent SIGKILL, it never runs again. This is signal 9, as in the ever-popular kill -9. The only time a SIGKILL won’t terminate a process immediately is when a process is running inside of a “fast” system call; it will wait for that system call to finish. If the fast system call is hung for some reason, the process can take quite a while to exit.

Stopping and Restarting Processes

SIGSTOP: When a process receives SIGSTOP, it stops running. It can’t ever wake itself up (because it isn’t running!), so it just sits in the stopped state until it receives a SIGCONT. The kernel never sends a SIGSTOP automatically; it isn’t used for normal job control. This signal cannot be caught or ignored; it always stops the process as soon as it’s received.

SIGCONT: When a stopped process receives SIGCONT, it starts running again. This signal is ignored by default for processes that are already running. SIGCONT can be caught, allowing a program to take special actions when it has been restarted. Ignoring this signal doesn’t prevent a process from being restarted; it just keeps the process from performing any actions when it is restarted. Blocking SIGCONT delays when the process handles the signal, but the process is restarted immediately if it had been stopped.

SIGTSTP: SIGTSTP is sent to a process when the suspend keystroke (normally ^Z) is pressed on its controlling tty, and it’s running in the foreground. The exact rules are a bit complicated. By default, a process is stopped when it receives SIGTSTP, but the signal can be caught. When it is caught, the process doesn’t stop.

To fix this, use a signal handler like this:


void catch_my_sigtstp(int signo) {
various_prep_stuff_for_suspending();
signal(SIGTSTP, SIG_DFL);
raise(SIGTSTP);
}

This will let your program suspend properly and still handle the signal. This is a better idea then simply doing raise(SIGSTOP), since most shells display a message explaining why a process was stopped, and it looks a bit odd if a program stops for the wrong reason. If you use this technique, be sure to catch SIGCONT and replace the SIGTSTP signal handler as well.

Signaling Programs That Violate System Rules

SIGFPE:This signal is sent when a program tries to perform an illegal mathematical operation, such as divide-by-0. The signal means “Floating Point Error,” but it can be sent for integer divide-by-zero as well.

SIGBUS:While a variety of things can result in SIGBUS, the most common are:

1. Hardware Errors. Needless to say, there isn’t much that the programmer can do about these.

2. Out-of-memory Situations.Rather then have malloc() fail, Linux prefers to send a SIGBUS when a process doesn’t have enough RAM. There are actually good reasons for this (lazy memory allocation), but this is the net effect. Most programs don’t handle a failed malloc() terribly gracefully anyway, so the end result is normally the same in practice.

3. Unaligned Access on Some Architectures.Many processors require that memory accesses be properly aligned, which means that 4-byte values are accessed on 4-byte boundaries, 2-byte values are on 2-byte boundaries, and so on. The Intel IA32 architecture doesn’t require aligned accesses, but it still is much slower to do unaligned fetches. Systems such as Linux/SPARC and Linux/m68k send a SIGBUS when a process tries to perform an unaligned access.

While SIGBUS can be caught and even ignored, doing so is normally a bad idea. It’s sent only in a genuine error condition, so the only reasonable reaction is to terminate. By default, SIGBUS causes a process to terminate and leave a core dump behind.

SIGILL: When a program tries to execute an illegal instruction, a SIGILL is sent to a process. Like SIGBUS, this really shouldn’t happen, so catching it is pretty pointless. In theory, a program can actually catch this signal, emulate the offending instruction, and restart the rest of the program. Doing this would be ridiculously slow, though, and is generally a bad idea. When this signal isn’t caught or ignored, it causes a core dump and program termination.

SIGSEGV:This is probably the least popular signal in Linux programming. It gets sent to a process that tries to access a bit of memory that doesn’t exist, or tries to access it in a way for which it doesn’t have permission (trying to write to a read-only section of memory, for example). A prime example of a SIGSEGV is dereferencing the NULL pointer, which always points to an unallocated page. By default, SIGSEGV causes the program to terminate with a core dump.

Terminal Handling

A couple of signals are related directly to terminal handling. SIGINT, SIGQUIT, and SIGTSTP are in this class, along with a few others:

SIGTTOU: When a background process tries to write to its terminal, it could be sent a SIGTTOU depending on the configuration of the terminal (the stty command’s TOSTOP controls this behavior). By default, this signal stops the process from running.

SIGTTIN:This signal is sent when a background process tries to read from its terminal. By default, it causes the process to be suspended.

SIGWINCH: Linux terminals are quite flexible, and the user can even change their sizes whenever they like. To let full-screen applications adjust to window-size changes, the kernel sends them SIGWINCH whenever their controlling windows change geometry. Normally, this signal is ignored, but it can be caught by applications that need to adjust their output to account for a changed terminal size.

SIGHUP:The HUP here is short for “Hang-Up.” SIGHUP is used by the kernel to tell processes when the terminal on which they’re running has been closed. By sending a SIGHUP when a user logs out, processes are given the chance to exit when the user can no longer access them.

This signal can be caught, and most programs should be sure to exit() properly after catching a SIGHUP. For daemon processes that don’t require input from a user and normally disassociate themselves from any terminal, SIGHUP is traditionally used to tell the daemon process to reopen its log files, allowing easy log-file rotation and cleanup.

Other Signals

SIGALRM: When a process sets a timer via the alarm() system call, a SIGALRM is sent to the process when the timer expires. By default, this signal terminates the process.

SIGALRM is a handy way to force a timeout of a system call, which can normally last indefinitely. For example, a process that wants to kill a child nicely, wait two seconds, then make sure it’s dead, can use a code snippet like this:


kill(childPid, SIGTERM);
alarm(2);
rc = waitpid(childPid, 0, &status);
alarm(0);
if (rc) {
/* we were interrupted by an alarm :-( */
kill(SIGKILL, SIGTERM);
rc = waitpid(childPid, 0, &status);
}

SIGCHLD: When a process dies, that process’s parent process is sent a SIGCHLD. This signal is normally ignored, but it provides an easy way to reap children for processes that either have a lot of child processes or need to restart children when they terminate.


SIGUSR1
SIGUSR2

Both of these signals are left for processes to use for anything they like. There is no defined purpose for them, and they are often used for groups of processes to work together.

While signals are a complex part of the Linux API, they are used quite often, and a strong understanding of them is essential for a good system programmer. If you’re interested in learning more about signals, any good Linux, Unix, or POSIX programming book will talk about them in more detail.

Additionally, the POSIX real-time specification, which Linux supports, greatly enhances the available signal set and signal semantics, and provides a useful tool for many event-driven applications.





Erik Troan is a developer for Red Hat Software, and co-author of the book Linux Application Development. He can be reached at ewt@redhat.com.

Comments are closed.