How is an Interrupt handled in Linux?
Here's a high-level view of the low-level processing. I'm describing a simple typical architecture, real architectures can be more complex or differ in ways that don't matter at this level of detail.
When an interrupt occurs, the processor looks if interrupts are masked. If they are, nothing happens until they are unmasked. When interrupts become unmasked, if there are any pending interrupts, the processor picks one.
Then the processor executes the interrupt by branching to a particular address in memory. The code at that address is called the interrupt handler. When the processor branches there, it masks interrupts (so the interrupt handler has exclusive control) and saves the contents of some registers in some place (typically other registers).
The interrupt handler does what it must do, typically by communicating with the peripheral that triggered the interrupt to send or receive data. If the interrupt was raised by the timer, the handler might trigger the OS scheduler, to switch to a different thread. When the handler finishes executing, it executes a special return-from-interrupt instruction that restores the saved registers and unmasks interrupts.
The interrupt handler must run quickly, because it's preventing any other interrupt from running. In the Linux kernel, interrupt processing is divided in two parts:
- The “top half” is the interrupt handler. It does the minimum necessary, typically communicate with the hardware and set a flag somewhere in kernel memory.
- The “bottom half” does any other necessary processing, for example copying data into process memory, updating kernel data structures, etc. It can take its time and even block waiting for some other part of the system since it runs with interrupts enabled.
As usual on this topic, for more information, read Linux Device Drivers; chapter 10 is about interrupts.
Gilles already described the general case of an interrupt, the following applies specifically to Linux 2.6 on an Intel architecture (part of this is also based on Intel's specifications).
An interrupt is an event that changes the sequence of instructions executed by the processor.
There are two different kinds of interrupts:
- Synchronous interrupt (Exception) produced by the CPU while processing instructions
- Asynchronous interrupt (Interrupt) issued by other hardware devices
Exceptions are caused by programming errors (f.e. Divide error, Page Fault, Overflow) that must be handled by the kernel. He sends a signal to the program and tries to recover from the error.
The following two exceptions are classified:
- Processor-detected exception generated by the CPU while detecting a anomalous condition; divided into three groups: Faults can generally be corrected, Traps report an execution, Aborts are serious errors.
- Programmed exception requested by the programmer, handled like a trap.
Interrupts can be issued by I/O devices (keyboard, network adapter, ..), interval timers and (on multiprocessor systems) other CPUs. When an interrupt occures, the CPU must stop his current instruction and execute the newly arrived interrupt. He needs to save the old interrupted process state to (probably) resume it after the interrupt is handled.
Handling interrupts is a sensitive task:
- Interrupts can occur at any time, the kernel tries to get it out of the way as soon as possible
- An interrupt can be interrupted by another interrupt
- There are regions in the kernel which must not be interrupted at all
Two different interrupt levels are defined:
- Maskable interrupts issued by I/O devices; can be in two states, masked or unmasked. Only unmasked interrupts are getting processed.
- Nonmaskable interrupts; critical malfunctions (f.e. hardware failure); always processed by the CPU.
Every hardware device has it's own Interrupt Request (IRQ) line. The IRQs are numbered starting from 0. All IRQ lines are connected to a Programmable Interrupt Controller (PIC). The PIC listens on IRQs and assigns them to the CPU. It is also possible to disable a specific IRQ line.
Modern multiprocessing Linux systems generally include the newer Advanced PIC (APIC), which distributes IRQ requests equally between the CPUs.
The mid-step between an interrupt or exception and the handling of it is the Interrupt Descriptor Table (IDT). This table associates each interrupt or exception vector (a number) with a specified handler (f.e. Divide error gets handled by the function divide_error()
).
Through the IDT, the kernel knows exactly how to handle the occurred interrupt or exception.
So, what does the kernel when an interrupt occurres?
- The CPU checks after each instruction if there's a IRQ from the (A)PIC
- If so, consults the IDT to map the received vector to a function
- Checks if the interrupt was issued by a authorized source
- Saves the registers of the interrupted process
- Call the according function to handle the interrupt
- Load the recently saved registers of the interrupted process and try to resume it
First of all participants involved in interrupt handling are peripheral hardware devices, interrupt controller, CPU, operating system kernel and drivers. Peripheral hardware devices are responsible for interrupt generation. They assert interrupt request lines when they want attention from operating system kernel. These signals are multiplexed by interrupt controller, which is responsible for interrupt signals collection. It also responsible for determination of the order in which interrupt signals will be passed to CPU. Interrupt controller is able to temporally disable particular interrupt request line (IRQL) and reenable it again (IRQL masking). Interrupt controller pass collected interrupt requests to the CPU sequentially. CPU after completion of execution of each instruction CPU does check is there any waiting interrupt requests from the interrupt controller. If CPU finds that there is waiting request AND Interrupt Enable flag is set in the internal CPU control register then CPU starts interrupt handling. As you can see, by manipulation on the Interrupt flag in the CPU and communication with interrupt controller, Linux kernel is able to control the interrupt acceptance. For example, Linux can disable acceptance of interrupts from the particular device or disable interrupt acceptance at all.
What happens when the processor receives an interrupt request? Firstly, CPU automatically disables interrupts by resetting Interrupt Flag. They will be reenabled once interrupt handling will be finished. In the same time CPU makes minimal amount of work needed for switching CPU from the user mode to kernel mode in such a way that will allow it resume the execution of the interrupted code. CPU consults with special CPU control structures filled by Linux kernel to find an address of code to which control will be passed. This address is the address of the first instruction of interrupt handler, which is a part of the Linux kernel.
As a first step of interrupt handling kernel identifies vector of interrupt received to identify what kind of event has been happened in the system. Interrupt vector defines what actions will Linux take to handle it. As a second step Linux saves the rest of the CPU registers (which wasn't saved by CPU automatically) and which potentially can be used by the interrupted program. This is very important action, because it allows Linux to handle interrupts transparently regard to the interrupted program. As a third step, Linux accomplish switching to the kernel mode by setting the kernel environment and setting the CPU state required for it. And finally, vector dependent interrupt handler is called. (You can look on BUILD_INTERRUPT3 macro in arch\x86\kernel\entry_32.S to grab the additional details for x86 architecture related example) In the case of peripheral devices this a do_IRQ() routine. (Look into the arch\x86\kernel\irq.c)
The vector dependent interrupt handler usually wrapped by calls to irq_enter() and irq_exit(). Code area enclosed within a pair of these functions, is atomic with respect to any other such areas and also is atomic with respect to pairs of cli/sti. Irq_enter() and irq_exit() also captures some statistics related to the interrupt handling. Finally, kernel looks into the vector_irq table to find the irq number assigned to the vector of the interrupt received and call handle_irq() (from arch\x86\kernel\irq_32.c).
At this point the common part of the interrupt handling in Linux ends, because kernel looks the device dependent interrupt handler routine installed by device driver as part of the irq descriptor and invokes it. If such handler wasn't been installed by driver, kernel just acknowledge interrupt on interrupt controller and goes to exit from general interrupt handler.
After the end of the interrupt handling kernel restores the state of the program which was interrupted previously and resumes this program execution.