You are on page 1of 8

Introduction to UNIX Signals and System

Calls
“A good conceptual model allows us to predict the effect of our actions. Without a good
model we operate by rote, blindly; we do operations as we were told to do them; we can’t
fully appreciate why, what effects to expect, or what to do if things go wrong. As long as
things works properly we can manage. When things go wrong, however, or when we
come upon novel situation, then we need a deeper understanding, a good model.” –
Donald A. Norman, The Design of Everyday Things

When using a computer it is always good to have some kind of understanding of the
software stack that you are using. Otherwise when the system does not behave as
expected you are lacking the appropriate mental model and metaphor that will help you
come up with a way to resolve the problem.

Getting a good overall understanding of your software stack is even more critical if you
are creating or deploying software applications. In these cases it is pretty much
guaranteed that, at some point, something will go wrong.

The better you grasp the fundamental principles of your environment, the quicker
you will recover when things go awry. In particular it is important to get a good
understanding of your operating system’s fundamental mechanisms, even if you
concentrate on a higher level of the stack – say writing web applications in a high-
level language such as Java, Ruby or Python.

My ”Troubleshooting Ruby Processes” book, for instance, demonstrates how to leverage


system tools to resolve complex problems when deploying Ruby and Ruby on Rails
applications – provided that you acquire a basic understanding of UNIX signals and
system calls.

So whether you are creating applications for Mac OS X, Linux, BSD or Solaris, I would
recommend that you develop a good mental model of UNIX signals and system calls.
This can be challenging as most material on this topic is way too in-depth and specific to
capture casual readers whose objective is not to become system programmers or UNIX
gurus, but rather to get more familiar with the platform that they are using. These casual
readers are the main audience for this article: keep reading if you want to get a good
overall understanding of signals and system calls without diving too deeply into UNIX
internals and operating system theory. In this way, the next time that you cannot make
any sense of the latest trick that the system is playing on you, you will be able to leverage
system tools such as strace, DTrace or SystemTap to figure it all out.

• 1. The Big Picture


o 1.1. The Partition Between Kernel and User Space
o 1.2. Switching Between User Space and Kernel Space
o 1.3. System Calls
o 1.4. Signals
• 2. Overview of UNIX Signals Semantics and API
o 2.1. Common Signals
• 3. References

1. The Big Picture


1.1. The Partition Between Kernel and User Space

In the UNIX operating system, applications do not have direct access to the computer
hardware (say a hard-drive). Applications have to request hardware access from a third-
party that mediates all access to computer resources, the Kernel.

As you can see from the above diagram, the Kernel is a central component of most
computer operating systems. Its main responsibilities are to:

• Provide an abstraction layer on top of the hardware on which the system


operates. In this way, the Kernel acts as a standard interface to the system
hardware: for example, when an application reads a file it does not have to be
aware of the hard-drive model or physical geometry.
• Be in charge of managing computer resources. Since the machine and its
devices are shared between multiple users and programs, access to those resources
must be synchronized. The Kernel implements and guarantees a (relatively) fair
access to the processor, the memory and the devices. In particular, the Kernel is in
charge of process management and prevents a misbehaving process from
monopolizing the CPU1.
• Implement multitasking. Each process gets the illusion that it is running
uninterrupted on its own processor. In reality, multiple processes compete
continuously for system resources: the Kernel keeps switching the active process
for each processor behind the scene.
• Enforce isolation between processes. Because the Kernel mediates access to all
system resources it can guarantee that one process cannot corrupt the execution
environment of another process. For instance, each process has its own virtual
memory and cannot access the memory that the Kernel allocated to another
process2.

The Kernel can also be seen as one abstraction layer part of the traditional computer
architecture design in which each layer relies solely on the functions of the layers beneath
itself. Its mission is to protect users and applications from each others and to provide a
simple metaphor that shields applications from most of the system complexity.

Now that we understand better what constitutes the Kernel Space, you might wonder
what User Space is. Well it is actually quite simple: User Space is everything else! More
exactly, all the software running on a UNIX system except the Kernel (and its device
drivers) is running in User Space.

1.2. Switching Between User Space and Kernel Space

If processes cannot directly access hardware resources, there must be a way to switch
from the User Space to the Kernel Space, right? There are actually more than one way:

• A user process can explicitly request to transition to Kernel Space by issuing a


system call.
• The Kernel can take over while a user process is running to perform some system
housekeeping task (e.g. time-slicing and servicing interrupts).

You might be wondering whether a rogue user process could be not so polite and try to
circumvent the Kernel to access system resources directly. Well, luckily that would not
work: The Kernel mode is not only a software but also a hardware state. Modern
processors offer a privileged execution mode, often referred to as Supervisor Mode in
which the Kernel, and only the Kernel runs. If it is not in Supervisor Mode, the processor
will deny privileged operations such as modifying special registers, disabling interrupts,
accessing memory management hardware or computer peripherals.

