Fundamentals 13 min read

Understanding IDT Initialization, Interrupt Gates, and System Call Implementation in the Linux Kernel

This article explains what an IDT is, how it is initialized, the structure of interrupt descriptor entries and gates, the implementation of traditional system calls via int 0x80, and the setup and handling of hardware interrupts in the Linux kernel.

360 Tech Engineering
360 Tech Engineering
360 Tech Engineering
Understanding IDT Initialization, Interrupt Gates, and System Call Implementation in the Linux Kernel

The Interrupt Descriptor Table (IDT) defines how the CPU handles interrupts and exceptions; it contains 256 entries, each describing a gate that points to an interrupt handler.

#define IDT_ENTRIES 256 gate_desc idt_table[IDT_ENTRIES] __page_aligned_bss;

Each entry is represented by a gate_struct that stores the handler address split into low, middle, and (on x86_64) high parts, the segment selector, and a set of attribute bits.

struct gate_struct { u16 offset_low; u16 segment; struct idt_bits bits; u16 offset_middle; #ifdef CONFIG_X86_64 u32 offset_high; u32 reserved; #endif } __attribute__((packed));

The idt_bits structure defines the IST index, type (interrupt, trap, call, task), DPL (descriptor privilege level), and present flag.

struct idt_bits { u16 ist : 3, zero : 5, type : 5, dpl : 2, p : 1; } __attribute__((packed));

Gate types are defined by an enum, e.g., GATE_INTERRUPT = 0xE for interrupt gates and GATE_TRAP = 0xF for trap gates.

enum { GATE_INTERRUPT = 0xE, GATE_TRAP = 0xF, GATE_CALL = 0xC, GATE_TASK = 0x5 };

Macros such as INTG , SYSG , and ISTG fill a gate_desc with the appropriate fields; the core macro G performs the assignment.

#define G(_vector, _addr, _ist, _type, _dpl, _segment) \ { .vector = _vector, .bits.ist = _ist, .bits.type = _type, .bits.dpl = _dpl, .bits.p = 1, .addr = _addr, .segment = _segment }

To install an interrupt gate, the kernel uses set_intr_gate , which populates an idt_data structure and calls idt_setup_from_table to copy the descriptor into the IDT.

static void set_intr_gate(unsigned int n, const void *addr) { struct idt_data data; BUG_ON(n > 0xFF); memset(&data, 0, sizeof(data)); data.vector = n; data.addr = addr; data.segment = __KERNEL_CS; data.bits.type = GATE_INTERRUPT; data.bits.p = 1; idt_setup_from_table(idt_table, &data, 1, false); }

Traditional 32‑bit system calls use the software interrupt int 0x80 . The kernel registers an entry in the IDT with DPL 3, allowing user‑space to invoke it.

#define SYSG(_vector, _addr) G(_vector, _addr, DEFAULT_STACK, GATE_INTERRUPT, DPL3, __KERNEL_CS)

The handler entry_INT80_32 saves registers, calls do_int80_syscall_32 , which eventually invokes the appropriate system‑call function from ia32_sys_call_table and returns to user space.

ENTRY(entry_INT80_32) ASM_CLAC pushl %eax /* pt_regs->orig_ax */ SAVE_ALL movl %esp, %eax call do_int80_syscall_32 INTERRUPT_RETURN ENDPROC(entry_INT80_32)

Hardware interrupts are also set up during kernel boot. init_IRQ eventually calls idt_setup_apic_and_irq_gates , which fills the IDT with entries for external vectors and then registers each with set_intr_gate .

void __init idt_setup_apic_and_irq_gates(void) { int i = FIRST_EXTERNAL_VECTOR; idt_setup_from_table(idt_table, apic_idts, ARRAY_SIZE(apic_idts), true); for_each_clear_bit_from(i, system_vectors, FIRST_SYSTEM_VECTOR) { void *entry = irq_entries_start + 8 * (i - FIRST_EXTERNAL_VECTOR); set_intr_gate(i, entry); } }

The assembly block irq_entries_start generates a series of stub handlers that push the interrupt vector (negated) and jump to common_interrupt , which ultimately calls do_IRQ to dispatch the interrupt.

ENTRY(irq_entries_start) .rept (FIRST_SYSTEM_VECTOR - FIRST_EXTERNAL_VECTOR) pushq $(~vector+0x80) jmp common_interrupt .align 8 .endr END(irq_entries_start)

do_IRQ retrieves the irq_desc for the vector, calls generic_handle_irq_desc , and finally restores the previous register state.

__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs) { unsigned vector = ~regs->orig_ax; struct irq_desc *desc = __this_cpu_read(vector_irq[vector]); if (likely(!IS_ERR_OR_NULL(desc))) generic_handle_irq_desc(desc); else ack_APIC_irq(); return 1; }

Overall, the article walks through the definition of the IDT, the layout of its entries, how the kernel populates it for both software and hardware interrupts, and how those interrupts are dispatched to their handlers.

assemblysystem callLinux KernelinterruptsIDTOS Fundamentals
360 Tech Engineering
Written by

360 Tech Engineering

Official tech channel of 360, building the most professional technology aggregation platform for the brand.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.