在对中断描述符表进行预初始化后, 内核将在启用分页功能后对IDT进行第二遍初始化,也就是说,用实际的陷阱和中断处理程序替换这个空的处理程序。一旦这个过程完成,对于每个异常,IDT都由一个专门的陷阱门或系统门,而对每个外部中断,IDT都包含专门的中断门。
1. IDT表项的设置
IDT表项的设置是通过_set_gaet()函数实现的,这与IDT表的预初始化比较相似,但这里使用的是嵌入式汇编,因此,理解起来比较困难。在此,我们给出函数源码(在traps.c中)及其解释:
#define _set_gate(gate_addr,type,dpl,addr)
\
do { \
int __d0, __d1; \
__asm__ __volatile__ ("movw %%dx,%%ax\n\t"
\
"movw %4,%%dx\n\t" \
"movl %%eax,%0\n\t" \
"movl %%edx,%1" \
:"=m" (*((long *) (gate_addr))), \
"=m" (*(1+(long *) (gate_addr))),
"=&a" (__d0), "=&d" (__d1) \
:"i" ((short) (0x8000+(dpl<<13)+(type<<8))),
\
"3" ((char *) (addr)),"2" (__KERNEL_CS <<
16)); \
} while (0)
这是一个带参数的宏定义,其中,gate_addr 是门的地址,type为门类型,dpl为请求特权级,addr为中断处理程序的地址。对这段嵌入式汇编代码的说明如下:
· 输出部分有4个变量,分别与%1、%2、%3及%4相结合,其中,%0与gate_addr结合,1%与(gate_aggr+1)结合,这两个变量都存放在内存;2%与局部变量__d0结合,存放在eax寄存器中;3%与__d1结合,存放在edx寄存器中。
· 输入部分有3个变量。由于输出部分已定义了0%~3%,因此,输入部分的第一个变量为4%,其值为“0x8000+(dpl<<13)+(type<<
· 有了参数的这种对照关系,再参考前面的set_idt()函数,就不难理解那4条mov语句了。
下面我们来看如何调用_set_get()函数来给IDT插入门:
void
set_intr_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,14,0,addr);
}
在第 n个表项中插入一个中断门。这个门的段选择符设置成代码段的选择符(__KERNEL_CS),DPL域设置成0,14表示D标志位为1而类型码为110,所以set_intr_gate()设置的是中断门,偏移域设置成中断处理程序的地址addr。
static void __init
set_trap_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,15,0,addr);
}
在第 n个表项中插入一个陷阱门。这个门的段选择符设置成代码段的选择符,DPL域设置成0,15表示D标志位为1而类型码为111,所以set_trap_gate()设置的是陷阱门,偏移域设置成异常处理程序的地址addr。
static void __init
set_system_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,15,3,addr);
}
在第 n个表项中插入一个系统门。这个门的段选择符设置成代码段的选择符,DPL域设置成3,15表示D标志位为1而类型码为111,所以set_system_gate()设置的也是陷阱门,但因为DPL为3,因此,系统调用在用户空间可以通过“INT0X
2.对陷阱门和系统门的初始化
trap_init()函数就是设置中断描述符表开头的19个陷阱门,如前所说,这些中断向量都是CPU保留用于异常处理的:
set_trap_gate(0,÷_error);
set_trap_gate(1,&debug);
set_intr_gate(2,&nmi);
set_system_gate(3,&int3); /* int3-5
can be called from all */
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_trap_gate(8,&double_fault);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_intr_gate(14,&page_fault);
set_trap_gate(15,&spurious_interrupt_bug);
set_trap_gate(16,&coprocessor_error);
set_trap_gate(17,&alignment_check);
set_trap_gate(18,&machine_check);
set_trap_gate(19,&simd_coprocessor_error);
set_system_gate(SYSCALL_VECTOR,&system_call);
在对陷阱门及系统门设置以后,我们来看一下中断门的设置。
3.中断门的设置
下面介绍的相关代码均在arch/I386/kernel/i8259.c文件中,其中中断门的设置是由init_IRQ( )函数中的一段代码完成的:
for (i = 0; i<
NR_IRQS; i++) {
int vector = FIRST_EXTERNAL_VECTOR + i;
if (vector != SYSCALL_VECTOR)
set_intr_gate(vector, interrupt[i]);
其含义比较明显:从FIRST_EXTERNAL_VECTOR开始,设置NR_IRQS个IDT表项。常数FIRST_EXTERNAL_VECTOR定义为0x20,而NR_IRQS则为224,即中断门的个数。注意,必须跳过用于系统调用的向量0x80,因为这在前面已经设置好了。
这里,中断处理程序的入口地址是一个数组interrupt[],数组中的每个元素是指向中断处理函数的指针。我们来看一下编码的作者如何巧妙地避开了繁琐的文字录入,而采用统一的方式处理多个函数名。
#define IRQ(x,y) \
IRQ##x##y##_interrupt
#define IRQLIST_16(x) \
IRQ(x,0), IRQ(x,1), IRQ(x,2), IRQ(x,3), \
IRQ(x,4), IRQ(x,5), IRQ(x,6), IRQ(x,7), \
IRQ(x,8),
IRQ(x,9), IRQ(x,a), IRQ(x,b), \
IRQ(x,c),
IRQ(x,d), IRQ(x,e), IRQ(x,f)
void
(*interrupt[NR_IRQS])(void) = IRQLIST_16(0x0)
其中,“##”的作用是把字符串连接在一起。经过gcc预处理,IRQLIST_16(0x0)被替换为IRQ0x00_interrupt,IRQ0x01_interrupt,IRQ0x02_interrupt…IRQ0x
到此为止,我们已经介绍了15个陷阱门、4个系统门和16个中断门的设置。内核代码中还有对其它中断门的设置,在此就不一一介绍。