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.
Kernel Diagram
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).
Kernel Diagram
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.
Name Number Default Action Semantics
SIGHUP 1 Terminate Hangup detected on controlling terminal or death of controlling process
SIGINT 2 Terminate Interrupt from keyboard. Usually terminate the process. Can be triggered by Ctrl-C
SIGQUIT 3 Core dump Quit from keyboard. Usually causes the process to terminate and dump core. Cab be triggered by Ctrl-\
SIGILL 4 Core dump The process has executed an illegal hardware instruction.
SIGTRAP 5 Core dump Trace/breakpoint trap. Hardware fault.
SIGABRT 6 Core dump Abort signal from abort(3)
SIGFPE 8 Core dump Floating point exception such as dividing by zero or a floating point overflow.
SIGKILL 9 Terminate Sure way to terminate (kill) a process. Cannot be caught or ignored.
SIGSEGV 11 Core dump The process attempted to access an invalid memory reference.
SIGPIPE 13 Terminate Broken pipe: Sent to a process writing to a pipe or a socket with no reader (most likely the reader has terminated).
SIGALRM 14 Terminate Timer signal from alarm(2)
SIGTERM 15 Terminate Termination signal. The kill command send this signal by default, when no explicit signal type is provided.
SIGUSR1 30,10,16 Terminate First user-defined signal, designed to be used by application programs which can freely define its semantics.
SIGUSR2 31,12,17 Terminate Second user-defined signal, designed to be used by application programs which can freely define its semantics.
SIGCHLD 20,17,18 Ignore Child stopped or terminated
SIGCONT 19,18,25 Continue / Ignore Continue if stopped
SIGSTOP 17,19,23 Stop Sure way to stop a process: cannot be caught or ignored. Used for non interactive job-control while SIGSTP is the interactive stop signal.
SIGTSTP 18,20,24 Stop Interactive signal used to suspend process execution. Usually generated by typing Ctrl-Z in a terminal.
SIGTTIN 21,21,26 Stop A background process attempt to read from its controlling terminal (tty input).
SIGTTOU 22,22,27 Stop A background process attempt to write to its 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.
SIGSYS 12,-,12 Core dump Invalid system call. The Kernel interpreted a processor 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).
SIGVTALRM 26,26,28 Terminate Expiration of a virtual interval timer set with 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).
SIGWINCH 28,28,20 Ignore Informs a process of a change in associated terminal window size.
3. References
* “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.
1.
Processor executing computer programs.
↩
2.
Unless the two processes previously explicitly agreed to share this memory zone.
↩
3.
Usually by means of a software trap.
↩
4.
Input/Output
↩
5.
More precisely execution can be interrupted during any non-atomic instruction.
↩
Sunday, August 30, 2009
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment