Linux内核在系统初始化要进行大量的初始化工作,其与中断相关的工作有,初始化可编程控制器8259A;将中断描述符表的起始地址装入IDTR寄存器,并初始化表中的每一项,
用户进程可以通过INT指令发出 一个中断请求向量在0~255之间。为了防止用户使用INT指令模拟非法的中断和异常,必须对中断描述符表进行谨慎的初始化。其措施之一就是将中断门或陷阱门中的请求特权级DPL域置为0,如果用户进程确实发出了这样一个中断请求,CPU会检查出其当前特权级CPL(3)与所请求的特权级DPL(0)有冲突,因此产生一个“通用保护”异常。
但是,有时候必须让用户进程能够使用内核所提供的功能(比如系统调用),也就是说从用户态进入内核态,这可以通过把中断门和陷阱门的DPL置为3来达到。
当计算机运行在实模式时,中断描述表被初始化,并由BIOS使用。然而,一旦真正进入Linux内核,中断描述符表 就被移动到内存的另一个区域,并为进入保护模式进行预初始化,把中断描述符表IDT的起始地址装入IDTR。
用setup_idt()函数填充中断描述符表中的256个表项。在对这个表进行填充时,使用了一个空的中断处理程序。因为现在处于初始化阶段,还没有任何中断处理程序,因此,用空的中断处理程序填充每个表项。
在对中断描述符表进行初始化后,内核将在其用分页功能后对IDT进行第二遍初始化,也就是说,用实际的陷阱和中断处理程序替换这个空的处理程序。一旦这个过程完成,对于每个异常,IDT都有一个专门的陷阱门或系统门,而对每个外部中断,IDT都包含专门的中断门。
1.IDT表项的设置
IDT表项的设置是通过_get_gate()函数实现的,下面给出如何调用该函数在IDT表中插入的一个门。
(1)插入一个中断门
void set_intr_gate(unsigned int n, void *addr) {
_set_gate(idt_table+n, 14, 0 , addr);
}
其中,idt_table时中断描述符表IDT在程序中的符号表示,n表示在第n个表项中插入一个中断门。这个们的段选择符设置成代码段的选择符(addr表示偏移域)。DPL设置成0(第三个参数代表权限),14表示D标志位为1(表示32位),而类型码位110(由14决定),所以set_intr_gate()设置的时中断门,偏移域设置成中断处理程序的地址addr。
(2)插入一个陷阱门
static void __init set_trap_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n, 15, 0, addr);
}
在第n个表项中插入一个陷阱门。这个门的断选择符设置成代码段的选择符,DPL设置成0,15表示D标志位为1,而类型码位111,所以set_system_gate()设置的时陷阱门,偏移域设置成异常处理程序的地址addr。
(3)插入一个系统门
static void __init set_system_gate(unsigned int n, void *addr)
{
_set_gate(idt_table+n, 15, 3, addr);
}
在第n个表项中插入一个系统门。这个门的段选择符设置成代码段的选择符,DPL域设置成3, 15表示D标志位为1,而类型码为111,所以set_system_gate()设置的也是陷阱门,但因为DPL为3,因此,系统调用在用户态可以通过“INT 0x80”顺利穿过系统门,从而进入内核态。
3.中断门的设置
中断门的设置是由init_IRQ()函数种的一段代码完成的:
for(i=0; i<(NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++)
{
int vector = FIRST_EXTERNAL_VECTOR + i;
if( i >= NR_IRQS)
break;
if(vector != SYSCALL+VECTOR)
set_intr_gate(vector, interrupt[i]);
}
从FIRST_EXTERNAL开始,设置 NR_IRQS(NR_VECTORS-FIRST_EXTERNAL_VECTOR)个IDT表项。