中斷分析(五)
——初始化中斷描述符表的具體實作
中斷描述符表的初始化:将中斷描述符表的起始位址裝入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,÷_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)