7. Interrupts and Interrupt Handlers

Interrupts and Interrupt Handlers #

A core responsibility of any operating system kernel is managing the hardware connected to the machine, hard drives and Blu-ray discs, keyboards and mice, 3D processors and wireless radios. To meet this responsibility, the kernel needs to communicate with the machine’s individual devices.

Processors can be orders of magnitudes faster than the hardware they talk to; it is not ideal for the kernel to issue a request and wait for a response from slower hardware. Instead, the kernel must be free to go and handle other work, dealing with the hardware only after that hardware has actually completed its work.

How can the processor work with hardware without impacting the machine’s overall performance? One answer to this question is polling. Periodically, the kernel can check the status of the hardware in the system and respond accordingly. Polling incurs overhead, however, because it must occur repeatedly regardless of whether the hardware is active or ready. A better solution is to provide a mechanism for the hardware to signal to the kernel when attention is needed. This mechanism is called an interrupt. In this chapter, we discuss interrupts and how the kernel responds to them, with special functions called interrupt handlers.

Interrupts #

Interrupts enable hardware to signal to the processor. For example, as you type, the keyboard controller (the hardware device that manages the keyboard) issues an electrical signal to the processor to alert the operating system to newly available key presses. These electrical signals are interrupts. The processor receives the interrupt and signals the operating system to enable the operating system to respond to the new data.

Hardware devices generate interrupts asynchronously to the processor clock, they can occur at any time. The kernel can be interrupted at any time to process interrupts.

  • An interrupt is physically produced by electronic signals originating from hardware devices and directed into input pins on an interrupt controller, a simple chip that multiplexes multiple interrupt lines into a single line to the processor.
  • Upon receiving an interrupt, the interrupt controller sends a signal to the processor.
  • The processor detects this signal and interrupts its current execution to handle the interrupt.
  • The processor can then notify the operating system that an interrupt has occurred, and the operating system can handle the interrupt appropriately.

Different devices can be associated with different interrupts by means of a unique value associated with each interrupt. This way, interrupts from the keyboard are distinct from interrupts from the hard drive. This enables the operating system to differentiate between interrupts and to know which hardware device caused which interrupt. In turn, the operating system can service each interrupt with its corresponding handler. These interrupt values are often called interrupt request (IRQ) lines. Each IRQ line is assigned a numeric value, for example, on the classic PC, IRQ zero is the timer interrupt and IRQ one is the keyboard interrupt. Not all interrupt numbers, however, are so rigidly defined. Interrupts associated with devices on the PCI bus, for example, generally are dynamically assigned.

A specific interrupt is associated with a specific device, and the kernel knows this. The hardware then issues interrupts to get the kernel’s attention.

Exceptions and Interrupts #

Exceptions are often discussed at the same time as interrupts. Unlike interrupts, exceptions occur synchronously with respect to the processor clock; they are often called synchronous interrupts. Exceptions are produced by the processor while executing instructions either in response to a programming error (e.g. divide by zero) or abnormal conditions that must be handled by the kernel (e.g. a page fault). Because many processor architectures handle exceptions in a similar manner to interrupts, the kernel infrastructure for handling the two is similar.

We can define Interrupts and Exceptions as follows:

  • Interrupts: asynchronous interrupts generated by hardware, also called hardware interrupts.
  • Exceptions: synchronous interrupts generated by the processor, also called software interrupts.

Interrupt Handlers #

The function the kernel runs in response to a specific interrupt is called an interrupt handler or interrupt service routine (ISR). Each device that generates interrupts has an associated interrupt handler. For example, one function handles interrupts from the system timer, whereas another function handles interrupts generated by the keyboard. The interrupt handler for a device is part of the device’s driver, the kernel code that manages the device. Each device has one associated driver. If that device uses interrupts (and most do), that driver must register one interrupt handler.

In Linux, interrupt handlers are normal C functions, which match a specific prototype and thus enables the kernel to pass the handler information in a standard way. What differentiates interrupt handlers from other kernel functions is that the kernel invokes them in response to interrupts and that they run in a special context called interrupt context. This special context is occasionally called atomic context because code executing in this context is unable to block.

Because an interrupt can occur at any time, an interrupt handler can be executed at any time. It is imperative that the handler runs quickly, to resume execution of the code that was interrupted and the hardware as soon as possible. At the very least, an interrupt handler’s job is to acknowledge the interrupt’s receipt to the hardware to let it continue its normal operation.

Often, however, interrupt handlers have a large amount of work to perform. For example, consider the interrupt handler for a network device. On top of responding to the hardware, the interrupt handler needs to copy networking packets from the hardware into memory, process them, and push the packets down to the appropriate protocol stack or application.

Top Halves Versus Bottom Halves #

These two goals, that an interrupt handler execute quickly and perform a large amount of work, conflict with one another.

Because of these competing goals, the processing of interrupts is split into two parts, or halves:

  • Top half: The interrupt handler is the top half. The top half is run immediately upon receipt of the interrupt and performs only the work that is time-critical, such as acknowledging receipt of the interrupt or resetting the hardware.
  • Bottom half: Work that can be performed later is deferred until the bottom half. The bottom half runs in the future, at a more convenient time, with all interrupts enabled.