The idea of having two different modes to operate on, User Space and Kernel Space, is a
building block for privilege separation. It guarantees that a User Space process will never
crash the whole system, even a malicious or poorly written one (provided the Kernel is
stable).

1.3. System Calls

The only way for an application in User Space to explicitly trigger a switch to Kernel
Mode is to issue a system call. Therefore system calls constitute the interface between
processes and the operating system. Each system call provides a basic operation such as
opening a file, getting the current time, creating a new process, or reading a character. In
this way system calls can be viewed as regular function calls, if it were for the fact that
they transfer control to the UNIX Kernel. System calls essentially are synchronous calls
to the operating system.

When a program invokes a system call, it is interrupted3 and the system switches to
Kernel space. The Kernel then saves the process execution context (so that it can resume
the program later) and determines what is being requested. The Kernel carefully checks
that the request is valid and that the process invoking the system call has enough
privilege. For instance some system calls can only be called by a user with superuser
privilege (often referred to as root).

If everything is good, the Kernel processes the request in Kernel Mode and can access the
device drivers in charge of controlling the hardware (e.g. reading a character inputted
from the keyboard). The Kernel can read and modify the data of the calling process as it
has access to memory in User Space (e.g. it can copy the keyboard character into a buffer
that the calling process has access to). However, it will not execute any code from a user
program, for obvious security reasons.

When the Kernel is done processing the request, it restores the process execution context
that was saved when the system call was invoked, and control returns to the calling
program which continues executing.

From the perspective of the calling process a system call is synchronous. In practice
though, the Kernel will not necessarily directly resume the program that invoked the
system call when it is done processing the request. Remember that the Kernel is
implementing multitasking and ensures a somewhat fair access to the processor? So when
the Kernel returns control to the User Space it gets to pick which user program to resume
based on his process scheduler heuristics. Actually, in many cases, not returning control
immediately to the invoking program is a good thing, as it may take a while for the
Kernel to get data from the hardware. This is especially true for I/O4 operations such as
reading data from the network.

It is worthwhile to get familiar with the most common UNIX System Calls as they
constitute the interface to the core of the operating system and permeates the
programming metaphor of the overall platform (like the fork/exec pattern for instance).
1.4. Signals

Signals offer another way to transition between Kernel and User Space. While system
call are synchronous calls originating from User Space, signals are asynchronous
messages coming from Kernel space. Signals are always delivered by the Kernel but they
can be initiated by:

• other processes on the system (using the kill command/system call)


• the process itself. This includes hardware exceptions triggered by a process:
when a program executes an illegal instruction, such as dividing a number by zero
or attempting to access a memory zone that has not been allocated yet, the
hardware detects it and a signal is sent to the faulty program.
• the Kernel. The Kernel also use signals to notify a process of some system
events, such as the arrival of out-of-band data. In the same way, when a program
sets a system alarm, the Kernel sends a signal to the process every time a timer
expires (e.g. every 10 seconds).

So a signal is an asynchronous message, but what happens exactly when a process


receives it? Well… it depends. For each signal, a process can instruct the Kernel to
either:

• Ignore this signal: In which case this signal has absolutely no effect on the
process. Ignoring the signal must be explicitly requested before the signal is
delivered. Also, some signals cannot be ignored.
• Catch this signal: In which case the Kernel will call a custom routine, as defined
by this process when delivering the signal. The process must explicitly register
this custom routine before the signal is delivered. The signal-catching function is
traditionally called a custom signal handler.
• Let the default action apply: For each signal the system defines a default action
that will be called if the process did not explicitly request to ignore or catch this
signal. The default signal handler typically but not always terminates the process
(we will cover the default actions for all common UNIX signals later in this
article). Letting the default action apply is the implicit system behavior, but it can
also be requested explicitly by a process.
The overall dynamic for signal delivery is quite simple:

1. When a process receives a signal that is not ignored, the program immediately
interrupts its current execution flow5.
2. Control is then transferred to a dedicated signal handler, a custom one defined by
the process or the system default.
3. Once the signal handler completes, the program resumes where it was originally
interrupted.

In practice, though, the mechanics used by the Kernel to send a signal are more involved
and consist of two distinct steps: generating and delivering the signal.

The Kernel generates a signal for a process simply by setting a flag that indicates the type
of the signal that was received. More precisely, each process has a dedicated bitfield used
to store pending signals; For the system, generating a signal is just a matter of updating
the bit corresponding to the signal type in this bitfield structure. At this stage, the signal is
said to be pending.

Before transferring control back to a process in user mode, the Kernel always checks the
pending signals for this process. This check must happen in Kernel space because some
signals can never be ignored by a process – namely SIGSTOP and SIGKILL (you trigger
SIGKILL with the infamous kill -9 command).

When a pending signal is detected by the Kernel, the system will deliver the signal by
performing one of the following actions:

• if the signal is SIGKILL the system does not switch back to user mode. It
processes the signal in Kernel mode and terminates the process. This is why
kill -9 is such a bulletproof way to terminate a misbehaving process.
• if the signal is SIGSTOP the system also stays in Kernel mode. Its suspends the
process and puts it to sleep.
• if the process did not register any custom handler for this signal, the default
system action is taken. If the default action is to ignore the signal, no action is
taken, and the system just switches back to user mode and transfers control to the
process. If the default action is not to ignore the signal, the system remains in
Kernel mode and the process will exit, dump core, or be suspended. For instance,
the default behavior for the SIGSEGV signal is to dump a core file and terminate
the process, so that one can analyze the bug that triggered the segmentation fault.
• if the process registered a custom handler for the signal, the Kernel transfers
control to the process and the custom signal handler is executed in user mode. At
this point, the program is the one responsible for handling the signal properly.

A crucial point here is to realize that the Kernel triggers the signal handler, when the
signal is delivered, not when the signal is generated. As signal delivery only happens
when the system schedules the target process as active in a multitasking system (just
before switching back to User Mode) there can be a significant delay between signal
generation and delivery.

Finally a process has one last option when it comes to signals. It can instruct the Kernel
to block the delivery of a specific signal. If a signal is blocked, the system still generates
it and the signal is considered pending. Nevertheless the Kernel will not deliver a blocked
signal until the process unblocks it. Signal blocking is typically used in critical sections
of code that must not be interrupted.

2. Overview of UNIX Signals Semantics and API


2.1. Common Signals

Most UNIX systems define about 30 signals. The most commons are described in the
table below.

Default
Name Number Semantics
Action
Hangup detected on controlling terminal or death of
SIGHUP 1 Terminate
controlling process
Interrupt from keyboard. Usually terminate the process.
SIGINT 2 Terminate
Can be triggered by Ctrl-C
Quit from keyboard. Usually causes the process to
SIGQUIT 3 Core dump
terminate and dump core. Cab be triggered by Ctrl-\
The process has executed an illegal hardware
SIGILL 4 Core dump
instruction.
SIGTRAP 5 Core dump Trace/breakpoint trap. Hardware fault.
SIGABRT 6 Core dump Abort signal from abort(3)
Floating point exception such as dividing by zero or a
SIGFPE 8 Core dump
floating point overflow.
Sure way to terminate (kill) a process. Cannot be
SIGKILL 9 Terminate
caught or ignored.
The process attempted to access an invalid memory
SIGSEGV 11 Core dump
reference.
Broken pipe: Sent to a process writing to a pipe or a
SIGPIPE 13 Terminate socket with no reader (most likely the reader has
terminated).
SIGALRM 14 Terminate Timer signal from alarm(2)
Termination signal. The kill command send this
SIGTERM 15 Terminate signal by default, when no explicit signal type is
provided.
First user-defined signal, designed to be used by
SIGUSR1 30,10,16 Terminate application programs which can freely define its
semantics.
Default
Name Number Semantics
Action
Second user-defined signal, designed to be used by
SIGUSR2 31,12,17 Terminate application programs which can freely define its
semantics.
SIGCHLD 20,17,18 Ignore Child stopped or terminated
Continue /
SIGCONT 19,18,25 Continue if stopped
Ignore
Sure way to stop a process: cannot be caught or
SIGSTOP 17,19,23 Stop ignored. Used for non interactive job-control while
SIGSTP is the interactive stop signal.
Interactive signal used to suspend process execution.
SIGTSTP 18,20,24 Stop
Usually generated by typing Ctrl-Z in a terminal.
A background process attempt to read from its
SIGTTIN 21,21,26 Stop
controlling terminal (tty input).
A background process attempt to write to its
SIGTTOU 22,22,27 Stop
controlling terminal (tty output).
SIGIO 23,29,22 Terminate Asynchronous I/O now event.
SIGBUS 10,7,10 Core dump Bus error (bad memory access)
SIGPOLL Terminate Signals an event on a pollable device.
SIGPROF 27,27,29 Terminate Expiration of a profiling timer set with setitimer.
Invalid system call. The Kernel interpreted a processor
SIGSYS 12,-,12 Core dump
instruction as a system call, but its argument is invalid.
SIGURG 16,23,21 Ignore Urgent condition on socket (e.g. out-of-band data).
Expiration of a virtual interval timer set with
SIGVTALRM 26,26,28 Terminate
setitimer.
SIGXCPU 24,24,30 Core dump CPU soft time limit exceeded (Resource limits).
SIGXFSZ 25,25,31 Core dump File soft size limit exceeded (Resource limits).
Informs a process of a change in associated terminal
SIGWINCH 28,28,20 Ignore
window size.

You might also like