What Is an Interrupt Vector and How Does It Work?

An interrupt vector is a pointer stored in memory that tells the processor where to find the code it should run when a specific interrupt occurs. These pointers are organized in an array called a vector table, where each entry corresponds to a numbered interrupt source. When a device or software event triggers an interrupt, the processor uses that interrupt’s assigned number as an index into the table, looks up the address stored there, and jumps to that code.

How the Vector Table Works

The operating system creates an array of pointers in memory known as the interrupt vector table. The ith entry in this array points to the handler code for the device or event assigned vector number i. Think of it like a contacts list: when interrupt number 5 fires, the processor looks at slot 5 in the table and calls whatever function is listed there.

When a hardware device needs the processor’s attention, it sends its vector number over the system bus. The processor (or the operating system, depending on the architecture) takes that number, uses it as an index into the vector table, retrieves the address stored at that slot, and begins executing the code at that address. That code is called an Interrupt Service Routine, or ISR. Once the ISR finishes, control returns to whatever the processor was doing before.

What’s Actually Inside Each Entry

The exact contents of a vector table entry depend on the processor architecture. On ARM Cortex-M processors, each entry is a straightforward 32-bit address pointing to the ISR’s location in memory. The very first entry is special: it holds the initial value of the stack pointer, and the second entry holds the address of the reset handler. After that come entries for system exceptions like non-maskable interrupts and hard faults, followed by entries for external device interrupts.

On x86 processors, the structure is more complex. The vector table is called the Interrupt Descriptor Table (IDT), and it contains 256 entries, each 8 bytes long. Every entry includes a segment selector, an offset pointing to the handler’s entry point, a privilege level field that controls which software is allowed to trigger the interrupt, and a flag indicating whether the entry is an interrupt gate or a trap gate. The distinction matters because interrupt gates automatically disable further interrupts while the handler runs, while trap gates do not.

Where the Table Lives in Memory

On ARM Cortex-M systems, the vector table starts at memory address 0x00000000 after a system reset. The vectors are stored in ROM at the beginning of memory, which means they’re baked into the firmware at compile time. However, the processor has a register called the Vector Table Offset Register (VTOR) that holds the base address of the table. By writing a new address to VTOR, an operating system can relocate the entire vector table to a different spot in memory, which is useful when switching between a bootloader and an application or when an OS needs to install its own handlers.

On x86 systems, a dedicated register called the IDTR holds the base address and size of the IDT. The operating system loads this register during boot, and from that point on, the processor knows exactly where to find the table whenever an interrupt arrives.

Hardware Interrupts vs. Software Interrupts

Both hardware events and software instructions use the same vector table, but they arrive differently. A hardware interrupt is triggered by an external device, like a keyboard controller or network card, sending a signal to the processor. A software interrupt is triggered by an instruction in a running program. Both result in the processor looking up a vector number in the table and jumping to the corresponding handler.

Linux illustrates this clearly. Vectors 0 through 31 are reserved for processor exceptions and non-maskable interrupts (things like divide-by-zero errors and page faults). Vectors 32 through 47 handle maskable hardware interrupts from devices. Vector 128 is the one Linux historically used for system calls: when a program in user mode executed the instruction int 0x80, the processor looked up entry 128 in the vector table and jumped into kernel mode to run the system call handler. The same lookup mechanism serves completely different purposes depending on what triggered it.

Priority and Preemption

When multiple interrupts fire at the same time, the system needs a way to decide which one runs first. On ARM processors, this job falls to the Nested Vectored Interrupt Controller (NVIC), which assigns a configurable priority level to each interrupt vector. If two interrupts arrive simultaneously, the one with the higher priority runs first. The lower-priority interrupt is “pended,” meaning it’s marked as waiting and will execute as soon as the higher-priority handler finishes.

Preemption can also happen mid-handler. If a high-priority interrupt fires while a lower-priority ISR is already running, the processor pauses the current handler, saves its state, and jumps to the higher-priority one. This creates nested interrupts. Each interrupt has an active status bit that gets set when its handler starts running and cleared when it returns. The nesting is managed automatically by the hardware, so the original handler resumes exactly where it left off once the higher-priority work is done.

Real-World Vector Assignments

In a Linux system running on multi-core hardware, specific vector numbers are assigned to internal coordination tasks between processor cores. Vector 0x30 is the reschedule vector, used to tell another core that a newly woken process should run there. Vector 0x31 is the TLB invalidation vector, signaling other cores to flush their translation caches when memory mappings change. Vector 0x41 handles a local timer used for process scheduling and profiling. Vector 0x50 is a general-purpose “call function” vector that lets one core ask another to execute a specific function.

On embedded ARM systems, the assignments are simpler and more direct. The vector at offset 0x0000002C points to the handler for supervisor calls (used by an operating system’s task scheduler), while offset 0x00000040 points to the handler for GPIO Port A, a common hardware pin used for buttons, sensors, or communication lines. Each peripheral on the chip gets its own vector slot, making it straightforward to wire up device-specific code.

What Happens With Unassigned Vectors

If the processor receives an interrupt for a vector that has no handler installed, the result depends on the operating system. In systems like Zephyr (a real-time OS for embedded devices), all unused entries in the vector table are filled with a default “spurious IRQ handler.” If one of these entries is ever triggered, the handler raises a fatal system error and halts execution. This is a safety net: an unexpected interrupt usually means a hardware fault or a configuration mistake, and crashing immediately is preferable to unpredictable behavior. Most operating systems take a similar approach, treating an unassigned vector as a sign that something has gone wrong.