Signals are one of the most visible aspects of the Linux operating system. They are also one of the least understood. Every sysadmin, even the PFYs who aren’t PFed yet, know how to kill a process. But do you know how this works underneath? Do you know how flexible the linux signalling system truly is?
If not, you’re about to find out.
Signals are yet another one of those kernel interfaces, like system calls and device drivers. They are not IPC in the sense that they cannot be used to send information to a program of themselves. They are basically an asynchronous way of telling a program that something is expected of it. They are also the kernel’s way of telling a program something as well.
There are three signals that are the most common, and a couple more that are less common but just as important. These are:
- SIGKILL (9) – Terminate a process. Noninterruptible.
- SIGSEGV (11) – Segmentation Fault.
- SIGTERM (15) – Terminate a process in an orderly fashion.
And then there are the less well known but at least as important:
- SIGBUS (2) – Bus Error
- SIGCHLD (17) – Child process terminated
- SIGSTOP (19) – Stop executing
- SIGCONT (18) – Continue executing after a stop
All of these different signals have a specific meaning.
SIGSEGV is one you’ll be very familiar with. It means “Segmentation Fault”. This is actually triggered by something very deep in the hardware itself, but is usually caused by a careless programmer. It is invoked when a programmer attempts to write to or read from memory that it has not actually been given. I’ll go into more details on that when I write about virtual memory.
This signal cannot be caught or ignored.
SIGTERM and SIGKILL are two ways of saying “kill the process”. The difference is that SIGTERM can be caught or even ignored – the process can decide not to listen to this signal. It does not have the same option when it comes to SIGKILL. When you run kill without any arguments, a SIGTERM is sent. When you run kill -9, a SIGKILL is sent.
Because it can’t be caught or ignored, the process does not have the ability to clean up after itself, and whatever it was doing at the time is left in an indeterminate state. Think of is this way – a SIGTERM is quitting time for the day, you get to pack up and take everything with you. a SIGKILL is like a fire alarm – you drop everything and leave the building.
SIGBUS is a bus error. This also originates deep in the hardware, but you’ll get this either under the same circumstances as SIGSEGV, or when hardware is failing. It’s just as catastrophic to a program as SIGSEGV.
SIGCHLD is the reason zombie proceses exist. When a linux process spawns a child (I’ll go into this process some other time), it basically owns the child. When the child dies, the parent process is notified of this fact via a SIGCHLD signal. The parent process is required to call the wait() system call in order to “reap” the child process. During the time between the SIGCHLD is sent and the parent process reaps the child process, the child process exists only as an entry in the process table. Also known as a defunct process, or zombie. So if you see a defunct process, one of two things has happened: The parent process is unable to reap the child, or whomever wrote the parent process screwed up.
I really have no idea why it was designed this way – I’m sure there’s some historical reason that will make perfect sense once I hear it, but it seems like an extra step to me.
SIGSTOP and SIGCONT are two special signals. SIGSTOP is sent to a process to tell to stop. At this point, if you run ps on the process, it will show up with a status of “T”. Then, it will start executing again when you send SIGCONT.
Strace and other processes that attach to a running process use these signals.
There are many other signals as well. SIGUSR1 and SIGUSR2 are some pretty intersting ones – they’re user defined. Some processes will listen for these signals and do some interesting things – such as increase logging, for example.
Look in /usr/include/asm/signal.h for a complete list of signals, or run kill -l.