Interrupts and Exception Handling in CPU Architecture: Vectors, Priorities, and ISRs

Interrupts and Exception Handling in CPU Architecture: Vectors, Priorities, and ISRs

A CPU running a program has no inherent way of knowing that a keyboard key was just pressed, a network packet just arrived, or a disk transfer just completed — unless it stops what it’s doing to check, constantly, which would be enormously wasteful (this “constantly checking” approach is called polling, and it’s precisely the inefficient pattern that DMA and interrupts together were designed to eliminate for I/O). Interrupts are the mechanism that lets hardware events reach into the CPU’s normal instruction flow and say, effectively, “stop what you’re doing, something important just happened, deal with it now.” This article covers how interrupts and exceptions work, how CPUs prioritize and route them, and why precise, well-managed interrupt handling is foundational to virtually every real-time responsive behavior in modern computing.

Interrupts vs. Exceptions vs. Traps: Getting the Terminology Straight

These terms are sometimes used loosely, but there’s a meaningful distinction worth establishing:

TypeTrigger SourceSynchronous or AsynchronousExample
Hardware interruptExternal deviceAsynchronousTimer, keyboard, network card, DMA completion
ExceptionCPU itself, during instruction executionSynchronousDivide by zero, page fault, illegal instruction
Trap / software interruptDeliberate software instructionSynchronousSystem call (e.g., syscall/int 0x80)

Despite these distinctions, all three ultimately use very similar underlying hardware and software mechanisms to redirect CPU execution to a handler routine, which is why they’re often discussed together under the general umbrella of “interrupt handling.”

The Basic Interrupt Handling Flow

When an interrupt occurs, the CPU follows a fairly standardized sequence:

  1. Detect the interrupt signal. The CPU checks for pending interrupts, typically at defined points in its instruction cycle (commonly at instruction boundaries, so an interrupt doesn’t have to awkwardly interrupt a partially-completed instruction).
  2. Save current execution state. The CPU pushes critical state — most importantly the program counter (so it knows where to resume) and processor status flags — onto a stack or dedicated save area, so normal execution can be precisely resumed later.
  3. Identify the interrupt source and look up its handler. Using a mechanism called an interrupt vector (explained below), the CPU determines which specific handler routine corresponds to this particular interrupt.
  4. Transfer control to the Interrupt Service Routine (ISR). Execution jumps to the appropriate handler code, often with an accompanying privilege level change (from user mode to kernel/supervisor mode, since interrupt handlers frequently need privileged access).
  5. Execute the ISR. The handler does whatever work is needed — reading data from a device, updating a data structure, waking up a waiting process, and so on.
  6. Restore saved state and resume. Once the ISR completes, the CPU restores the previously saved program counter and status flags, and normal program execution resumes exactly where it left off, as if nothing had happened (from the interrupted program’s perspective).
Normal Program Execution
   |
   |   <-- Interrupt signal arrives -->
   v
[Save PC, flags, state]
   v
[Look up handler via interrupt vector]
   v
[Jump to Interrupt Service Routine (ISR)]
   v
[ISR executes: service the device/event]
   v
[Restore saved state]
   v
Resume Normal Program Execution (exactly where it left off)

Interrupt Vectors and the Interrupt Vector Table

Rather than having a single generic handler that has to figure out what caused every interrupt, modern CPUs use an interrupt vector table (IVT), sometimes called the Interrupt Descriptor Table (IDT) in x86 terminology — a data structure, typically stored in memory, that maps each possible interrupt or exception number to the memory address of its specific handler routine.

When an interrupt occurs, the hardware (or the interrupt controller, discussed next) provides an interrupt number, which the CPU uses as an index into this table to directly jump to the correct handler, without needing generic dispatch logic to figure out the cause. This is dramatically faster and cleaner than a single monolithic handler that has to poll every possible device to figure out what happened.

x86 systems, for example, reserve the first 32 vector numbers for CPU-generated exceptions (like divide error, page fault, general protection fault), with higher-numbered vectors available for hardware interrupts and software-triggered interrupts.

Interrupt Controllers: Managing Multiple Sources

In any real system, many different devices can generate interrupts, and the CPU needs a way to manage, prioritize, and route all of them. This is handled by dedicated interrupt controller hardware:

Interrupt Priority and Masking

Not all interrupts are equally urgent, and systems need mechanisms to manage this:

Precise vs. Imprecise Interrupts

Recall the concept of precise exceptions from the out-of-order execution article — this connects directly here. In a modern out-of-order superscalar CPU, ensuring that an interrupt or exception can be handled “precisely” (meaning the CPU can cleanly identify a specific point in program order such that everything before it is complete and nothing after it has taken effect) is essential for correctness and for the operating system to be able to resume execution reliably afterward. This is one of the key reasons the reorder buffer and in-order retirement discipline exist — they make it possible to interrupt an aggressively out-of-order-executing CPU at a precise, well-defined point despite all the internal parallelism happening under the hood.

Interrupt Service Routines (ISRs): Design Considerations

Because ISRs run in a special context — often with interrupts disabled, at elevated privilege, and potentially interrupting arbitrary code at an arbitrary point — they’re held to a stricter set of design constraints than ordinary code:

Real-World Applications and Examples

Performance Considerations

