摘要
??Linux内核在初始化阶段完成了对页式虚拟管理的初始化以后,便调用trap_init()和init_IRQ()两个函数进行中断机制的初始化。其中trap_init()主要是对一些系统保留的中断向量的初始化,而init_IRQ()则主要是用于外设的中断。
三、Linux下中断管理的实现
??1.中断向量表IDT的初始化
??Linux内核在初始化阶段完成了对页式虚拟管理的初始化以后,便调用trap_init()和init_IRQ()两个函数进行中断机制的初始化。其中trap_init()主要是对一些系统保留的中断向量的初始化,而init_IRQ()则主要是用于外设的中断。
void _init trap_init(void)
{
#ifdef CONFIG_EISA
if (isa_readl(0x0FFFD9) ==
'E+('I'
EISA_bus = 1;
#endif
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_trap_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);
/*
* default LDT is a single_entry callgate to lcall7 for iBCS
* and a callgate to lcall27 for Solaris/x86 binaries
*/
set_call_gate(&default_ldt[0],lcall7);
set_call_gate(&default_ldt[4],lcall27);
/*
_set_gate(a,12,3,addr);
//12 即1100b,类型码为100,即调用门
}
??这些函数都调用同一个子程序_set_gate(),第一个参数用以设置中断描述符表idt_table中的第n项,第二个参数对应于门格式中的D位加类型位段,第三个参数是对应的DPL位段。
#define _set_gate(gate_addr,type,dpl,addr)
do {
int _d0,_d1;
_asm_ _volatile_("movw %%dx,%%ax
")
"movw %4,%%dx
"
"mov1 %%eax,%0
"
"mov1 %%edx,%1"
:"=m" (*((long * ) (gate_addr))),
"=m" (*(1+(long *)(gate_addr))),"=&a" (_d0),"=&d" (_d1)
:"i" ((short) (0x8000+(dpl
"3" ((char *) (addr)),"2" (_KERNEL_CS
}while(0)
??在第一个“:”到第二个“:”之间为输出部,有四个约束输出,将有四个变量会被改变,分别为%0、%1、%2和%3相结合。其中%0和%1都是内存单元,分别和gate_addr、gate_addr+1结合,%2于局部变量_d0结合,存放在寄存器%%eax中;%3于局部变量_d1结合,存放在寄存器%%edx中。
??第二个“:”之后的部分是输入部,输出部已经定义了%0-%3,输入部中的第一个变量为%4,而紧接着的第二、第三个变量分别等价于输出部的%3和%2。输入部中说明的个输入变量地值,包括%3和%2,都会在引用这些变量之前设置好。在执行指令部的代码之前,会先将%%eax设成(_KERNEL_CS
有表格
??指令部第一条指令“movw %%dx,%%ax”,将%%dx的低16位移入%%ax的低16位,这样,在%%eax中,其高16位为_KERNEL_CS,而低16位为addr的低16位,形成了门结构中的第0-31位。
??第二条指令“movx %4 ,%%dx”,将%4放入%%dx中,也就是将门结构中的第32-47位放在%%dx中,而对于%%edx而言,就对应着门结构中的高32位。
??第三条指令“mov1 %%eax,%0”,将%%edx写入变量%0中,即*gate_addr。
??第四条指令“mov1 %%eax,%1”将%%edx写入变量%1中,即*(gate_addr+1)。
??将第三、第四条指令和起来看,就是将整个门结构的内容都写道*gate_addr中去了。
void _inti init_IRQ(void)
{
int i;
#ifndef CONFIG_X86_VISWS-APIC
init_ISA_irqs();
#else
init_VISWS_APIC_irqs();
#endif
/*
* Cover the whole vector space,no vector can escape
* us. (some of these will be overridden and become
* 'special' SMP interrupts)
*/
for (i=0;i
int vector = FIRST_EXTERNAL_VECTOR + i;
if (vector != SYSCALL_VECTOR)
set_intr_gate(vector,interrupt[i]);
}
#ifdef CONFIG_SMP
/*
* IRQ0 must ve given a fixed assignment and initialized,
* because it's used before the I0-APIC is set up.
*/
set_intr-gate(FIRST_DEVICE_VECTOR,interrupt[0]);
/*
* The reschedule interrupt is a CPU-to-CPU reschedule-helper
* IPI,driven by wakeup.
*/
set_intr_gate(RESCHEDULE_VECTOR,reschedule_interrupt);
/* IPI for generic function call */
set_intr_gate(CALL_FUCTION_VECTOR,call_funtion_interrupt);
#endif
#ifdef CONFIG_X86_LOCAL_APIC
/* IPI vectors for APIC spurious and error interrupts */
set_intr_gate(SPURIOUS_APIC_VECTOR,spurious_interrupt);
set_intr_gate(ERROR_APIC_VECTOR,error_interrupt);
#endif
/*
* Set the clock to HZ Hz, we already have a valid
* vector now;
*/
outb_p(0x34,0x43); /* binary, mode 2, LSB/MSB ,ch 0*/
outb_p(LATCH & 0xff,0x40); /* LSB */
outb(LATCH 8, 0x40); /* MSB */
#ifndef CONFIG_VISWS
setup_irq(2, &irq2)
#endif
/*
* External FPU? Set up irq13 if so,for
* original braindamaged IBM FERR coupling.
*/
if (boot_cpu_data.hard_math && !cpu_has_fpu)
setup_irq(13,&irq13);
??i386体系支持256各中断向量,扣除为cpu保留的向量,Linux作为通用操作系统,很难说剩下的中断向量是否够用。所以,在Linux系统中,为每个中断向量设置一个队列,而根据每个中断源所使用的中断向量,将其中断服务程序挂到相应的队列中,而数组irq_desc[]中的每个元素则是这样一个队列头部以及控制结构。当中断发生时,首先执行中断向量相对应的一段总服务程序,根据具体中断源的设备号在其所属队列中找到特定的服务程序加以执行。
??首先对PC的中断控制器8259A的初始化,并初始化了数组iirq_desc[]。接着从FIRST_EXTERNAL_VECTOR开始,设立NR_IRQS个中断向量的 IDT表项。常数FIRST_EXTERNAL_VECTOR定义为 0x20,而NR_IRQS则为224。其中还跳过了用于系统调用的向量0x80。
??忽略我们不关心的多处理器SMP结构和SG1工作站的特殊处理,剩下的就是对系统时钟的初始化。在PC中,定时器/计数器芯片8254共有三个通道,通道0是一个产生实时时钟信号的系统计时器,而程序中要设置的也就是通道0。用于控制8254的端口共有四个,前三个分别对应于单个通道的端口,最后一个通道对应于8254的控制字寄存器端口。
outb_p(0x34,0x43);//设置通道的工作方式
//选通通道0,先读写高字节,后读写低字节,工作于方式2,二进制数
outb_p(LATCH&0xff,0x40);
//写入低字节
outb(LATCH8,0x40);
//写入高字节
//设置通道0的记数值
??到此,已经设置好了IDT,也有了一个中断向量,0号中断时钟中断。但是,虽然该中断服务的入口地址已经设置到中断向量表中,但我们还没有把0号中断具体的中断服务程序挂到0号中断的队列中去。这时,这些中断地队列都是空的,因此,即使开了中断,并产生了时钟中断,也只不过是让它在中断处理的总服务程序中空跑一趟。
??设置好了中断向量表,中断队列都还是空的。想要中断程序生效,下一步就要初始化中断请求队列,并把具体的中断服务程序挂入中断队列中去。
??2、中断请求队列的初始化
??通用中断门是让多个中断源共用的,而且允许这种共用的结构在系统运行的过程中动态的改变,所以在IDT地初始化阶段只是为每个中断向量准备一个中断请求队列,从而