0. 中断那些事儿
中断分类
- 外部中断
1.1 可屏蔽中断(INTR)
1.2 不可屏蔽中断(NMI) - 内部中断
2.1 软中断
2.2 异常
一共0~255,256个中断。
这个0~255就是中断向量号。处理器就是根据中断向量号来定位中断处理程序的。
操作系统是中断驱动的,在实模式下有中断向量表(IVT),中断发生后找到中断处理程序的入口;在实模式下有中断描述符表(IDT),中断发生后根据中断描述符来找到中断处理程序。
中断描述符表 IDT 中不只有中断描述符,还有任务门描述符、陷阱门描述符。
为什么称为“门”?
因为是通往某段程序的大门,中断描述符表中的描述符叫做——门。
中断处理过程
- 处理器根据中断向量号来定位到中断描述符。
- 进行特权级检查
2.1若是软中断 int n、int3、into 引发的中断,先检查CPL权限大于等于门描述符DPL,即CPL <= 门描述符DPL。(门槛检查)
2.2 CPL权限小于目标代码段的DPL,即CPL > 目标代码段的DPL(门框检查)。
2.3 若是外部中断或者异常,则只检查2.2。 - 执行中断处理程序。
- 中断返回。返回时也要进行特权级检查。
中断发生时的压栈
- 比较CPL和目标代码的DPL,若CPL > DPL,则表示要向高特权级转移。
1.1 保存旧栈SS_old、ESP_old
1.2 找到目标级别的栈,加载到SS、ESP.
1.3 将SS_old、ESP_old压入新栈 - 新栈中压入EFLAGS寄存器
- 将CS、EIP 保存到当前栈,(中断返回时用)
- 将ERROR_CODE记入栈
- 若在第 1 步判断未发生特权级转移,则用旧栈来保存EFLAGS、CS、EIP、ERROR_CODE。
下图为中断处理过程。
我们要处理中断,要做的就是:
- 建立中断处理函数
- 建立中断描述符表 IDT
- 打开中断
- 等待中断发生
问题来了,中断向量号是怎么给处理器的呢?
我们测试的是用 INTR 引脚给 处理器发送外部可屏蔽中断。
8259A 可编程中断控制器
它是可屏蔽中断的总代理,用它来统一管理可屏蔽中断,
各种可屏蔽中断得经过它才能达到CPU。
8259A 的编程就是两步,初始化、控制。
写 ICW1 ~ ICW4 寄存器和 OCW1 ~ OCW3 寄存器。
具体过程,各个寄存器代表的含义,不多说。
8259A 已经和CPU在硬件上连接好了,就像前面图所示。
我们只需对它进行编程就可以了。
1 总步骤
任务:我们要测试一个时钟中断,就是连接在 8259A 主片上的 IR0 的中断。
步骤:
- 写好中断处理程序。(kernel.S)
- 建立中断描述符表 IDT。(my_interrupt.c)
- 加载 IDT。(my_interrupt.c)
- 设置 8259A。 (8259A.S)
- 打开中断。(kernel.S)
- 观察是否执行中断处理程序的打印字符。
2 写中断处理函数
kernel.S
kernel.S先是实现了33个中断处理程序的入口地址,最后的一个函数是以后在my_interrupt.c中调用的,要加载 idt 到 IDTR寄存器的。
[bits 32]
%define ERROR_CODE nop ; 若在相关的异常中cpu已经自动压入了错误码,为保持栈中格式统一,这里不做操作.
%define ZERO push 0 ; 若在相关的异常中cpu没有压入错误码,为了统一栈中格式,就手工压入一个0
extern idt_table ;idt_table是C中注册的中断处理程序数组
section .data
idt_ptr dw 0
dd 0 ;为以后的加载 idt 存放48位立即数
global intr_entry_table ;在 C 中引用intr_entry_table数组,intr_entry_table里存的是0x21个中断处理程序的入口地址
intr_entry_table:
%macro VECTOR 2
section .text
intr%1entry: ; 每个中断处理程序都要压入中断向量号,所以一个中断类型一个中断处理程序,自己知道自己的中断向量号是多少
%2 ; 中断若有错误码会压在eip后面
; 以下是保存上下文环境
push ds
push es
push fs
push gs
pushad ; PUSHAD指令压入32位寄存器,其入栈顺序是: EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI
; 如果是从片上进入的中断,除了往从片上发送EOI外,还要往主片上发送EOI
mov al,0x20 ; 中断结束命令EOI
out 0xa0,al ; 向从片发送
out 0x20,al ; 向主片发送
push %1 ; 不管idt_table中的目标程序是否需要参数,都一律压入中断向量号,调试时很方便
call [idt_table + %1*4] ; 调用idt_table中的C版本中断处理函数
jmp intr_exit
section .data
dd intr%1entry ; 存储各个中断入口程序的地址,形成intr_entry_table数组
%endmacro
section .text
global intr_exit
intr_exit:
; 以下是恢复上下文环境
add esp, 4 ; 跳过中断号
popad
pop gs
pop fs
pop es
pop ds
add esp, 4 ; 跳过error_code
iretd
VECTOR 0x00,ZERO
VECTOR 0x01,ZERO
VECTOR 0x02,ZERO
VECTOR 0x03,ZERO
VECTOR 0x04,ZERO
VECTOR 0x05,ZERO
VECTOR 0x06,ZERO
VECTOR 0x07,ZERO
VECTOR 0x08,ERROR_CODE
VECTOR 0x09,ZERO
VECTOR 0x0a,ERROR_CODE
VECTOR 0x0b,ERROR_CODE
VECTOR 0x0c,ZERO
VECTOR 0x0d,ERROR_CODE
VECTOR 0x0e,ERROR_CODE
VECTOR 0x0f,ZERO
VECTOR 0x10,ZERO
VECTOR 0x11,ERROR_CODE
VECTOR 0x12,ZERO
VECTOR 0x13,ZERO
VECTOR 0x14,ZERO
VECTOR 0x15,ZERO
VECTOR 0x16,ZERO
VECTOR 0x17,ZERO
VECTOR 0x18,ERROR_CODE
VECTOR 0x19,ZERO
VECTOR 0x1a,ERROR_CODE
VECTOR 0x1b,ERROR_CODE
VECTOR 0x1c,ZERO
VECTOR 0x1d,ERROR_CODE
VECTOR 0x1e,ERROR_CODE
VECTOR 0x1f,ZERO
VECTOR 0x20,ZERO
section .text
global lodid