当计算机运行在实模式时,IDT被初始化并由BIOS使用。然而,一旦真正进入了Linux内核,IDT就被移到内存的另一个区域,并进行进入实模式的初步初始化。
1.中断描述表寄存器IDTR的初始化
用汇编指令LIDT对中断向量表寄存器IDTR进行初始化,其代码在arch/i386/boot/setup.S
中:
lidt idt_48
# load idt with 0,0
…
idt_48:
.word 0
# idt limit = 0
.word 0, 0
# idt base =
2.把IDT表的起始地址装入IDTR
用汇编指令LIDT装入IDT的大小和它的地址(在arch/i386/kernel/head.S中):
#define IDT_ENTRIES 256
.globl SYMBOL_NAME(idt)
lidt idt_descr
…
idt_descr:
.word IDT_ENTRIES*8-1
# idt contains 256 entries
SYMBOL_NAME(idt):
.long SYMBOL_NAME(idt_table)
其中idt为一个全局变量,内核对这个变量的引用就可以获得IDT表的地址。表的长度为256´8=2048字节。
3.用setup_idt()函数填充idt_table表中的256个表项。
我们首先要看一下idt_table的定义(在arch/i386/kernel/traps.c中):
struct
desc_struct idt_table[256] __attribute__((__section__(".data.idt")))
= { {0, 0}, };
desc_struct结构定义为:
struct desc_struct {
unsigned
long a,b }
对idt_table变量还定义了其属性(__attribute__),__section__是汇编中的“节”,指定了idt_table的起始地址存放在数据节的idt变量中,如上面第2条所述。
在对idt_table表进行填充时,使用了一个空的中断处理程序ignore_int()。因为现在处于初始化阶段,还没有任何中断处理程序,因此用这个空的中断处理程序填充每个表项。ignore_int()是一段汇编程序(在head.S中):
ignore_int:
cld
#方向标志清0,表示串指令自动增长它们的索引寄存器(esi和edi)
pushl %eax
pushl %ecx
pushl %edx
pushl %es
pushl %ds
movl $(__KERNEL_DS),%eax
movl %eax,%ds
movl %eax,%es
pushl $int_msg
call SYMBOL_NAME(printk)
popl %eax
popl %ds
popl %es
popl %edx
popl %ecx
popl %eax
iret
int_msg:
.asciz
"Unknown interrupt\n"
ALIGN
该中断处理程序模仿一般的中断处理程序,执行如下操作:
·
在栈中保存一些寄存器的值
·
调用printk()函数打印“Unknown interrupt”系统信息
·
从栈中恢复寄存器的内容
·
执行iret指令以恢复被中断的程序。
实际上,ignore_int()处理程序应该从不执行。如果在控制台或日志文件中出现了 “Unknown interrupt”消息,说明要么是出现了一个硬件问题(一个I/O设备正在产生没有预料到的中断),要么就是出现了一个内核问题(一个中断或异常未被恰当地处理)。
最后,我们来看setup_idt()函数如何对IDT表进行填充:
/*
* setup_idt
*
* sets up a idt with 256 entries pointing to
* ignore_int, interrupt gates. It doesn't actually load
* idt - that can be done only after paging has been enabled
* and the kernel moved to PAGE_OFFSET. Interrupts
* are enabled elsewhere, when we can be relatively
* sure everything is ok.
*/
setup_idt:
lea ignore_int,%edx /*计算ignore_int地址的偏移量,并将其装入%edx*/
movl $(__KERNEL_CS << 16),%eax /* selector = 0x0010 = cs */
movw %dx,%ax
movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
lea SYMBOL_NAME(idt_table),%edi
mov $256,%ecx
rp_sidt:
movl %eax,(%edi)
movl %edx,4(%edi)
addl $8,%edi
dec %ecx
jne rp_sidt
ret
这段程序的理解要对照门描述符的格式。8个字节的门描述符放在两个32位寄存器eax和edx,如图3.5所示,从rp_sidt开始的那段程序是循环填充256个表项。
32
16 15 0
eax
图3.4 门描述符存放在两个32位的寄存器