中断和异常
中断和异常都是程序执行过程中的强制性转移,转移到相应的处理程序。
中断通常因为硬件而发生,用来处理处理器外部的事件, 比如外围设备的请求。
异常通常在处理器执行指令过程中检测到错误时发生,比如除零, 页错误等
什么是中断
在实模式下,使用的是 BIOS 中断。BIOS 中断是由系统提供的使用 BIOS 的一组功能。中断存在中断向量表中。
在保护模式下, BIOS 中断不能使用。原来的中断向量表被 IDT(中断描 述符表) 代替。
IDT
IDT 中的描述符有三种
• 中断门描述符(int 调用)
• 陷阱门描述符(call 调用)
• 任务门描述符(不常用, Linux 根本没用)
IDT 的作用是将每一个中断向量和一个描述符对应起来。
中断产生有两种情况
• 硬件产生的外部中断
• intn
intn 类型调用使用 IDT 寻找中断处理程序,类似调用门的使用
外部中断: 要建立硬件中断和中断向量号之间的对应关系
• 不可屏蔽中断(NMI 引脚接收,于 IF 是否被设置无关,对应中断向 量号为 2)
• 可屏蔽中断(INTR 接收,中断和向量号的关系由可编程中断控制器 8259 A 建立)
什么是异常
异常的三种类型
• Fault:可更正的异常, 处理完返回到原指令
功能
• 根据优先级在同时发生的中断设备中选择应该处理的请求
• 通过对其寄存器的设置来屏蔽或打开相应中断 结构和工作原理
结构:两片级联的 8259A 与 CPU 相连,共挂十五个外设。
工作:
主 8259A 对应的端口地址是 20 h 和 21 h
从 8259A 对应的端口地址是 A0 h 和 A1 h
设置 8259A:通过向相应的端口写入特定的 ICW
ICW 格式
分析
ICW 1 触发模式,选择中断向量种类,选择 8259A 架构
ICW 2 决定对应哪个中断向量
ICW 3 决定了传进哪个外设中断
ICW 4 正常为 01 h
写入 ICW 2 时, IRQ 0~ IRQ 7 对应 20 h ~ 27 h;同理 IRQ 8 ~ IRQ 15 对应 28 h ~ 2 F h
io_delay 函数:延迟函数,等待操作完成
314 ~ 322 屏蔽了所有的外部中断,此时写入的是 OCW
使用 OCW 的两种情况
• 屏蔽或打开外部中断
• 发送 EOI 给 8259A 通知它中断处理结束
屏蔽或打开外部中断,只需往 8259A 写入 OCW 1 即可
OCW 1 被写入中断屏蔽寄存器(IMR)
发送 EOI 给 8259A 通过往 20 h 或 A0 h 写 OCW 2 来实现的
mov al , 20h
out 20h/A0h, al
如何建立IDT,如何实现一个自定义的中断
建立 IDT
IDT 段
/*
255 个描述符完全相同
SpuriousHandler : 在屏幕右上角打印红色"!",然后死循环
*/
// SpuriousHandler 实现
// 加载IDTR
实现一个中断
/*
修改IDT ---> 将80h 中断单独列出来 ---> 新增一个函数来处理中断 UserIntHandler 在末尾iretd 指令返回
*/
新增一个函数
UserIntHandler 实现
3. 时钟中断例程
pmtest9.asm 部分代码展示
时钟中断处理程序
_ClockHandler:
ClockHandler equ _ClockHandler - $$
inc byte [gs:((80 * 0 + 70) * 2)] ; 屏幕第 0 行, 第 70 列。
mov al, 20h
out 20h, al ; 发送 EOI
iretd
打开时钟中断
;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
IDT 表的修改
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]
开中断
call Init8259A
int 080h
sti
jmp $
代码调试调试
在时钟中断处理程序 ClockHandler 中插入 Magic Break:
_ClockHandler:
ClockHandler equ _ClockHandler - $$
xchg bx, bx
inc byte [gs:((80 * 0 + 70) * 2)] ; 屏幕第 0 行, 第 70 列。
mov al, 20h
out 20h, al ; 发送 EOI
iretd
调试截图展示如下:
尽管没有使用 int 指令直接跳转,因为外中断的特性,程序进入了设置好的 02h 中断,将 int 80h 设置的字符进行变化。 实验结果展示如下:
4. 自定义的中断向量
自定义中断功能
int 80h 中断
功能说明:初始化时间字符串"20yy/mm/dd hh:mm:ss"为当前时间, 并输出在屏幕上
int 20h 中断(时间中断)
功能说明:将屏幕上已输出的时间字符串更新为当前时间
部分代码展示
int 80h 中断
LABEL_DATA:
_szTime db "20yy/mm/dd hh:mm:ss", 0
szTime equ _szTime - $$
DataLen equ $ - LABEL_DATA
; END of [SECTION .data1]
...
_UserIntHandler:
UserIntHandler equ _UserIntHandler - $$
xor eax, eax
xor ebx, ebx
xor esi, esi
xor edi, edi
mov esi, szTime ; 源数据偏移
mov al, 9 ; 年
out 70h, al
in al, 71h
mov bl, al ; 十位
shr bl, 4
and al, 0fh ; 个位
mov ah, bl
add ax, 03030h ; 转换为 ascii 码
mov [esi + 2], ah ; 赋值
mov [esi + 3], al
mov al, 8 ; 月
out 70h, al
in al, 71h
mov bl, al ; 十位
shr bl, 4
and al, 0fh ; 个位
mov ah, bl
add ax, 03030h ; 转换为 ascii 码
mov [esi + 5], ah ; 赋值
mov [esi + 6], al
mov al, 7 ; 日
out 70h, al
in al, 71h
mov bl, al ; 十位
shr bl, 4
and al, 0fh ; 个位
mov ah, bl
add ax, 03030h ; 转换为 ascii 码
mov [esi + 8], ah ; 赋值
mov [esi + 9], al
mov al, 4 ; 时
out 70h, al
in al, 71h
mov bl, al ; 十位
shr bl, 4
and al, 0fh ; 个位
mov ah, bl
add ax, 03030h ; 转换为 ascii 码
mov [esi + 11], ah ; 赋值
mov [esi + 12], al
mov al, 2 ; 分
out 70h, al
in al, 71h
mov bl, al ; 十位
shr bl, 4
and al, 0fh ; 个位
mov ah, bl
add ax, 03030h ; 转换为 ascii 码
mov [esi + 14], ah ; 赋值
mov [esi + 15], al
mov al, 0 ; 秒
out 70h, al
in al, 71h
mov bl, al ; 十位
shr bl, 4
and al, 0fh ; 个位
mov ah, bl
add ax, 03030h ; 转换为 ascii 码
mov [esi + 17], ah ; 赋值
mov [esi + 18], al
; 下面显示一个字符串
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov edi, (80 * 8 + 0) * 2 ; 目的数据偏移。屏幕第 8 行, 第 0 列。
cld
.1:
lodsb
test al, al
jz .2
mov [gs:edi], ax
add edi, 2
jmp .1
.2: ; 显示完毕
iretd
int 20h 时间中断
_ClockHandler:
ClockHandler equ _ClockHandler - $$
xor eax, eax
xor ebx, ebx
mov al, 9 ; 年
out 70h, al
in al, 71h
mov bl, al ; 十位
shr bl, 4
and al, 0fh ; 个位
mov ah, bl
add ax, 03030h ; 转换为 ascii 码
mov [gs:((80 * 8 + 2) * 2)], ah ; 更改屏幕显示
mov [gs:((80 * 8 + 3) * 2)], al
mov al, 8 ; 月
out 70h, al
in al, 71h
mov bl, al ; 十位
shr bl, 4
and al, 0fh ; 个位
mov ah, bl
add ax, 03030h ; 转换为 ascii 码
mov [gs:((80 * 8 + 5) * 2)], ah ; 更改屏幕显示
mov [gs:((80 * 8 + 6) * 2)], al
mov al, 7 ; 日
out 70h, al
in al, 71h
mov bl, al ; 十位
shr bl, 4
and al, 0fh ; 个位
mov ah, bl
add ax, 03030h ; 转换为 ascii 码
mov [gs:((80 * 8 + 8) * 2)], ah ; 更改屏幕显示
mov [gs:((80 * 8 + 9) * 2)], al
mov al, 4 ; 时
out 70h, al
in al, 71h
mov bl, al ; 十位
shr bl, 4
and al, 0fh ; 个位
mov ah, bl
add ax, 03030h ; 转换为 ascii 码
mov [gs:((80 * 8 + 11) * 2)], ah; 更改屏幕显示
mov [gs:((80 * 8 + 12) * 2)], al
mov al, 2 ; 分
out 70h, al
in al, 71h
mov bl, al ; 十位
shr bl, 4
and al, 0fh ; 个位
mov ah, bl
add ax, 03030h ; 转换为 ascii 码
mov [gs:((80 * 8 + 14) * 2)], ah; 更改屏幕显示
mov [gs:((80 * 8 + 15) * 2)], al
mov al, 0 ; 秒
out 70h, al
in al, 71h
mov bl, al ; 十位
shr bl, 4
and al, 0fh ; 个位
mov ah, bl
add ax, 03030h ; 转换为 ascii 码
mov [gs:((80 * 8 + 17) * 2)], ah; 更改屏幕显示
mov [gs:((80 * 8 + 18) * 2)], al
mov al, 20h
out 20h, al ; 发送 EOI
iretd
中断调用
; 下面显示一个字符串
push szPMMessage
call DispStr
add esp, 4
; 调用中断向量
int 080h
sti
jmp $
实验结果展示
成功输出当前时间, 且不断进行更新。
=========================================================================
1. 什么是中断,什么是异常?
中断包括外部中断和来自于指令 int n 的中断(n 即向量号),其中外部中断又分为不可 屏蔽中断(NMI 引脚接收,于 IF 是否被设置无关,对应中断向量号为 2)和可屏蔽中断(INTR 接收, 中断和向量号的关系由可编程中断控制器 8259A 建立)。中断是由硬件设备产生的, 从物理上说就是电信号, 它们通过中断控制器(8259A)发送给 CPU,接着 CPU 判断收到的 中断来自于哪个硬件设备(定义在内核中),最后由 CPU 发送给内核, 由内核处理中断。
异常包括 Fault 、Trap 和 Abort,分别对应出错、陷入和编程异常。出错保存的 EIP 指向 触发异常的那条指令,也就是说当从异常返回时, 出错会重新执行那条指令,如缺页异常就 是是一种出错, 所以当缺页异常处理完成之后,还会去尝试重新执行那条触发异常的指令; 陷入保存的 EIP 指向触发异常的那条指令的下一条指令,陷入不会重新执行因为异常的指令; 可编程中断可由编程者用 int 指令来触发,和陷入类似,从此类异常返回时也是返回到触发 异常的下一条指令。
中断和异常的相同点在于: 最后都是由 CPU 发送给内核, 由内核去处理;处理程序的流 程设计上是相似的。两者的不同点在于: 产生源不相同, 异常是由 CPU 产生的, 而中断是由 硬件设备产生的; 中断是异步的,这意味着中断可能随时到来; 而异常是 CPU 产生的, 所以是时钟同步的; 当处理中断时, 处于中断上下文中;处理异常时, 处于进程上下文中。
2. 8259A 的工作原理是怎样的?怎么给这些中断号的处理向量初始化值?
8259A 的结构如下图所示, 它的功能是根据优先级在同时发生的中断设备中选择应该处 理的请求,并通过对其寄存器的设置来屏蔽或打开相应中断。
通过初始化编程向 8259A 写入相应的初始化命令 ICW,可以使芯片处于一个规定的基本 工作方式, 并在此方式下进行工作。8259A 的初始化命令字共有 4 个 ICW1-ICW4,进行初始 化时要求 ICW1-ICW4 按如下图所示的顺序写入。其中, ICW1 触发模式,选择中断向量种 类,选择 8259A 架构;ICW2 决定对应哪个中断向量;ICW3 决定了传进哪个外设中断; ICW4 正常为 01h。
当 8259A 开始工作时,一个外部中断请求信号会首先通过中断请求线 IRQ 传输到 IMR (中断屏蔽寄存器),IMR 根据所设定的 OCW1,决定是将其丢弃还是接受。使用 OCW 的两 种情况,一种是用于屏蔽或打开外部中断,另一种发送 EOI 给 8259A 通知它中断处理结束, 这里使用的是前者。
如果可以接受,则 8259A 将 IRR(中断请求暂存寄存器)中代表此 IRQ 的位置 1,以表 示此 IRQ 有中断请求信号,并同时向 CPU 的 INTR(中断请求)管脚发送一个信号,在等待 CPU 转到中断服务的过程中可能有其余的 IRQ 线送来中断请求,这些请求都会接受 IMR 的 挑选, 如果没有被屏蔽, 那么这些请求也会被放到 IRR 中,也即 IRR 中代表它们的 IRQ 的 相应位会被置 1。当 CPU 执行完一条指令时后, 会检查一下 INTR 管脚是否有信号,如果发 现有信号, 就会转到中断服务, 此时,CPU 会立即向 8259A 芯片的 INTA(中断应答) 管脚 发送一个信号。当芯片收到此信号后,判优部件开始工作,它在 IRR 中,挑选优先级最高的 中断, 将中断请求送到 ISR(中断服务寄存器),也即将 ISR 中代表此 IRQ 的位置位,并将 IRR 中相应位置 0,表明此中断正在接受 CPU 的处理。同时,将它的编号写入中断向量寄存 器 IVR 的低三位(IVR 是由 ICW2 所指定的)。这时,CPU 还会送来第二个 INTA 信号,当 收到此信号后, 芯片将 IVR 中的内容, 也就是此中断的中断号送上通向 CPU 的数据线。
3. 如何建立 IDT,如何实现一个自定义的中断?
1)将 IDT 放入一个单独的段中, 定义了 IDT 所对应的中断向量的偏移。
2)定义偏移定义
上图展现的是时钟中断处理程序。其功能为在时钟中断到来时,将[gs:((80 * 0 + 70)*2]位 置的字符的值增 1。
3)调用中断, 开中断
4. 如何控制时钟中断, 为什么时钟中断的时候没有看到 int 指令?
控制时钟中断的方法就是在 IDT 表中的中断向量号单独写出来,对应一个中断处理的函 数,在函数里定义时钟中断的处理过程, 比如此例中就单独定义了 020h 中断和 080h 中断:
没有看到 int 的原因是我们在 289 行进行了开中断,时钟中断是外部中断, 每次中断都 会通过 IRQ 中断请求线来请求中断, 不需要 int 调用。
5. 简要解释一下 IOPL 的作用与基本机理
(1)IOPL 的作用
保护模式通过 IOPL 和 I/O 许可位图来限制用户进程进行的 I/O 操作。 IOPL 是 I/O 保护机制的 关键之一,位于寄存器 eflags 的第 12 、13 位。指令 in 、ins 、out 、outs 、cli 、sti 只有在 CPL <= IOPL 时才能执行。这些指令被称为 I/O 敏感指令(I/O Sensitive Instructions)。如果低特权 级的指令试图访问这些 I/O 敏感指令将会导致常规保护错误(#GP)。
(2)IOPL 的运行机理
I/O 位图基址是一个以 TSS 的地址为基址的偏移, 指向的便是 I/O 许可位图。之所以叫做位 图,是因为它的每一位表示一个字节的端口地址是否可用。如果某一位为 0,则表示此位对 应的端口号可用,为 1 则不可用。由于每一个任务都可以有单独的 TSS,所以每一个任务可 以有它单独的 I/O 许可位图。
以教材例子演示:
由于 I/O 许可位图开始有 12 字节内容为 0FFh,即有 12×8=96 位被置为 1,所以从端口 00h 到 5Fh 共 96 个端口地址对此任务不可用。同理,接下来的 1 字节只有第 1 位(从 0 开始数) 是 0,表示这一位对应的端口(61h)可用。如果 I/O 位图基址大于或等于 TSS 段界限,就表示没有 I/O 许可位图, 如果 CPLIOPL,则所有 I/O 指令都会引起异常。 I/O 许可位 图的使用使得即便在同一特权级下不同的任务也可以有不同的 I/O 访问权限。