中断在linux驱动中占有很重要的地位,所以也一定要好好分析一下。还是一样的套路,先讲一下基本的概念,然后分析源码再来加深理解。
中断门与其他三个门(调用门、陷阱门、任务门)一起是操作系统里的四扇门。它们之间的区别以后再补充,今天重点分析中断门。
那么什么是门呢?门其实就是一中描述符,直观来看这个描述符描述了由一个选择子和一个偏移所指定的线性地址,程序正是通过这个地址进行转移的。中断描述符放在中断描述符表里(IDT),也就是这个IDT将中断向量和中断服务程序联系起来,他们之间的关系如下图:
那中断向量怎么来得呢,其实每一种中断(异常)都会对应一个中断向量号,具体有哪些中断向量,可以看下面这张图:
向量号 | 助记符 | 描述 | 类型 | 出错码 | 源 |
0 | #DE | 除法错 | Fault | 无 | DIV和IDIV指令 |
1 | #DB | 调试异常 | Fault/Trap | 无 | 任何代码和数据的访问 |
2 | — | 非屏蔽中断 | Interrupt | 无 | 非屏蔽外部中断 |
3 | #BP | 调试断点 | Trap | 无 | 指令INT 3 |
4 | #OF | 溢出 | Trap | 无 | 指令INTO |
5 | #BR | 越界 | Fault | 无 | 指令BOUND |
6 | #UD | 无效(未定义)操作码 | Fault | 无 | 指令UD2或无效指令 |
7 | #NM | 设备不可用(无数学协处理器) | Fault | 无 | 浮点或WAIT/FWAIT指令 |
8 | #DF | 双重错误 | Abort | 有(0) | 所有能产生异常或NMI或INTR 的指令 |
9 | 协处理器段越界(保留) | Fault | 无 | 浮点指令(386后不再处理此 异常) |
|
10 | #TS | 无效TSS | Fault | 有 | 任务切换或访问TSS时 |
11 | #NP | 段不存在 | Fault | 有 | 加载段寄存器或访问系统段时 |
12 | #SS | 堆栈段错误 | Fault | 有 | 堆栈操作或加载SS时 |
13 | #GP | 常规保护错误 | Fault | 有 | 内存或其他保护检验 |
14 | #PF | 页错误 | Fault | 有 | 内存访问 |
15 | — | Intel保留,未使用 | |||
16 | #MF | x87FPU浮点错(数学错) | Fault | 无 | x87FPU浮点指令或WAIT/FWAIT指令 |
17 | #AC | 对齐检验 | Fault | 有(0) | 内存中的数据访问(486开始支持) |
18 | #MC | Machine Check | Abort | 无 | 错误码(若有的话)和源依赖于 具体模式(奔腾CPU开始支持) |
19 | #XF | SIMD浮点异常 | Fault | 无 | SSE和SSE2浮点指令(奔腾三 开始支持) |
20~31 | — | Inter保留,未使用 | |||
32~255 | — | 用户定义中断 | Interrupt | 外部中断或int n指令 |
上图中除了两个Interrupt(中断)外,其他还有三种Fault、Trap、Abort异常。我们这里讨论的中断主要是用户定义中断,这种中断产生的原因有两种:一是外部中断,就是由硬件产生的中断;另一种是由指令int n产生的中断。
通过指令int n产生中断的情形如第一张图所示,这有点像调用门的适用。
外部中断的情况则复杂一些,因为需要建立硬件中断和向量号之间的对应关系。外部中断的简单示意图如下:
通过对8259A的配置,可将IRQ0~IRQ7对应到中断向量20h~27h,同样地IRQ8~IRQ15可对应到中断向量28h~2Fh。具体初始化配置代码就不进行分析了。
; IDT [SECTION .idt] ;sect.idt #show#--> ALIGN 32 [BITS 32] LABEL_IDT: ; 门 目标选择子, 偏移, DCount, 属性 %rep 32 Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate %endrep .020h: Gate SelectorCode32, ClockHandler, 0, DA_386IGate %rep 95 Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate %endrep .080h: Gate SelectorCode32, UserIntHandler, 0, DA_386IGate IdtLen equ $ - LABEL_IDT IdtPtr dw IdtLen - 1 ; 段界限 dd 0 ; 基地址 ; END of [SECTION .idt] [SECTION .s16] [BITS 16] LABEL_BEGIN: ; 为加载 IDTR 作准备 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_IDT ; eax <- idt 基地址 mov dword [IdtPtr + 2], eax ; [IdtPtr + 2] <- idt 基地址 ; 保存 IDTR sidt [_SavedIDTR] ; 保存中断屏蔽寄存器(IMREG)值 in al, 21h mov [_SavedIMREG], al ; 加载 GDTR lgdt [GdtPtr] ; 关中断 ;cli ; 加载 IDTR lidt [IdtPtr] ; 打开地址线A20 in al, 92h or al, 00000010b out 92h, al ; 准备切换到保护模式 mov eax, cr0 or eax, 1 mov cr0, eax ; 真正进入保护模式 jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处 ; Init8259A --------------------------------------------------------------------------------------------- Init8259A: mov al, 011h out 020h, al ; 主8259, ICW1. call io_delay out 0A0h, al ; 从8259, ICW1. call io_delay mov al, 020h ; IRQ0 对应中断向量 0x20 out 021h, al ; 主8259, ICW2. call io_delay mov al, 028h ; IRQ8 对应中断向量 0x28 out 0A1h, al ; 从8259, ICW2. call io_delay mov al, 004h ; IR2 对应从8259 out 021h, al ; 主8259, ICW3. call io_delay mov al, 002h ; 对应主8259的 IR2 out 0A1h, al ; 从8259, ICW3. call io_delay mov al, 001h out 021h, al ; 主8259, ICW4. call io_delay out 0A1h, al ; 从8259, ICW4. call io_delay ;mov al, 11111111b ; 屏蔽主8259所有中断 mov al, 11111110b ; 仅仅开启定时器中断 out 021h, al ; 主8259, OCW1. call io_delay mov al, 11111111b ; 屏蔽从8259所有中断 out 0A1h, al ; 从8259, OCW1. call io_delay ret ; Init8259A --------------------------------------------------------------------------------------------- LABEL_SEG_CODE32: mov ax, SelectorData mov ds, ax ; 数据段选择子 mov es, ax mov ax, SelectorVideo mov gs, ax ; 视频段选择子 mov ax, SelectorStack mov ss, ax ; 堆栈段选择子 mov esp, TopOfStack call Init8259A int 080h sti jmp $ ; int handler --------------------------------------------------------------- _UserIntHandler: UserIntHandler equ _UserIntHandler - $$ mov ah, 0Ch ; 0000: 黑底 1100: 红字 mov al, 'I' mov [gs:((80 * 0 + 70) * 2)], ax ; 屏幕第 0 行, 第 70 列。 iretd