中断分析(五)
——初始化中断描述符表的具体实现
中断描述符表的初始化:将中断描述符表的起始地
址装入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 /* selector = 0x0010 = cs
*/ #ignore_int的有效地址存入eax的底16位。此时,eax中含有门描述符底4字节(32位)的值。
movw
$0x8E00,%dx /* interrupt gate - dpl=0, present */
#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); /*
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);
}
我们可以看到
将异常处理函数插入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)