Now that you understand what the Intel processor does 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 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.
As mentioned in the earlier section Section 4.2.3, Intel provides three types of interrupt descriptors: Task, Interrupt, and Trap Gate Descriptors. Task Gate Descriptors are irrelevant to Linux, but its Interrupt Descriptor Table contains several Interrupt and Trap Gate Descriptors. Linux classifies them as follows, using a slightly different breakdown and terminology from Intel:
- 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 four Linux exception handlers associated with the vectors 3, 4, 5, and 128 are activated by means of system gates, so the four assembly language instructions
int3
,into
,bound
, andint $0x80
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.
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_trap_gate(n,addr)
Similar to the previous function, except the DPL field is set to 0.
The IDT is initialized and used by the BIOS routines when 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, since Linux does not use any BIOS routines (see Appendix A).
The idt
variable points to the IDT, while the IDT
itself is stored in the idt_table
table, which
includes 256 entries.[26] The 6-byte
idt_descr
variable stores both the size of the IDT
and its address and is used only when the kernel initializes 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:
Saves the content of some registers in the stack
Invokes the
printk( )
function to print an “Unknown interrupt” system messageRestores the register contents from the stack
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.
[26] Some Pentium models have the notorious " f00f” bug, which allows a User Mode program to freeze the system. When executing on such CPUs, Linux uses a workaround based on storing the IDT in a write-protected page frame. The workaround for the bug is offered as an option when the user compiles the kernel.
Get Understanding the Linux Kernel, Second 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.