1. 从硬件故障中断处理理解
要理解中断系统从理解一段代码开始
no_error_code:
xchgl %eax,(%esp) #将eax的内容和esp所指堆栈的内容相交换
pushl %ebx
pushl %ecx
pushl %edx
pushl %edi
pushl %esi
pushl %ebp
push %ds
push %es
push %fs
pushl $0 # 第二个参数
lea 44(%esp),%edx
pushl %edx #第一个参数
movl $0x10,%edx #转到内核态
mov %dx,%ds
mov %dx,%es
mov %dx,%fs
call *%eax #调用中断处理函数
addl $8,%esp
pop %fs
pop %es
pop %ds
popl %ebp
popl %esi
popl %edi
popl %edx
popl %ecx
popl %ebx
popl %eax
iret #返回被中断程序
以上内容是linux/kernel/asm.s中实现中断调用最核心的代码
完整的中断调用程序如下
_divide_error:
pushl $_do_divide_error
jmp no_error_code
_divide_error是代表在内核中的一个地址,在汇编中代表定义了标号,在c语言中代表void divide_error(void);函数
然后我们再来看中断的初始化操作linux/kernel/traps.c中的trap_init(void)函数的实现
void trap_init(void)
{
int i;
set_trap_gate(0,÷_error);
...
}
set_trap_gate(0,÷_error)就是将中断向量表中第1项的中断处理程序设置为divide_error,也就是asm.s中定义的_divide_error标号,而在_divide_error中push $_do_divide_error变量是一个C函数,这个函数在traps.c中定义
void do_divide_error(long esp, long error_code)
{
die("divide error",esp,error_code);
}
以上设置中断处理函数要特别注意中断向量号,上面为0,是什么意思?
这些就是Intel保留的中断向量号的定义,也就是从CPU发出的俗成约定,这个地方我理解了很久:!
中断号 | 名称 | 类型 | 信号 | 说明 |
---|---|---|---|---|
0 | Devide error | 故障 | SIGFPE | 当进行除以零的操作时产生 |
1 | Debug | 陷阱故障 | SIGTRAP | |
2 | nmi | 硬件 | 由不可屏蔽中断NMI产生 | |
3 | Breakpoint | 陷阱 | SIGTRAP | 由断点指令int3产生,与debug处理相同 |
4 | Overflow | 陷阱 | SIGSEGV | eflags的溢出标志OF 引起 |
5 | Bounds check | 故障 | SIGSEGV | 寻址到有效地址意外时引起 |
6 | Invalid Opcode | 故障 | SIGILL | CPU执行时发现一个无效的指令操作码 |
7 | Device not available | 故障 | SIGSEGV | |
8 | Double fault | 异常中止 | SIGSEGV | 双故障出错 |
9 | Coprocessor segment overrun | 异常中止 | SIGFPE | 协处理器段超出 |
10 | Invalid TSS | 故障 | SIGSEGV | CPU切换时发觉TSS无效 |
11 | Segment not present | 故障 | SIGBUS | 描述符所指的段不存在 |
12 | Stack segment | 故障 | SIGBUS | 堆栈段不存在或者寻址越出堆栈段 |
13 | General protection | 故障 | SIGSEGV | 没有符合80386保护机制的操作引起 |
14 | Page fault | 故障 | SIGSEGV | 页不再内存 |
15 | Reserved | |||
16 | Coprocessor error | 故障 | SIGPE | 协处理器发出的出错信号引起 |
以上内容就是大部分硬件故障的中断处理函数的处理过程,剩下的部分就是对8259A中断控制器的中断响应处理和系统调用(俗称软中断)
2. 8259A中断处理
理解8259A的中断处理过程,从理解一段代码开始:
mov al,#0x11 #(ICW1设置)中断请求边沿触发多片8259级联并最需发送ICW4
out #0x20,al ! send it to 8259A-1
.word 0x00eb,0x00eb #0x00eb跳转到下一句的机器码
out #0xA0,al ! and to 8259A-2
.word 0x00eb,0x00eb
mov al,#0x20 #(ICW2设置)主片中断号范围从0x20开始
out #0x21,al
.word 0x00eb,0x00eb
mov al,#0x28 #从片中断号范围从0x28开始
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0x04 #(ICW3设置)设置主芯片
out #0x21,al
.word 0x00eb,0x00eb
mov al,#0x02 #设置从芯片
out #0xA1,al
.word 0x00eb,0x00eb
mov al,#0x01 #(ICW4设置):普通EOI,非缓冲切需发送指令来复位的模式
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
#8259A中断控制器初始化结束
.word 0x00eb,0x00eb
mov al,#0xFF #屏蔽所有中断请求
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al
读取以上代码有困难的话,需要复习一下8259A中断控制器的相关知识,这是微机接口原理的主要内容,不过阅读上面代码的重点是告诉我们8259的中断向量是从0x20开始的,要记住这一点,不然时钟中断,硬盘中断等的中断向量号是怎么来的,你就不从知晓,可以参考一下列表:
现在来看具体的中断处理向量的设置,它们分散在不同的地方
1. 时钟中断向量设置
timer_interrupt这就是操作系统的心跳函数
linux/kernel/sched.c
void sched_init(void)
{
...
set_intr_gate(0x20,&timer_interrupt);
...
}
2. 硬盘中断向量设置
linux/kernel/blk_drv/hd.c
void hd_init(void)
{
...
set_intr_gate(0x2E,&hd_interrupt);
...
}
3. 键盘中断向量设置
linux/kernel/chr_drv/console.c
void con_init(void)
{
...
set_trap_gate(0x21,&keyboard_interrupt);
...
}
想象一下,如果没有中断,那操作系统不是会一直要去查询各种设备的状态而忙死么