Course contentsShow
← Rust
Lesson 1495 of 1,91012. Unsafe Rust and Low-Level AccessFree lesson

Interrupt Descriptor Tables

You will configure the interrupt vector table to route hardware events to Rust functions.

In this lesson, you will learn how to bridge the gap between hardware-triggered events and your Rust code by configuring the Interrupt Descriptor Table (IDT).

The Role of the IDT

The Interrupt Descriptor Table is a data structure used by the CPU to determine how to handle hardware interrupts and software exceptions. When an external event occurs (like a timer pulse or a keyboard stroke), the CPU pauses execution and looks up the corresponding entry in the IDT. This entry contains the memory address of the function—the "Interrupt Service Routine" (ISR)—that should handle the event.

Defining the Table Structure

In Rust, we represent this table as a repr(C) array or struct to ensure the memory layout matches what the CPU expects. Each descriptor consists of 8 or 16 bytes containing the handler's address, segment selectors, and privilege flags.

Since we are operating in a no_std environment, we must define these handlers carefully. A common approach uses the x86_64 crate abstractions to map an interrupt index (0–255) to a Rust function:

use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};

static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new();

pub fn init_idt() {
    unsafe {
        // Route the timer interrupt (index 32) to our handler
        IDT.interrupts[32].set_handler_fn(timer_interrupt_handler);
        IDT.load(); // Load the address into the IDTR register
    }
}

extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) {
    // Perform low-level hardware interaction here
}

Critical Considerations

  1. Calling Convention: ISRs must use the x86-interrupt calling convention. This ensures that the compiler preserves all general-purpose registers and terminates the function with an iretq instruction, which restores the CPU to its previous state.
  2. Safety: Because loading the IDT involves modifying global processor state, this process is inherently unsafe. You must ensure that the IDT memory remains valid for as long as the CPU is running.
  3. Reentrancy: Remember that interrupts can happen at almost any time. If your handler modifies shared state, you must ensure that access is synchronized to prevent race conditions during execution.

Key Takeaway: The IDT acts as a gateway; by mapping hardware vectors to extern "x86-interrupt" Rust functions, you create the precise routing necessary to handle asynchronous hardware events.