1 中断描述符表IDT的初始化
1.1 预初始化
中断描述表寄存器IDTR的初始化,代码在arch/i386/boot/setup.S
lidt idt_48 # load idt with 0,0
…
idt_48:
.word 0 # idt limit = 0
.word 0, 0 # idt base = 0L
把IDT其实地址装入IDTR,代码在arch/i386/kernel/head.S
#define IDT_ENTRIES 256
.globl SYMBOL_NAME(idt)
lidt idt_descr
…
idt_descr:
.word IDT_ENTRIES*8-1 # idt contains 256 entries
SYMBOL_NAME(idt):
.long SYMBOL_NAME(idt_table)
用setup_idt()函数填充idt_table表中的256个表项
先看idt_table定义,在arch/i386/kernel/traps.c
struct desc_struct idt_table[256] __attribute__((__section__(".data.idt"))) = { {0, 0}, };
desc_struct定义为
struct desc_struct {
unsigned long a,b }
对idt_table表进行填充,使用了一个空的中断处理函数ignore_int()。Ignore_int()是一段汇编程序,在head.S中
ignore_int:
cld #方向标志清0,表示串指令自动增长它们的索引寄存器(esi和edi)
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
int_msg:
.asciz "Unknown interrupt/n"
ALIGN
该中断程序模拟一般中断处理程序,执行如下操作
在栈中保存一些寄存器的值
调用printk打印”Unknown interrupt”信息
从栈中恢复寄存器的值
执行iret以恢复被中断的程序
最后,看setup_idt()如何对idt进行填充
/*
* setup_idt
*
* sets up a idt with 256 entries pointing to
* ignore_int, interrupt gates. It doesn't actually load
* idt - that can be done only after paging has been enabled
* and the kernel moved to PAGE_OFFSET. Interrupts
* are enabled elsewhere, when we can be relatively
* sure everything is ok.
*/
setup_idt:
lea ignore_int,%edx /*计算ignore_int地址的偏移量,并将其装入%edx*/
movl $(__KERNEL_CS << 16),%eax /* selector = 0x0010 = cs */
movw %dx,%ax
movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
lea SYMBOL_NAME(idt_table),%edi
mov $256,%ecx
rp_sidt:
movl %eax,(%edi)
movl %edx,4(%edi)
addl $8,%edi
dec %ecx
jne rp_sidt
ret
主要就是循环填充256个表项。
1.2 最终初始化
IDT表项的设置是通过_set_gate()函数实现的。
#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)
插入中断门
void set_intr_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,14,0,addr ,__KERNEL_CS);
}
这个门的段选择符设置成代码段的选择符(__KERNEL_CS),DPL域设置成0,14表示D标志位为1而类型码为110,偏移域设置成中断处理程序的地址addr
插入陷阱门
static void __init set_trap_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,15,0,addr ,__KERNEL_CS);
}
这个门的段选择符设置成代码段的选择符,DPL域设置成0,15表示D标志位为1而类型码为111偏移域设置成异常处理程序的地址addr
,
插入系统门
static void __init set_system_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n,15,3,addr,__KERNEL_CS);
}
这个门的段选择符设置成代码段的选择符,DPL域设置成3,15表示D标志位为1而类型码为111,所以set_system_gate()设置的也是陷阱门,但因为DPL为3,因此,系统调用在用户空间可以通过“INT0X80”顺利穿过系统门,从而进入内核空间
对陷阱门和系统门的初始化
trap_init()就是设置中断描述符表开头的19个陷阱门,这些中断向量都是CPU保留用于异常处理的
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);
对中断门的初始化
对中断门的设置由init_IRQ()中一段代码实现,代码在arch/i386/kernel/i8259.