An interrupt is usually defined as an event that alters the sequence of instructions executed by a processor. Such events correspond to electrical signals generated by hardware circuits both inside and outside the CPU chip.
Interrupts are often divided into synchronous and asynchronous interrupts:
Synchronous interrupts are produced by the CPU control unit while executing instructions and are called synchronous because the control unit issues them only after terminating the execution of an instruction.
Asynchronous interrupts are generated by other hardware devices at arbitrary times with respect to the CPU clock signals.
Intel microprocessor manuals designate synchronous and asynchronous interrupts as exceptions and interrupts, respectively. We’ll adopt this classification, although we’ll occasionally use the term "interrupt signal” to designate both types together (synchronous as well as asynchronous).
Interrupts are issued by interval timers and I/O devices; for
instance, the arrival of a keystroke from a user sets off an
interrupt. Exceptions, on the other hand, are caused either by
programming errors or by anomalous conditions that must be handled by
the kernel. In the first case, the kernel handles the exception by
delivering to the current process one of the signals familiar to
every Unix programmer. In the second case, the kernel performs all
the steps needed to recover from the anomalous condition, such as a
Page Fault or a request (via an int
instruction)
for a kernel service.
We start by describing in the next section the motivation for introducing such signals. We then show how the well-known IRQs (Interrupt ReQuests) issued by I/O devices give rise to interrupts, and we detail how 80 × 86 processors handle interrupts and exceptions at the hardware level. Then we illustrate, in Section 4.4, how Linux initializes all the data structures required by the Intel interrupt architecture. The remaining three sections describe how Linux handles interrupt signals at the software level.
One word of caution before moving on: in this chapter, we cover only “classic” interrupts common to all PCs; we do not cover the nonstandard interrupts of some architectures. For instance, laptops generate types of interrupts not discussed here.
As the name suggests, interrupt signals provide a way to divert the
processor to code outside the normal flow of control. When an
interrupt signal arrives, the CPU must stop what
it’s currently doing and switch to a new activity;
it does this by saving the current value of the
program
counter (i.e., the content of the eip
and
cs
registers) in the Kernel Mode stack and by
placing an address related to the interrupt type into the program
counter.
There are some things in this chapter that will remind you of the context switch described in the previous chapter, carried out when a kernel substitutes one process for another. But there is a key difference between interrupt handling and process switching: the code executed by an interrupt or by an exception handler is not a process. Rather, it is a kernel control path that runs on behalf of the same process that was running when the interrupt occurred (see the later section Section 4.3). As a kernel control path, the interrupt handler is lighter than a process (it has less context and requires less time to set up or tear down).
Interrupt handling is one of the most sensitive tasks performed by the kernel, since it must satisfy the following constraints:
Interrupts can come at any time, when the kernel may want to finish something else it was trying to do. The kernel’s goal is therefore to get the interrupt out of the way as soon as possible and defer as much processing as it can. For instance, suppose a block of data has arrived on a network line. When the hardware interrupts the kernel, it could simply mark the presence of data, give the processor back to whatever was running before, and do the rest of the processing later (such as moving the data into a buffer where its recipient process can find it and then restarting the process). The activities that the kernel needs to perform in response to an interrupt are thus divided into two parts: a top half that the kernel executes right away and a bottom half that is left for later. The kernel keeps a queue pointing to all the functions that represent bottom halves waiting to be executed and pulls them off the queue to execute them at particular points in processing.
Since interrupts can come at any time, the kernel might be handling one of them while another one (of a different type) occurs. This should be allowed as much as possible since it keeps the I/O devices busy (see the later section Section 4.3). As a result, the interrupt handlers must be coded so that the corresponding kernel control paths can be executed in a nested manner. When the last kernel control path terminates, the kernel must be able to resume execution of the interrupted process or switch to another process if the interrupt signal has caused a rescheduling activity.
Although the kernel may accept a new interrupt signal while handling a previous one, some critical regions exist inside the kernel code where interrupts must be disabled. Such critical regions must be limited as much as possible since, according to the previous requirement, the kernel, and particularly the interrupt handlers, should run most of the time with the interrupts enabled.
Get Understanding the Linux Kernel, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.