Introduction to UNIX Signals and System Calls
First published: Sept 2007
Last update: Dec 2007
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
SystemTap to figure it all out.
1. Why Even Bother Learning This?
“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 &ldot; provided that you acquire a basic understanding of UNIX signals and system calls!
2. The Big Picture
2.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.
2.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).
2.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
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
exec pattern for instance).
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
- 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:
- When a process receives a signal that is not ignored, the program immediately interrupts its current execution flow5.
- Control is then transferred to a dedicated signal handler, a custom one defined by the process or the system default.
- 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
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
SIGKILLthe system does not switch back to user mode. It processes the signal in Kernel mode and terminates the process. This is why
kill -9is such a bulletproof way to terminate a misbehaving process.
if the signal is
SIGSTOPthe 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
SIGSEGVsignal 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.
3. Overview of UNIX Signals Semantics and API
3.1. Common Signals
Most UNIX systems define about 30 signals. The most commons are described in the table below.
|1||Terminate||Hangup detected on controlling terminal or death of controlling process|
|2||Terminate||Interrupt from keyboard. Usually terminate the process. Can be triggered by
|3||Core dump||Quit from keyboard. Usually causes the process to terminate and dump core. Cab be triggered by
|4||Core dump||The process has executed an illegal hardware instruction.|
|5||Core dump||Trace/breakpoint trap. Hardware fault.|
|6||Core dump||Abort signal from abort(3)|
|8||Core dump||Floating point exception such as dividing by zero or a floating point overflow.|
|9||Terminate||Sure way to terminate (kill) a process. Cannot be caught or ignored.|
|11||Core dump||The process attempted to access an invalid memory reference.|
|13||Terminate||Broken pipe: Sent to a process writing to a pipe or a socket with no reader (most likely the reader has terminated).|
|14||Terminate||Timer signal from alarm(2)|
|15||Terminate||Termination signal. The
|30,10,16||Terminate||First user-defined signal, designed to be used by application programs which can freely define its semantics.|
|31,12,17||Terminate||Second user-defined signal, designed to be used by application programs which can freely define its semantics.|
|20,17,18||Ignore||Child stopped or terminated|
|19,18,25||Continue / Ignore||Continue if stopped|
|17,19,23||Stop||Sure way to stop a process: cannot be caught or ignored. Used for non interactive job-control while
|18,20,24||Stop||Interactive signal used to suspend process execution. Usually generated by typing
|21,21,26||Stop||A background process attempt to read from its controlling terminal (tty input).|
|22,22,27||Stop||A background process attempt to write to its controlling terminal (tty output).|
|23,29,22||Terminate||Asynchronous I/O now event.|
|10,7,10||Core dump||Bus error (bad memory access)|
|Terminate||Signals an event on a pollable device.|
|27,27,29||Terminate||Expiration of a profiling timer set with
|12,-,12||Core dump||Invalid system call. The Kernel interpreted a processor instruction as a system call, but its argument is invalid.|
|16,23,21||Ignore||Urgent condition on socket (e.g. out-of-band data).|
|26,26,28||Terminate||Expiration of a virtual interval timer set with
|24,24,30||Core dump||CPU soft time limit exceeded (Resource limits).|
|25,25,31||Core dump||File soft size limit exceeded (Resource limits).|
|28,28,20||Ignore||Informs a process of a change in associated terminal window size.|
- “Operating Systems Design and Implementation” by Andrew S Tanenbaum and Albert S Woodhull.
- Advanced Programming in the UNIX Environment by W. Richard Stevens
- “Kernel command using Linux system calls” by M. Tim Jones.
- “Linux System Calls” by Mark Mitchell and Jeffrey Oldham for a good description of common system calls.
- Linux System Calls by Mark Mitchell, Jeffrey Oldham
- “The Linux Programmer’s Guide” by Sven Goldt, Sven van der Meer Scott Burkett and Matt Welsh.
- Open Group Specification of System Interfaces
- “How UNIX Works” by Jonathan Lewis.