Back to DAG

Interrupts & IRQ

os

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 0x80 instruction 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:

VectorsPurpose
0-31CPU exceptions (page fault = 14, GPF = 13, divide error = 0)
32-47Hardware IRQs (remapped by PIC/APIC)
48-255Available 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

  1. Save state: the CPU automatically pushes RIP, CS, RFLAGS, RSP, SS. The handler pushes additional registers it will use.
  2. Handle the interrupt: read device status registers, acknowledge the interrupt to the PIC/APIC, process the data.
  3. Restore state: pop saved registers.
  4. 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

Real-World Example

The timer interrupt is the heartbeat of a preemptive operating system. Here is what happens every time it fires (typically every 1-10 ms):

  1. The hardware timer sends an IRQ to the CPU.
  2. The CPU suspends the currently running process, saves its registers, and jumps to the timer interrupt handler.
  3. The handler increments the system tick counter (jiffies in Linux).
  4. The scheduler checks: has the current process used its entire time slice? If yes, it marks the process as "needs reschedule."
  5. The handler acknowledges the interrupt to the APIC and returns.
  6. 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.

Interrupt Handling Flow

Devices Keyboard IRQ 1 Disk IRQ 14 Network IRQ 11 Timer IRQ 0 I/O APIC Routes IRQ to CPU core by priority CPU 1. Finish instr 2. Check IRQ pin 3. Save RIP, RFLAGS, RSP 4. Look up IDT 5. Jump handler IDT (256 entries) [0] Divide Error [14] Page Fault [32] Timer Handler [33] Keyboard Handler Top-Half (hardirq) Interrupts disabled, runs immediately Ack device, copy data, schedule bottom-half Bottom-Half (softirq/workqueue) Interrupts enabled, runs later Parse packets, wake processes, heavy work Interrupt Lifecycle Save state Top-half run ACK APIC Restore iret Bottom-half
Step 1 of 2