Initializing the Interrupt Descriptor Table

Now that we understand what the 80×86 microprocessors do with interrupts and exceptions at the hardware level, we can move on to describe how the Interrupt Descriptor Table is initialized.

Remember that before the kernel enables the interrupts, it must load the initial address of the IDT table into the idtr register and initialize all the entries of that table. This activity is done while initializing the system (see Appendix A).

The int instruction allows a User Mode process to issue an interrupt signal that has an arbitrary vector ranging from 0 to 255. Therefore, initialization of the IDT must be done carefully, to block illegal interrupts and exceptions simulated by User Mode processes via int instructions. This can be achieved by setting the DPL field of the particular Interrupt or Trap Gate Descriptor to 0. If the process attempts to issue one of these interrupt signals, the control unit checks the CPL value against the DPL field and issues a “General protection " exception.

In a few cases, however, a User Mode process must be able to issue a programmed exception. To allow this, it is sufficient to set the DPL field of the corresponding Interrupt or Trap Gate Descriptors to 3 — that is, as high as possible.

Let’s now see how Linux implements this strategy.

Interrupt, Trap, and System Gates

As mentioned in the earlier section "Interrupt Descriptor Table,” Intel provides three types of interrupt descriptors : Task, Interrupt, and Trap Gate Descriptors. Linux uses a slightly different breakdown and terminology from Intel when classifying the interrupt descriptors included in the Interrupt Descriptor Table:

Interrupt gate

An Intel interrupt gate that cannot be accessed by a User Mode process (the gate’s DPL field is equal to 0). All Linux interrupt handlers are activated by means of interrupt gates , and all are restricted to Kernel Mode.

System gate

An Intel trap gate that can be accessed by a User Mode process (the gate’s DPL field is equal to 3). The three Linux exception handlers associated with the vectors 4, 5, and 128 are activated by means of system gates , so the three assembly language instructions into , bound , and int $0x80 can be issued in User Mode.

System interrupt gate

An Intel interrupt gate that can be accessed by a User Mode process (the gate’s DPL field is equal to 3). The exception handler associated with the vector 3 is activated by means of a system interrupt gate, so the assembly language instruction int3 can be issued in User Mode.

Trap gate

An Intel trap gate that cannot be accessed by a User Mode process (the gate’s DPL field is equal to 0). Most Linux exception handlers are activated by means of trap gates .

Task gate

An Intel task gate that cannot be accessed by a User Mode process (the gate’s DPL field is equal to 0). The Linux handler for the “Double fault " exception is activated by means of a task gate.

The following architecture-dependent functions are used to insert gates in the IDT:

set_intr_gate(n,addr)

Inserts an interrupt gate in the n th IDT entry. The Segment Selector inside the gate is set to the kernel code’s Segment Selector. The Offset field is set to addr, which is the address of the interrupt handler. The DPL field is set to 0.

set_system_gate(n,addr)

Inserts a trap gate in the n th IDT entry. The Segment Selector inside the gate is set to the kernel code’s Segment Selector. The Offset field is set to addr, which is the address of the exception handler. The DPL field is set to 3.

set_system_intr_gate(n,addr)

Inserts an interrupt gate in the n th IDT entry. The Segment Selector inside the gate is set to the kernel code’s Segment Selector. The Offset field is set to addr, which is the address of the exception handler. The DPL field is set to 3.

set_trap_gate(n,addr)

Similar to the previous function, except the DPL field is set to 0.

set_task_gate(n,gdt)

Inserts a task gate in the n th IDT entry. The Segment Selector inside the gate stores the index in the GDT of the TSS containing the function to be activated. The Offset field is set to 0, while the DPL field is set to 3.

Preliminary Initialization of the IDT

The IDT is initialized and used by the BIOS routines while the computer still operates in Real Mode. Once Linux takes over, however, the IDT is moved to another area of RAM and initialized a second time, because Linux does not use any BIOS routine (see Appendix A).

The IDT is stored in the idt_table table, which includes 256 entries. The 6-byte idt_descr variable stores both the size of the IDT and its address and is used in the system initialization phase when the kernel sets up the idtr register with the lidt assembly language instruction.[*]

During kernel initialization, the setup_idt( ) assembly language function starts by filling all 256 entries of idt_table with the same interrupt gate, which refers to the ignore_int( ) interrupt handler:

    setup_idt:
        lea ignore_int, %edx
        movl $(_ _KERNEL_CS << 16), %eax
        movw %dx, %ax       /* selector = 0x0010 = cs */
        movw $0x8e00, %dx   /* interrupt gate, dpl=0, present */
        lea idt_table, %edi
        mov $256, %ecx
    rp_sidt:
        movl %eax, (%edi)
        movl %edx, 4(%edi)
        addl $8, %edi
        dec %ecx
        jne rp_sidt
        ret

The ignore_int( ) interrupt handler, which is in assembly language, may be viewed as a null handler that executes the following actions:

  1. Saves the content of some registers in the stack.

  2. Invokes the printk( ) function to print an “Unknown interrupt” system message.

  3. Restores the register contents from the stack.

  4. Executes an iret instruction to restart the interrupted program.

The ignore_int( ) handler should never be executed. The occurrence of “Unknown interrupt” messages on the console or in the log files denotes either a hardware problem (an I/O device is issuing unforeseen interrupts) or a kernel problem (an interrupt or exception is not being handled properly).

Following this preliminary initialization, the kernel makes a second pass in the IDT to replace some of the null handlers with meaningful trap and interrupt handlers. Once this is done, the IDT includes a specialized interrupt, trap, or system gate for each different exception issued by the control unit and for each IRQ recognized by the interrupt controller.

The next two sections illustrate in detail how this is done for exceptions and interrupts.



[*] Some old Pentium models have the notorious “f00f” bug, which allows User Mode programs to freeze the system. When executing on such CPUs, Linux uses a workaround based on initializing the idtr register with a fix-mapped read-only linear address pointing to the actual IDT (see the section "Fix-Mapped Linear Addresses" in Chapter 2).

Get Understanding the Linux Kernel, 3rd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.