Processes running in the system require a way to be told about events that influence them. On UNIX there is infrastructure between the kernel and processes called signals which allows a process to receive notification about events important to it.
When a signal is sent to a process, the kernel invokes a handler which the process must register with the kernel to deal with that signal. A handler is simply a designed function in the code that has been written to specifically deal with interrupt. Often the signal will be sent from inside the kernel its self, however it is also common for one process to send a signal to another process (one form of interprocess communication). The signal handler gets called asynchronously; that is the currently running program is interrupted from what it is doing to process the signal event.
For example, one type of signal is an
interrupt (defined in system headers as
SIGINT
) is delivered to the
process when the ctrl-c
combination is pressed.
As a process uses the read
system call to read input from the keyboard, the kernel will be
watching the input stream looking for special characters. Should
it see a ctrl-c
it will jump into
signal handling mode. The kernel will look to see if the process
has registered a handler for this interrupt. If it has, then
execution will be passed to that function where the function will
handle it. Should the process have not
registered a handler for this particular signal, then the kernel
will take some default action. With
ctrl-c
the default action is to
terminate the process.
A process can choose to ignore some signals, but other
signals are not allowed to be ignored. For example,
SIGKILL
is the signal sent when a
process should be terminated. The kernel will see that the
process has been sent this signal and terminate the process from
running, no questions asked. The process can not ask the kernel
to ignore this signal, and the kernel is very careful about which
process is allowed to send this signal to another process; you may
only send it to processes owned by you unless you are the root
user. You may have seen the command kill
-9
; this comes from the implementation
SIGKILL
signal. It is commonly
known that SIGKILL
is actually
defined to be 0x9
, and so when
specified as an argument to the
kill
program means that the
process specified is going to be stopped immediately. Since the
process can not choose to ignore or handle this signal, it is seen
as an avenue of last resort, since the program will have no chance
to clean up or exit cleanly. It is considered better to first
send a SIGTERM
(for terminate) to
the process first, and if it has crashed or otherwise will not
exit then resort to the SIGKILL
.
As a matter of convention, most programs will install a handler
for SIGHUP
(hangup -- a left over
from days of serial terminals and modems) which will reload the
program, perhaps to pick up changes in a configuration file or
similar.
If you have programmed on a Unix system you would be
familiar with segmentation faults
when you try to read or write to memory that has not been
allocated to you. When the kernel notices that you are touching
memory outside your allocation, it will send you the segmentation
fault signal. Usually the process will not have a handler
installed for this, and so the default action to terminate the
program ensues (hence your program "crashes"). In some cases a
program may install a handler for segmentation faults, although
reasons for doing this are limited.
This raises the question of what happens after the signal is received. Once the signal handler has finished running, control is returned to the process which continues on from where it left off.
The following simple program introduces a lot of signals to run!
1 $ cat signal.c #include <stdio.h> #include <unistd.h> 5 #include <signal.h> void sigint_handler(int signum) { printf("got SIGINT\n"); 10 } int main(void) { signal(SIGINT, sigint_handler); 15 printf("pid is %d\n", getpid()); while (1) sleep(1); } $ gcc -Wall -o signal signal.c 20 $ ./signal pid is 2859 got SIGINT # press ctrl-c # press ctrl-z [1]+ Stopped ./signal 25 $ kill -SIGINT 2859 $ fg ./signal got SIGINT 30 Quit # press ctrl-\ $
We have simple program that simply defines a handler for
the SIGINT
signal, which is
sent when the user presses
ctrl-c
. All the signals for
the system are defined in
signal.h
, including the
signal
function which allows us
to register the handling function.
The program simply sits in a tight loop doing nothing
until it quits. When we start the program, we try pressing
ctrl-c
to make it quit. Rather
than taking the default action, or handler is invoked and we get
the output as expected.
We then press ctrl-z
which sends a SIGSTOP
which by
default puts the process to sleep. This means it is not put in
the queue for the scheduler to run and is thus dormant in the
system.
As an illustration, we use the
kill program to send the same signal
from another terminal window. This is actually implemented with
the kill
system call, which
takes a signal and PID to send to (this function is a little
misnamed because not all signals do actually kill the process,
as we are seeing, but the
signal
function was already
taken to register the handler). As the process is stopped, the
signal gets queued for the process. This
means the kernel takes note of the signal and will deliver it
when appropriate.
At this point we wake the process up by using the command
fg
. This actually sends a
SIGCONT
signal to the process,
which by default will wake the process back up. The kernel
knows to put the process on the run queue and give it CPU time
again. We see at this point the queued signal is
delivered.
In desperation to get rid of the program, we finally try
ctrl-\
which sends a
SIGABRT
(abort) to the process.
But if the process has aborted, where did the
Quit
output come from?
You guessed it, more signals! When a parent child has a
process that dies, it gets a
SIGCHLD
signal back. In this
case the shell was the parent process and so it got the signal.
Remember how we have the zombie process that needs to be reaped
with the wait
call to get the
return code from the child process? Well another thing it also
gives the parent is the signal number that the child may have
died from. Thus the shell knows that child process died from a
SIGABRT
and as an informational
service prints as much for the user (the same process happens to
print out "Segmentation Fault" when the child process dies from a
SIGSEGV
).
You can see how in even a simple program, around 5
different signals were used to communicate between processes and
the kernel and keep things running. There are many other
signals, but these are certainly amongst the most common. Most
have system functions defined by the kernel, but there are a few
signals reserved for users to use for their own purposes within
their programs (SIGUSR
).