Hardware and Software Interrupts
An interrupt is a signal that causes the CPU to stop what it is currently doing, save its state, and execute a special handler routine. Interrupts are the fundamental mechanism that allows hardware devices to communicate with the CPU asynchronously and that enables the OS to implement preemptive multitasking.
Hardware Interrupts
Hardware interrupts (also called external interrupts) are generated by devices outside the CPU:
- Keyboard: pressing a key sends an interrupt to notify the CPU that input is available.
- Disk controller: signals when a read/write operation has completed.
- Network card: signals when a packet has arrived.
- Timer: a programmable interval timer (PIT or APIC timer) fires periodically, typically at 100-1000 Hz. This timer interrupt is what drives preemptive scheduling -- the OS can take control away from a running process even if it has not voluntarily yielded.
Each device is assigned an IRQ (Interrupt Request) number. On classic x86 systems, the 8259A PIC (Programmable Interrupt Controller) handled 15 IRQ lines. Modern systems use the APIC (Advanced PIC) and I/O APIC, which support more interrupts and can route them to specific CPU cores.
Software Interrupts (Traps)
Software interrupts are triggered by the CPU itself, either deliberately or due to errors:
- System calls: the
syscall/int 0x80instruction is a deliberate software interrupt to request kernel services. - Exceptions: division by zero, invalid opcode, page fault. These are synchronous -- they occur at the exact instruction that caused the problem.
The Interrupt Vector Table (IVT) / IDT
The CPU uses an Interrupt Descriptor Table (IDT) on x86 (or Interrupt Vector Table on simpler architectures) to map each interrupt number to a handler address. The IDT has 256 entries on x86:
| Vectors | Purpose |
|---|---|
| 0-31 | CPU exceptions (page fault = 14, GPF = 13, divide error = 0) |
| 32-47 | Hardware IRQs (remapped by PIC/APIC) |
| 48-255 | Available for software use (e.g., syscalls) |
When interrupt N fires, the CPU looks up entry N in the IDT, switches to ring 0 (if not already there), pushes the interrupted state onto the kernel stack, and jumps to the handler address.
Interrupt Handler Lifecycle
- Save state: the CPU automatically pushes RIP, CS, RFLAGS, RSP, SS. The handler pushes additional registers it will use.
- Handle the interrupt: read device status registers, acknowledge the interrupt to the PIC/APIC, process the data.
- Restore state: pop saved registers.
- Return: execute
iret(Interrupt Return), which restores RIP, CS, RFLAGS, RSP, SS and returns to the interrupted code.
Top-Half vs. Bottom-Half
Interrupt handlers must execute quickly because interrupts are often disabled during handling (to prevent nested interrupts from causing stack overflow or race conditions). Linux splits interrupt processing into two halves:
- Top-half (hardirq): runs immediately in interrupt context with interrupts disabled. Does the minimum: acknowledge the device, copy data from hardware buffers, schedule the bottom-half. Must not sleep or block.
- Bottom-half (softirq, tasklet, or workqueue): runs later with interrupts re-enabled. Performs the heavier processing -- parsing network packets, waking blocked processes, updating data structures.
Interrupt Priority and Masking
Interrupts can be masked (temporarily disabled) using:
- CLI/STI instructions: disable/enable all maskable interrupts on the current CPU.
- PIC/APIC masking: disable specific IRQ lines.
- NMI (Non-Maskable Interrupt): cannot be disabled; used for critical hardware failures (memory parity errors, watchdog timeout).
Higher-priority interrupts can preempt lower-priority handlers on systems with interrupt priority levels (common in APIC-based systems).
Timer Interrupt and Preemptive Scheduling
The timer interrupt is the heartbeat of a preemptive operating system. Here is what happens every time it fires (typically every 1-10 ms):
- The hardware timer sends an IRQ to the CPU.
- The CPU suspends the currently running process, saves its registers, and jumps to the timer interrupt handler.
- The handler increments the system tick counter (
jiffiesin Linux). - The scheduler checks: has the current process used its entire time slice? If yes, it marks the process as "needs reschedule."
- The handler acknowledges the interrupt to the APIC and returns.
- On the return path, the kernel checks the reschedule flag. If set, it performs a context switch to a different process instead of returning to the interrupted one.
Without the timer interrupt, a CPU-bound process (e.g., an infinite loop) would monopolize the CPU forever. The timer interrupt ensures the OS always regains control.
Network card example: when a packet arrives, the NIC raises an IRQ. The top-half handler copies the packet from the NIC's DMA buffer into a kernel buffer and schedules a softirq. The bottom-half (NET_RX softirq) parses headers, matches the packet to a socket, and wakes the application blocked on recv(). The top-half takes ~1 microsecond; the bottom-half might take 5-20 microseconds.