x86 BIOS Interrupts: IDT, Interrupt Gates, and ISR Execution
Interrupt handling is one of the fundamental mechanisms connecting the x86 processor with hardware, software, and exceptional execution conditions. In BIOS and low-level firmware development, understanding interrupts requires more than knowing how to invoke an INT instruction: developers must understand interrupt vectors, descriptor tables, privilege checks, CPU-generated stack frames, and the transition into and out of interrupt service routines.
On x86 systems, the interrupt architecture changes substantially between real mode and protected mode. Real mode uses the Interrupt Vector Table (IVT), while protected mode uses the Interrupt Descriptor Table (IDT). In 64-bit mode, the IDT remains central, but descriptor and address formats expand to accommodate 64-bit handler addresses.
This article focuses primarily on the 32-bit protected-mode model while highlighting the distinctions between real mode, protected mode, and long mode.
๐งญ Interrupt Fundamentals #
An interrupt is an event that causes the processor to temporarily suspend its current execution flow and transfer control to an interrupt or exception handler.
The event may originate from:
- External hardware
- A software instruction such as
INT - A processor-detected exception
- A non-maskable hardware condition
After the handler completes, an appropriate return mechanism restores the interrupted execution context.
At a conceptual level, interrupt processing follows this path:
Interrupt event โ vector number โ descriptor lookup โ privilege checks โ context save โ ISR execution โ context restoration
The exact mechanics depend on the processor mode and the type of event being handled.
Hardware Interrupts #
External hardware interrupts originate outside the CPU core. Traditional x86 systems commonly use an interrupt controller such as the 8259A Programmable Interrupt Controller (PIC) to collect and route hardware interrupt requests.
Typical sources include:
- Keyboard controllers
- Timers
- Storage controllers
- Network devices
- Peripheral controllers
The interrupt controller ultimately supplies an interrupt vector that allows the processor to identify the appropriate handler.
Modern systems may use more advanced interrupt controllers such as the Local APIC and I/O APIC, but the fundamental concept remains the same: an external event is mapped to a processor interrupt vector.
Software Interrupts #
Software interrupts are explicitly generated by instructions such as:
int 0x80
The operand specifies the interrupt vector.
Historically, BIOS services commonly use software interrupts in real mode. For example, firmware interfaces traditionally expose services through interrupt vectors such as INT 10h for video services and INT 13h for disk services.
In protected-mode operating systems, software interrupt mechanisms have also been used for system calls and debugging facilities.
Maskable Interrupts #
Maskable hardware interrupts can be controlled through the processor’s interrupt-enable flag, IF.
The two classic instructions are:
cli ; Clear IF and disable maskable interrupts
sti ; Set IF and enable maskable interrupts
Masking interrupts is useful when executing critical sections in which asynchronous interruption could corrupt shared state or violate timing assumptions.
However, CLI does not disable every form of interrupt or exception. In particular, non-maskable interrupts and processor exceptions follow different delivery rules.
Non-Maskable Interrupts #
A Non-Maskable Interrupt (NMI) is designed for conditions that should not be suppressed by the normal interrupt-enable mechanism.
NMIs may be associated with serious hardware conditions, system-level fault reporting, or platform-specific reliability mechanisms.
Because the normal IF flag does not control NMI delivery, firmware and operating-system developers must treat NMI handlers as a separate class of interrupt infrastructure.
๐งฉ Interrupt Vectors and Descriptor Tables #
An interrupt vector is an index identifying an interrupt or exception handler.
x86 provides 256 possible vector numbers:
0x00โ0xFF
The vector itself does not contain the handler address in protected mode. Instead, it selects an entry in the processor’s interrupt descriptor table.
The lookup can be expressed conceptually as:
IDT entry address = IDTR.base + vector ร descriptor_size
For 32-bit protected mode, an IDT gate descriptor occupies 8 bytes.
Therefore:
Entry 0 = IDTR.base + 0 ร 8
Entry 1 = IDTR.base + 1 ร 8
Entry 2 = IDTR.base + 2 ร 8
...
Entry 255 = IDTR.base + 255 ร 8
This makes the vector-to-handler mapping deterministic and extremely fast.
Real Mode: The Interrupt Vector Table #
Real mode uses a different structure called the Interrupt Vector Table (IVT).
The IVT begins at physical address:
0x00000
and contains 256 entries.
Each entry is 4 bytes:
16-bit offset
16-bit segment
Therefore, the complete IVT occupies:
256 ร 4 = 1024 bytes
or:
0x00000โ0x003FF
The processor uses the vector number to index directly into this table.
For example:
Vector 0x10
Entry address = 0x10 ร 4
= 0x40
The four bytes beginning at 0x40 contain the Segment:Offset address of the corresponding handler.
This is fundamentally different from protected mode, where the vector indexes an IDT descriptor containing metadata such as a segment selector, handler offset, gate type, privilege level, and presence state.
๐๏ธ The Interrupt Descriptor Table #
In protected mode, the Interrupt Descriptor Table (IDT) defines how the processor handles interrupts and exceptions.
Each entry describes a gate through which the processor transfers control to a handler.
For 32-bit protected mode, an interrupt or trap gate is represented by an 8-byte descriptor containing fields such as:
- Handler offset
- Code-segment selector
- Present bit
- Descriptor Privilege Level
- Storage-segment indicator
- Gate type
- Operand-size information
The descriptor allows the processor to validate the requested transition before transferring control.
Interrupt Gate #
An interrupt gate is commonly used for interrupt and exception handlers.
In 32-bit protected mode, an interrupt gate contains the address of the handler and the selector for the code segment in which the handler executes.
A significant architectural property is that when the processor transfers through an interrupt gate, it clears IF. This prevents additional maskable interrupts from interrupting the handler unless the handler explicitly re-enables them.
This behavior differs from a trap gate.
Trap Gate #
A trap gate provides a similar transfer mechanism but does not automatically clear IF.
Trap gates are therefore useful when nested maskable interrupts are allowed or when the semantics of the event require interrupts to remain enabled.
They are commonly associated with software-generated traps and debugging-related mechanisms, although the exact assignment depends on the operating-system or firmware design.
Task Gate #
A task gate identifies a Task State Segment (TSS) rather than directly identifying an interrupt handler.
It is associated with the hardware-supported task-switching mechanism of protected-mode x86.
Modern operating systems generally avoid hardware task switching in favor of software-managed context switching, but task gates remain part of the architectural interrupt-descriptor model.
๐งฑ 32-bit Interrupt Gate Structure #
A 32-bit interrupt or trap gate occupies 8 bytes.
Conceptually, its fields can be represented as:
31 16 15 8 7 5 4 0
+---------------------------+-----------+----+-----+
| Offset 31..16 | P DPL | S |Type |
+---------------------------+-----------+----+-----+
| Code Selector | Offset 15..0 |
+---------------------------+-----------------------+
The principal fields are:
Offset #
The handler’s offset within its code segment.
In 32-bit mode, the offset is divided into:
Offset 15..0
Offset 31..16
The processor combines these fields when constructing the handler address.
Selector #
The segment selector identifies the code segment containing the interrupt handler.
The processor uses this selector together with the Global Descriptor Table (GDT) to determine the target code segment.
Present Bit #
The P bit indicates whether the descriptor is currently valid.
P = 1 โ descriptor present
P = 0 โ descriptor not present
Attempting to use an unavailable descriptor can result in a processor exception rather than transferring to the intended handler.
Descriptor Privilege Level #
The DPL specifies the privilege level associated with access to the gate.
x86 privilege levels range from:
Ring 0 โ highest privilege
Ring 1
Ring 2
Ring 3 โ lowest privilege
DPL checks are particularly important for software-generated interrupts because they help determine whether code executing at a given privilege level is permitted to invoke a particular gate.
Storage Segment Bit #
For interrupt, trap, and task gates, the descriptor is a system descriptor, so the S bit is cleared.
S = 0 โ system descriptor
Gate Type #
The type field identifies the descriptor’s function, such as:
- 32-bit interrupt gate
- 32-bit trap gate
- Task gate
The exact encoding is defined by the x86 architecture.
๐ IDTR: Locating the IDT #
The processor uses the Interrupt Descriptor Table Register (IDTR) to locate the active IDT.
In 32-bit protected mode, the IDTR contains a 48-bit descriptor:
+-----------------------------------+-----------------------+
| 32-bit Base Address | 16-bit Limit |
| 31..0 | 15..0 |
+-----------------------------------+-----------------------+
The two fields are:
- Base: Linear address of the first byte of the IDT.
- Limit: Size of the IDT in bytes minus one.
The limit therefore represents the highest valid byte offset within the table rather than the number of descriptors.
Calculating the IDT Boundary #
Given:
BASE
LIMIT
the first byte of the IDT is:
Start = BASE
and the last valid byte is:
End = BASE + LIMIT
The IDT occupies:
LIMIT + 1
bytes.
For example, if:
BASE = 0x00100000
LIMIT = 0x07FF
then:
Start = 0x00100000
End = 0x001007FF
Size = 0x800 bytes
A common mistake is to subtract one again when calculating the end address. Because the limit already represents the offset of the final valid byte, the correct expression is BASE + LIMIT.
IDT Size in 32-bit Protected Mode #
If all 256 vectors use 8-byte descriptors:
256 ร 8 = 2048 bytes
Therefore, a complete 32-bit IDT requires:
0x800 bytes
and its corresponding limit is:
0x7FF
The IDT does not necessarily have to contain all 256 entries, but the limit must be large enough for every vector the processor may legally access.
๐ LIDT and SIDT #
Two instructions provide direct access to the processor’s IDT location.
LIDT #
LIDT loads the IDTR from a memory operand.
In 32-bit mode, the operand contains:
16-bit limit
32-bit base
For example:
lidt [idtr_descriptor]
A typical descriptor might be assembled as:
idtr_descriptor:
dw idt_end - idt_start - 1
dd idt_start
The LIDT instruction is privileged and is normally executed during operating-system or firmware initialization.
SIDT #
SIDT stores the current IDTR contents into memory:
sidt [saved_idtr]
This allows low-level software to inspect the currently configured IDT base and limit.
The instruction is useful for diagnostics, debugging, and low-level runtime analysis.
โก Interrupt Execution Flow #
Interrupt processing can be understood as a sequence of architectural operations.
1. Interrupt Request #
An event occurs.
Possible sources include:
- External hardware
- Software
INTinstruction - Processor exception
- NMI
The processor determines the corresponding vector.
2. Vector Resolution #
The vector identifies an entry in the interrupt table.
In protected mode, the processor effectively computes:
Descriptor = IDTR.base + vector ร 8
for the 8-byte descriptor format used by 32-bit protected mode.
The processor also verifies that the referenced descriptor lies within the IDT limit.
3. Descriptor Validation #
The processor validates the descriptor and determines:
- Whether it is present
- What gate type it represents
- Which code segment should execute
- Whether the requested privilege transition is permitted
- Where the handler begins execution
For software-generated interrupts, privilege checks involving the gate’s DPL are particularly important.
4. Context Preservation #
Before transferring control, the processor saves the execution state required to return to the interrupted code.
For a same-privilege-level transition, the saved state includes:
EFLAGS
CS
EIP
For a privilege-level transition, additional stack state is involved, including:
SS
ESP
The exact stack frame also depends on whether the event is an exception that pushes an error code.
5. Transfer to the ISR #
The processor loads the target code-segment state and instruction pointer from the descriptor.
Execution then begins at the interrupt service routine:
ISR:
; preserve registers as required
; service interrupt
; acknowledge hardware if required
; restore registers
iret
The ISR itself is responsible for preserving any additional general-purpose registers that its implementation needs to modify.
6. Interrupt Handler Execution #
The handler performs the work associated with the event.
For a hardware interrupt, this can include:
- Reading device state
- Clearing or acknowledging the interrupt source
- Updating firmware or kernel state
- Scheduling follow-up work
- Communicating with another subsystem
A well-designed ISR should generally minimize the amount of work performed synchronously in the interrupt context.
7. Returning with IRET #
The handler returns using IRET in 32-bit protected mode:
iret
IRET restores the processor state saved during interrupt entry, including the instruction pointer, code segment, and flags.
When a privilege transition occurred, the return sequence also restores the previous stack segment and stack pointer.
The processor can then resume execution at the point where the interrupt interrupted the original instruction stream.
๐ก๏ธ Privilege Transitions #
One of the most important capabilities of the protected-mode interrupt mechanism is controlled privilege transition.
Consider a user-mode application executing at Ring 3 while the interrupt handler executes at Ring 0.
Conceptually:
Ring 3 application
|
| interrupt / exception
v
IDT gate
|
| privilege validation
v
Ring 0 ISR
|
| IRET
v
Ring 3 application
When the processor changes privilege levels, it switches to the appropriate privileged stack and preserves the previous stack state so that IRET can later restore it.
This mechanism forms an important foundation for operating-system kernels and protected firmware environments.
๐งฎ Real Mode vs. Protected Mode #
The interrupt architecture differs significantly between x86 execution modes.
| Feature | Real Mode | 32-bit Protected Mode |
|---|---|---|
| Interrupt table | IVT | IDT |
| Table location | Fixed at 0x00000 |
Configurable through IDTR |
| Entry size | 4 bytes | 8 bytes |
| Entry contents | Segment:Offset | Gate descriptor |
| Privilege checks | Minimal | Hardware-enforced |
| Handler addressing | 16:16 | Selector + 32-bit offset |
| Typical firmware usage | BIOS services | Protected-mode firmware/OS |
| Return instruction | IRET |
IRET |
This distinction is particularly important in BIOS development because traditional BIOS execution begins in real mode, while modern firmware can transition through protected mode and eventually long mode during system initialization.
๐ง Practical BIOS Development Considerations #
Interrupt development at the BIOS or firmware level requires careful coordination between the processor, interrupt controller, descriptor tables, and handler code.
Several issues deserve particular attention.
Interrupt Vector Ownership #
Firmware must know which vectors are currently owned by BIOS services, hardware, or the operating system.
Replacing an existing vector without preserving the original handler can break firmware services or later system initialization.
Interrupt Controller Configuration #
The processor’s IDT does not independently determine the source of every hardware interrupt.
External interrupt routing also depends on the interrupt-controller configuration.
On legacy systems, the 8259A PIC maps hardware IRQ lines to interrupt vectors. Modern platforms generally use APIC-based mechanisms.
Stack Availability #
Interrupt handlers execute using a stack.
A corrupted, undersized, or incorrectly initialized stack can cause failures that appear unrelated to the original interrupt.
During privilege transitions, the processor may also switch to a different stack, making stack configuration part of the interrupt-security boundary.
Register Preservation #
The processor automatically saves only part of the execution state.
An ISR that modifies general-purpose registers normally needs to preserve them explicitly when the interrupted code depends on their values.
A typical low-level handler may therefore use:
pushad
; handler body
popad
iret
where appropriate for the execution mode and calling convention.
Exception Error Codes #
Some processor exceptions automatically push an error code onto the stack while others do not.
An exception-dispatch framework must account for this difference before executing a common handler or attempting to return with IRET.
A robust implementation often normalizes the stack frame so that different exception types can be dispatched through a common entry path.
๐ง Key Takeaways #
The x86 interrupt architecture can be reduced to several core relationships:
Interrupt event
โ
Interrupt vector
โ
IDT / IVT lookup
โ
Descriptor validation
โ
Privilege and segment checks
โ
CPU context save
โ
Interrupt Service Routine
โ
IRET
โ
Original execution resumes
The most important distinction for BIOS developers is between real-mode IVT handling and protected-mode IDT handling.
In real mode, a vector directly indexes a four-byte Segment:Offset entry in the IVT at physical address 0x00000.
In protected mode, the vector indexes an IDT descriptor referenced through IDTR. That descriptor provides the processor with the information necessary to validate the transition and locate the interrupt handler.
Understanding this mechanism is essential for implementing BIOS services, firmware interrupt handlers, bootloaders, operating-system entry points, exception handlers, and other low-level x86 components.
The interrupt system is ultimately more than a mechanism for jumping to a function. It is a hardware-enforced execution framework that combines event routing, handler discovery, privilege control, context preservation, and controlled returnโmaking it one of the foundational mechanisms behind protected x86 software.