天天看點

中斷分析(五)

中斷分析(五)

    ——初始化中斷描述符表的具體實作    

中斷描述符表的初始化:将中斷描述符表的起始位址裝入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)

繼續閱讀