3.2.3 中断向量表的最终初始化

在对中断描述符表进行预初始化后, 内核将在启用分页功能后对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相结合,其中,%0gate_addr结合,1%与(gate_aggr1)结合,这两个变量都存放在内存;2%与局部变量__d0结合,存放在eax寄存器中;3%与__d1结合,存放在edx寄存器中。

·      输入部分有3个变量。由于输出部分已定义了0%3%,因此,输入部分的第一个变量为4%,其值为“0x8000+(dpl<<13)+(type<<8,而后面两个变量分别等价于输出部分的%3edx)和2(eax),其值分别为“addr”和“__KERNEL_CS << 16

·      有了参数的这种对照关系,再参考前面的set_idt()函数,就不难理解那4mov语句了。

 

下面我们来看如何调用_set_get()函数来给IDT插入门:

 

void set_intr_gate(unsigned int n, void *addr)

{

         _set_gate(idt_table+n,14,0,addr);

}

在第 n表项中插入一个中断门。这个门的段选择符设置成代码段的选择符(__KERNEL_CS)DPL域设置014表示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域设置015表示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域设置315表示D标志位为1而类型码111,所以set_system_gate()设置的也是陷阱门,但因为DPL3,因此,系统调用在用户空间可以通过“INT0X80顺利穿过系统门,从而进入内核空间。

 

2.对陷阱门和系统门的初始化

trap_init()函数就是设置中断描述符表开头的19个陷阱门,如前所说,这些中断向量都是CPU保留用于异常处理的:

 

        set_trap_gate(0,&divide_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_IRQSIDT表项。常数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_interruptIRQ0x01_interruptIRQ0x02_interrupt…IRQ0x0f_interrupt

 

到此为止,我们已经介绍了15个陷阱门、4个系统门和16个中断门的设置。内核代码中还有对其它中断门的设置,在此就不一介绍。