For example using the network card:

  • When network cards receive packets from the network, the network cards immediately issue an interrupt. This optimizes network throughput and latency and avoids timeouts. The kernel responds by executing the network card’s registered interrupt.
  • The interrupt runs, acknowledges the hardware, copies the new networking packets into main memory, and readies the network card for more packets. These jobs are the important, time-critical, and hardware-specific work.
    • The kernel generally needs to quickly copy the networking packet into main memory because the network data buffer on the networking card is fixed and miniscule in size, particularly compared to main memory. Delays in copying the packets can result in a buffer overrun, with incoming packets overwhelming the networking card’s buffer and thus packets being dropped.
    • After the networking data is safely in the main memory, the interrupt’s job is done, and it can return control of the system to whatever code was interrupted when the interrupt was generated.
  • The rest of the processing and handling of the packets occurs later, in the bottom half.

Reentrancy and Interrupt Handlers #

Interrupt handlers in Linux need not be reentrant. When a given interrupt handler is executing, the corresponding interrupt line is masked out on all processors, preventing another interrupt on the same line from being received. Normally all other interrupts are enabled, so other interrupts are serviced, but the current line is always disabled. Consequently, the same interrupt handler is never invoked concurrently to service a nested interrupt. This greatly simplifies writing your interrupt handler.

Shared Handlers #

Multiple devices could use the same IRQ line, its called Interrupt Sharing. When the kernel receives an interrupt, it invokes sequentially each registered handler on the line. Therefore, it is important that the handler be capable of distinguishing whether it generated a given interrupt. The handler must quickly exit if its associated device did not generate the interrupt. This requires the hardware device to have a status register (or similar mechanism) that the handler can check. Most hardware has such a feature.

Interrupt Context #

When executing an interrupt handler, the kernel is in interrupt context.

The Kernel can only be in two different context:

  • In process context
    • In user-space, running user code in a process.
    • In kernel-space, running kernel code on behalf of a specific process.
  • in interrupt context, in kernel-space, not associated with any process, handling an interrupt.

Interrupt context cannot sleep and cannot reschedule. Therefore, you cannot call certain functions from interrupt context. If a function sleeps, you cannot use it from your interrupt handler: this limits the functions that can be called from an interrupt handler. Interrupt context is time-critical, because the interrupt handler interrupts other code.

Implementing Interrupt Handlers #

The implementation of the interrupt handling system in Linux is architecture-dependent. The implementation depends on the processor, the type of interrupt controller used, and the design of the architecture and machine.

The path that an interrupt takes from hardware and on through the kernel.

Interrupt path #

From a device to the processor

  • A device issues an interrupt by sending an electric signal over its bus to the interrupt controller.
  • If the interrupt line is enabled (they can be masked out), the interrupt controller sends the interrupt to the processor. In most architectures, this is accomplished by an electrical signal sent over a special pin to the processor.
  • If interrupts are not disabled in the processor, the processor immediately stops what it is doing, disables the interrupt system, and jumps to a predefined location in memory and executes the code located there. This predefined point is set up by the kernel and is the entry point for interrupt handlers.

Inside the Kernel

Similarly to system calls that enter the kernel through a predefined exception handler, the interrupt in the kernel begins at this predefined entry point.

  • For each interrupt line, the processor jumps to a unique location in memory and executes the code located there. In this manner, the kernel knows the IRQ number (the interrupt value) of the incoming interrupt.
  • The initial entry point (assembly entry routine) saves the interrupt value and stores the current register values of the interrupted task on the stack.
  • Then, the Kernel ensures that a valid handler is registered on the line and that it is enabled and not currently executing. If so, it calls the corresponding interrupt handler (using do_IRQ()).

From this point, most of the interrupt handling code is written in C, but is still architecture-dependent.

  • Since the processor disabled interrupts, they are turned back on if IRQF_DISABLED was not specified during the handler’s registration.
  • Each potential handler is executed in a loop. If this line is not shared, the loop terminates after the first iteration. Otherwise, all handlers are executed.

At this point, all potential handlers have been executed.

  • If a reschedule is pending
    • If the kernel is returning to user-space (that is, the interrupt interrupted a user process), the scheduler is called.
    • If the kernel is returning to kernel-space (that is, the interrupt interrupted the kernel itself), the scheduler is called only if the Kernel does not hold locks. Otherwise it is not safe to preempt the kernel.
  • After schedule() returns, or if there is no work pending, the initial registers are restored and the kernel resumes whatever was interrupted.

Interrupts and ProcFS #

ProcFS is a virtual filesystem that exists only in kernel memory and is typically mounted at /proc. Reading or writing files in procfs invokes kernel functions that simulate reading or writing from a real file.

The /proc/interrupts file is populated with statistics related to interrupts on the system.

Interrupt Control #

The Linux kernel implements a family of interfaces for manipulating the state of interrupts. These interfaces enable you to disable the interrupt system for the current processor or mask out an interrupt line for the entire machine. These routines are all architecture-dependent.

Controling the interrupt system is needed to provide synchronization.

  • Disabling interrupts guarantees that an interrupt handler will not preempt the current code.
  • Disabling interrupts also disables kernel preemption.

However, neither disabling interrupt delivery nor disabling kernel preemption provides any protection from concurrent access from another processor. Because Linux supports multiple processors, kernel code generally needs to obtain some sort of lock to prevent another processor from accessing shared data simultaneously. These locks are often obtained in conjunction with disabling local interrupts.