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.
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
, andint $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.
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:
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.