Chinaunix首页 | 论坛 | 博客
  • 博客访问: 707172
  • 博文数量: 140
  • 博客积分: 8196
  • 博客等级: 中将
  • 技术积分: 1350
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-16 19:53
文章分类

全部博文(140)

文章存档

2011年(2)

2010年(2)

2009年(2)

2008年(20)

2007年(52)

2006年(62)

分类: LINUX

2007-05-12 11:15:49

IA32架构将中断分成了中断与异常,中断是指外部设备与CPU交互的方法,也就是说由外部条件触发的,具体来说就是两个8259A级联所产生的中断源,而异常则是在CPU执行指令的过程中产生的,比如当一条执行发生了除0,那么就产生一个除0的异常。具体的异常还分成Fault,Trap,Abort 等,当然,在中断和异常之间也存在着优先级,这里不多讲了。

中断的处理是需要通过软件来实现的。内存中存在着一个中断向量表,该表的每个表项存储的都是具体的中断处理程序的地址(具体来说是中断描述符,也可以叫做中断向量,不只是地址),当系统产生一个中断时,会根据中断号到中断向量表中查找,找到对应的中断处理程序之后执行之。

以上所有的步骤都是硬件的事,对于操作系统来说,最重要的任务是设置中断向量表,以及编写中断处理程序。在保护模式中,为了能够使用中断向量表,我们需要把表的地址加载到寄存器idtr中去,使用的指令是lidt。在Linux内核的启动过程中首先在setup.S中首先临时的设置了一下中断向量表 (IDT),只是为了跳到保护模式才这么做的,实际上没有具体内容。之后再head.S中真正的设置的系统的IDT。下面贴一些Linux 0.11的代码来说明一下:

head.S:

78 setup_idt:          
79     lea ignore_int, %edx
80     movl $0x00080000, %eax
81     movw %dx, %ax
82     movw $0x8E00, %dx
83
84     lea _idt, %edi
85     mov $256, %ecx
86 rp_sidt:
87     movl %eax, (%edi)
88     movl %edx, 4(%edi)
89     addl $8, %edi
90     jne rp_sidt
91     lidt idt_descr
92     ret

解释一下,从79~82行的作用是构建一个指向ignore_int子程序的中断描述符,该中断描述符具体由edx和eax两个寄存器组成,具体如下:
          edx           |    eax         |
ignore_int高地址(2B)8E00 0008ignore_int低地址
每个描述符是8个字节, 第84行将_idt的地址装入edi。_idt标签在head.S文件的232行定义:
232 _idt: .fill 256, 8, 0
由此可见,_idt是一个具有256*8个字节,内容为0的内存区域的开始地址。
从第86行到最后,是把由eax和edx组成的中断描述符填写到_idt所指向的内存中,一共填写256个。最后装载idtr寄存器。idt_descr也是在head.s中定义,其中包含了_idt。
222: idt_descr:
223:     .word 256*8 - 1
224:     .long _idt
ignore_int在head.S的150处,这个子程序只是简单的输出了一些文字信息,其他什么也不做,因此,到目前为止的IDT表没有什么实际的作用,有实际作用的内容是在main.c中设置的。

在main.c中,void main(void)函数中有一个trap_init()使用来初始化中断向量的,trap_init()在traps.c中定义:
void trap_init(void)
{
    int i;
    set_trap_gate(0, ÷_error);
    set_trap_gate(1, &debug);
    …………
}
这个函数用来设置中断向量表中的前17个int0~int16,也就是Intel定义的异常,其他中断号在以后设置。trap_init使用了set_trap_gate和set_system_gate来具体设置某个中断描述符,这里可以看到,前17个向量被设置成了指向相应处理函数的描述符,这些处理函数(例如divide_error, debug, nmi等)的声明同样是在traps_init中,而定义却是在asm.s中。

asm.s这个文件实现了一些低层的操作,比如中断产生的时候需要进行的各项压栈操作。在asm.s中有很多类似_divide_error这样的标签,这就是在traps.c中具体函数定义的地方,例如debug函数在asm.s中对应的就是_debug标签等等。在_函数名称标签中,asm.s会call一个_do_函数名称的程序,而这个_do_函数名称的程序又是在traps.c中定义,例如:
_nmi:
    pushl $_do_nmi
    jmp no_error_code
这里,以do开头的程序是真正的中断处理程序。

整理一下:
linux对中断的处理:
setup.s生成临时IDT -> head.s中生成新IDT,该IDT中的项全部指向ignore_int -> main.c中调用trap_init来初始化异常,其他中断则是在各个驱动程序中初始化 -> traps.c和asm.s中定义了异常的处理函数
当产生中断时,系统的流程:
硬件中断/软件异常 -> CPU中断当前程序 -> 根据idtr的内容找到IDT -> 根据中断号查找具体的中断向量 -> 进入asm.s或其他文件中执行具体函数 -> 进入traps.c或其他文件中执行do_函数 -> 函数执行结束 -〉回到asm.s等文件中进行然后处理 -> 恢复被中断程序

参考:Linux内核完全注释
阅读(1858) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~