Advantages of Interrupt-Driven Design

Limitations and Trade-offs

Common Misconceptions

“Interrupts and exceptions are the same thing.” As covered above, they’re related but distinct — interrupts are asynchronous and externally triggered, while exceptions are synchronous and generated by the CPU itself as a direct consequence of the currently executing instruction.

“Polling is always worse than interrupts.” Not universally true — under extremely high-frequency event conditions, the per-event overhead of interrupt handling can actually exceed the cost of periodic polling, which is exactly why some high-throughput networking and storage code deliberately uses polling or hybrid interrupt/polling schemes under heavy load.

“An interrupt handler runs ‘whenever it wants’ relative to the interrupted program.” In practice, interrupt delivery is tightly constrained by the CPU’s own timing (checked at instruction boundaries) and by whatever masking is currently in effect — it isn’t an arbitrary mid-instruction intrusion, and the whole architecture is built specifically to guarantee clean, resumable transitions.

A Detailed Walkthrough: Handling a Keystroke

To make the abstract interrupt flow more tangible, consider what happens, at a simplified architectural level, when a user presses a key on a keyboard connected to a modern system:

  1. Hardware event. The keyboard controller detects the keypress and signals an interrupt request to the system’s interrupt controller (an APIC, in a modern x86 system).
  2. Interrupt controller routing. The interrupt controller, based on its current priority and masking configuration, determines that this interrupt should be delivered now (rather than deferred behind a higher-priority pending interrupt) and signals the appropriate CPU core.
  3. CPU acknowledges and saves state. At the next available instruction boundary, the CPU recognizes the pending interrupt, saves its current program counter and relevant status flags (so whatever program was running can resume exactly where it left off), and looks up the correct handler address using the interrupt vector table, indexed by the keyboard interrupt’s assigned vector number.
  4. ISR executes (top half). The keyboard interrupt handler runs, typically doing minimal, fast work: reading the specific key code from the keyboard controller’s hardware buffer, storing it into an operating-system-managed input queue, and acknowledging the interrupt to the controller so further keyboard interrupts can be delivered.
  5. Deferred work (bottom half), if applicable. More involved processing — like updating on-screen text, triggering application-level event callbacks, or handling more complex input processing logic — is often deferred to run outside the strict, time-critical interrupt context, in a less constrained execution environment.
  6. State restoration and resumption. Once the immediate interrupt handling completes, the CPU restores the previously saved program counter and flags, and the originally interrupted program resumes exactly as if the interruption had never happened, aside from the small amount of elapsed real time.

This entire sequence typically completes in a matter of microseconds on modern hardware — fast enough that, from a human perspective, keystrokes feel instantaneous, even though a genuinely complex, multi-layered hardware and software process is happening behind every single keypress.

Spurious Interrupts and Interrupt Acknowledgment

A subtlety worth understanding: interrupt controllers and devices generally require explicit acknowledgment from the handling software once an interrupt has been serviced, to signal that the device can safely generate further interrupts and that the interrupt controller can consider the current interrupt fully handled. Failing to properly acknowledge an interrupt can, depending on the specific hardware and interrupt controller design, either cause the same interrupt to be redelivered unnecessarily, or worse, cause the affected interrupt line to become “stuck,” preventing further legitimate interrupts of that type from being delivered at all — a class of bug that can be notoriously difficult to diagnose, since the system may appear to simply stop responding to a particular device with no obvious error message.

Related to this, spurious interrupts — interrupts that fire without a genuinely corresponding, currently-pending hardware event, often due to specific hardware race conditions or edge cases in interrupt controller behavior — are a recognized phenomenon that robust interrupt handler code needs to account for defensively, typically by verifying that a genuine pending condition actually exists before doing substantive work, rather than assuming every interrupt delivery necessarily corresponds to real, actionable device state.

Interrupt Handling in Real-Time Systems

Real-time operating systems (RTOS), used in contexts like industrial control systems, automotive systems, and aerospace applications, place extraordinarily strict requirements on interrupt latency — the maximum time between an interrupt occurring and its handler beginning execution needs to be not just low on average, but bounded and predictable in the worst case, since a real-time system’s correctness often depends on guaranteed timing behavior, not just typically-fast behavior. This drives real-time system design toward careful, deliberate interrupt priority schemes, strict limits on how long interrupts can remain masked during critical sections, and often specialized hardware and operating system designs (dedicated real-time kernels, or real-time extensions layered onto general-purpose operating systems) specifically engineered to provide these hard timing guarantees — a meaningfully different design goal than general-purpose desktop or server systems, which typically optimize for good average-case responsiveness and throughput rather than strict worst-case timing bounds.

Wrapping Up

Interrupts and exception handling form the nervous system of a modern computer — the mechanism that lets the outside world (devices, timers, hardware faults) and the CPU’s own internal error conditions reach into an otherwise self-contained stream of sequential instruction execution and demand attention, precisely and safely. Combined with DMA for efficient data movement and built on the precise-state guarantees that out-of-order CPUs work hard to preserve, interrupt handling is what allows a single processor to feel like it’s doing dozens of things “at once” — responding to your keystrokes, streaming audio, downloading a file, and running your applications — when in reality, it’s just extremely good at knowing when, and how, to stop what it’s doing.

Exit mobile version