天天看点

中断分析(五)

中断分析(五)

    ——初始化中断描述符表的具体实现    

中断描述符表的初始化:将中断描述符表的起始地址装入IDTR,并初始化表中的每一项。具体过程如下图所示:

一、中断描述符表的预初始化:

用256个指向ignore_int中断门的入口地址填充中断描述符表。它不是真正的初始化idt,等到分页和内核跳转到PAGE_OFFSET处时才真正的进行初始化。确定所有相关的准备都已就绪之后,中断可以在任何地方发生。

setup_idt:

    lea ignore_int,%edx  //取ignore_int的有效地址到edx寄存器

    movl $(__KERNEL_CS << 16),%eax    #把内核代码段选择符左移16位,送到eax寄              存器,此时eax的高16位存放选择符。

    movw %dx,%ax        #ignore_int的有效地址存入eax的底16位。此时,eax中含有门描述符底4字节(32位)的值。

    movw $0x8E00,%dx      #edx中含有门描述符高4字                       节的值。

    lea SYMBOL_NAME(idt_table),%edi //取中断描述符表的地址到edx中

    mov $256,%ecx

rp_sidt:

    movl %eax,(%edi)    #//将通用的中断描述符存入表中(将ignore_int存入edi所指向的地址中)

    movl %edx,4(%edi)    #eax内容放到edi+4所指内存位置处

    addl $8,%edi          #edi指向表中下一项 

    dec %ecx

    jne rp_sidt         #条件跳转,使得idt表有256项

    Ret

Ignore_int()中断处理程序,可以看作是一个空的处理程序,它执行的主要动作有:

1、在栈中保存一些寄存器的内容。

2、调用printk()函数打印“Unknown interrupt”系统消息。

3、恢复栈中寄存器的内容。

4、执行iret指令,恢复被中断的程序。

ignore_int:   

    cld

    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

二、中断描述符表的第二遍初始化

在上述预初始化之后后,内核将在IDT中进行第二遍初始化,用有意义的陷阱和中断处理程序替换空处理程序。第二遍处理过程完成后,针对控制单元产生的每个不同的异常,IDT都有一个专门的陷阱门或系统门;而对于可编程控制器确认的每一个IRQ,IDT都将包含一个专门的中断门。Trap_init()函数的工作就是将一些处理异常的函数插入到IDT的非屏蔽中断及异常表项中。

由于内核中只用到了陷阱门和系统门,因此只考虑对这两个门的初始化。Trap_init()函数用于设置中断描述符表开头的19个陷阱门和Linux系统中唯一用到的一个系统门。这些中断向量都是CPU保留,用于异常处理的。

void __init trap_init(void)

{   

    set_trap_gate(0,&divide_error);

    set_trap_gate(1,&debug);

    set_intr_gate(2,&nmi);

    set_system_gate(3,&int3);   

    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);

        }

我们可以看到将异常处理函数插入IDT的表项是由set_trap_gate()和set_system_gate()函数来完成的。具体实现如下:

设置中断门函数。其中,参数n表示中断号;addr是中断程序偏移地址(相对于内核代码或数le据段来说的)。

void set_intr_gate(unsigned int n, void *addr)

{

    _set_gate(idt_table+n,14,0,addr);//idt_table是中断描述符表的起始地址。Idt_table+n是中断描述符表中中断号n对应项的偏移值。中断描述符的类型是14,特权级是0.

}

设置陷阱门函数。

static void __init set_trap_gate(unsigned int n, void *addr)

{

     _set_gate(idt_table+n,15,0,addr);    //陷阱门描述符的类型是15,特权级是0.

}

设置系统陷阱门函数。与陷阱门不同,系统陷阱门的特权级是3,即系统陷阱门设置的中断处理过程能够被所有进程调用(如单步调试、溢出出错和超出边界出错等)

static void __init set_system_gate(unsigned int n, void *addr)

{

    _set_gate(idt_table+n,15,3,addr);//系统陷阱门描述符的类型是15,特权级是0.

}

上述门函数都是调用_set_gate宏来实现的。该宏的具体实现如下:

   其中,参数的含义为:gate_addr—门描述符地址;type—门描述符类型域值;dpl—门的特权级;addr—偏移地址。

#define _set_gate(gate_addr,type,dpl,addr) \

do { \

  int __d0, __d1; \    #定义两个整型变量_d0,_d1;

  __asm__ __volatile__ ("movw %%dx,%%ax\n\t" \ #将偏移地址低字与选择符组合成描述符低4字节(eax);

    "movw %4,%%dx\n\t" \  #将类型标识字与偏移高字组合成描述符高4字节(edx);

    "movl %%eax,%0\n\t" \ #设置门描述符的低4字节

    "movl %%edx,%1" \    #设置门描述符的高4字节

    :"=m" (*((long *) (gate_addr))), \ #输出寄存器:%0(内存地址)指向描述符地址(偏        移值)

     "=m" (*(1+(long *) (gate_addr))), "=&a" (__d0), "=&d" (__d1) \#%1指向描述符地址强制转化为长整型加1字节后的地址处;%2(eax)输出到_d0;%3(edx)输出到_d1;

    :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \#输入寄存器:%4,dpl和type组合成类型标志字(门描述符中低2字节)

     "3" ((char *) (addr)),"2" (__KERNEL_CS << 16)); \#3表示与上面相同位置上的输出寄存器,(即%3,edx),2同理(eax),在这段代码开始运行时将__KERNEL_CS(内核代码段)左移16位放到eax寄存器中。

} while(0)

继续阅读