1. 自营销首页
  2. 文档

保护模式下的中断和异常(上) — 硬件原理篇

1. 引言

在我们这个系列的第一篇文章中,我们就已经使用过了 BIOS 10H 中断,用来在屏幕上打印一行字符:
计算机是如何启动的?如何制作自己的操作系统

除了 10H 中断,我们还使用过 BIOS 21H 中断,用来让实地址模式的程序退出,以及使用 15H 中断获取物理内存的信息:
保护模式进阶 — 再回实模式
实战分页机制实现 — 通过实际内存大小动态调整页表个数

在计算机体系中,硬件中断是触发 CPU 与其他硬件设备进行通信的重要方式。
但你有没有发现,这些中断都是在实地址模式下使用的,一旦进入保护模式,我们就再没有使用过中断功能,那么,在保护模式中,我们是否还可以像保护模式中一样,通过 int 指令触发 BIOS 中断呢?答案是不可以,因为我们在进入保护模式前,通过 cli 指令关闭了硬件中断。
但是,在系统运行中,常常会出现需要暂停当前流程,响应突发事件的中断场景,那么,有什么办法让我们能够在软件的层面实现硬件中断的类似效果呢?答案当然是可以的,本文就来详细介绍。

2. 异常和中断机制

2.1. 异常

有时,我们运行了错误的指令,或指令执行时发生了错误,例如去计算除 0 的情况,或者前面提到的程序调用过程中错误特权级的切换等,这类异常情况就是系统中的“异常”。

2.1.1. 异常的分类

处理器预设了一系列异常,他们分为三类:

  1. Fault — 可更正异常,也称为“故障”,这类异常一旦被更正,系统可以继续原来的程序执行下去,因此,在 fault 异常发生时,处理器会首先保存当前运行状态,在异常处理完成后自动加载刚才的状态重新执行并继续
  2. Trap — 也称为“陷阱”,Fault 在发生时,EIP 值指向的是触发异常的指令,也就是说,在异常处理完成后,会重新执行触发异常的指令,而 Trap 发生时,EIP 指向的是触发异常的下一条指令,当异常处理完成后,CPU 会从下一条指令开始继续执行,Trap 的典型场景是调试中断,调试完成继续执行的时候,当然要调度下一条指令,而不是重新回到刚才的断点再调试一次
  3. Abort — “中止”,是严重的异常,比如硬件错误和系统表中包含非法值或不一致的状态等,一旦这类异常发生,程序运行便随之中止

2.1.2. 异常列表

CPU 预设的异常列表

向量号 助记符 类型 描述 来源
#DE 错误 除零错误 DVI和IDIV指令
1 #DB 错误/陷阱 调试异常,用于软件调试 任何代码或数据引用
2   中断 NMI中断 不可屏蔽的外部中断
3 #BP 陷阱 断点 INT 3指令
4 #OF 陷阱 溢出 INTO指令
5 #BR 错误 数组越界 BOUND指令
6 #UD 错误 无效指令(没有定义的指令) UD2指令(奔腾Pro CPU引入此指令)或任何保留的指令
7 #NM 错误 数学协处理器不存在或不可用 浮点或WAIT/FWAIT指令
8 #DF 终止 双重错误(Double Fault) 任何可能产生异常的指令、不可屏蔽中断或可屏蔽中断
9 #MF 错误 向协处理器传送操作数时检测到页错误(Page Fault)或段不存在,486及以后集成了协处理器,本错误就保留不用了 浮点指令
10 #TS 错误 无效TSS 任务切换或访问TSS
11 #NP 错误 段不存在 加载段寄存器或访问系统段
12 #SS 错误 栈段错误 栈操作或加载SS寄存器
13 #GP 错误 通用/一般保护异常,如果一个操作违反了保护模式下的规定,而且该情况不属于其他异常,CPU就是认为是该异常 任何内存引用或保护性检查
14 #PF 错误 页错误 任何内存引用
15   保留    
16 #MF 错误 浮点错误 浮点或WAIT/FWAIT指令
17 #AC 错误 对齐检查 对内存中数据的引用(486CPU引入)
18 #MC 终止 机器检查(Machine Check) 错误代码和来源与型号有关(奔腾CPU引入)
19 #XF 错误 SIMD浮点异常 SIMD浮点指令(奔腾III CPU引入)
20~31   保留    
32~255 用户自定义中断 中断 可屏蔽中断 来自INTR的外部中断或INT n指令

2.2. 中断

正常的程序运行,除了发生异常外,即便是发生跳转,也都是程序主动的行为,但有时,处理器外部的硬件事件,比如外围设备的请求突然到来等都是随机发生的,我们可以预先设定事件发生时执行的程序,但不能预知事件何时到来,这样的场景就是“中断”,也就是上表中标记为“Interrupt”的类型,另一个触发中断的方式是通过 int 指令手动触发,这就是中断产生的两大原因:

  1. 外部硬件中断,他又可以分为以下两种:
    1. NMI — 不可屏蔽中断,向量号为 2
    2. INTR — 可屏蔽中断2. int n 指令触发中断

3. 可屏蔽中断的响应 — 可编程中断控制器 8259A

不可屏蔽中断和可屏蔽中断分别是通过 CPU 的 NMI 引脚和 INTR 引脚触发的,顾名思义,可屏蔽中断对硬件中断实现了是否屏蔽的标识,这意味着更加灵活的中断控制,因此也是所有中断最为常用的类型。
为了控制中断的屏蔽,以及在众多中断中控制中断触发的优先级等功能,CPU 在 INTR 引脚上级联了两个 8259A 芯片,8259A 芯片就是“可编程中断控制器”。
如下图所示,这两个级联的 8259A 芯片,每一个都有 8 根中断信号线,从而可以挂接 15 个不同的外部设备,在实地址模式下,IRQ0 ~ IRQ7 被设置为了中断向量号 08h ~ 0Fh 的中断。

保护模式下的中断和异常(上) -- 硬件原理篇

3.1. 8259A 的初始化

8259A 芯片有两种工作状态:

  1. 编程状态
  2. 操作状态

加电之初,8259A 处于编程状态,此时 CPU 可以通过 out 指令,向分别挂载在 20h、21h 端口和 A0h、A1h 端口的主 8259A 芯片和从 8259A 芯片写入特定的初始化指令来实现芯片的设置,这个特定的初始化指令被称为 ICW(Initialization Command Word),ICW 命令共有 4 个,分别是 ICW1、ICW2、ICW3、ICW4,他们必须从 1 到 4 依次进行初始化。
ICW 具体取值如下:

保护模式下的中断和异常(上) -- 硬件原理篇

可以看到,由于 80×86 体系约定使用主片的 IR2 引脚级联从片,我们就可以确定全部的 ICW 字段取值了,其中最为重要的是 ICW2 的中断向量号标识,他表示 IQR0 对应的中断向量号,此后,IQR1 ~ IQR7 会分别对应 IQR0 的中断向量号 + 1 ~ IQR0 的向量中断号 + 7。

  • 下面的代码展示了如何初始化 8259A,在实地址模式或是保护模式下执行都可以,但只能执行一次,且必须按照顺序执行:Init8259A: mov al, 011h ; ICW1,级联 8259A,需要 ICW4,8 字节中断向量,边沿触发 out 020h, al ; 主 8259A,ICW1 out 0A0h, al ; 从 8259A,ICW1 mov al, 020h ; 主 ICW2,IRQ0 中断向量设置为 0x20 out 021h, al ; 主 8259A,ICW2 mov al, 028h ; 从 ICW2,IRQ8 中断向量设置为 0x28 out 021h, al ; 从 8259A,ICW2 mov al, 004h ; 主 ICW3,IR2 引脚级联从 8259A out 021h, al ; 主 8259A,ICW3 mov al, 002h ; 从 ICW3,主 8259A 的 IR2 引脚级联从 8259A out 0A1h, al ; 从 8259A,ICW3 mov al, 001h ; ICW4,非自动 IOE,80x86 模式,无缓冲,非特殊完全嵌套方式 out 021h, al ; 主 8259A,ICW4 out 0A1h, al ; 从 8259A,ICW4

3.2. 8259A 的操作控制 — OCW

完成了上述初始化操作,8259A 就从编程状态进入了操作状态,此时我们可以通过操作控制字 OCW(Operation Control Word)来实现操作控制,虽然和 ICW 一样,OCW 也不只有一个,而是有 OCW1、OCW2、OCW3 三个,但实际上我们只需要使用 OCW1 和 OCW2。
他们分别具有下面两个功能:

  1. OCW1 — 屏蔽或打开外部中断,通过 021h 或 0A1h 端口发送
  2. OCW2 — 发送 EOI 通知 8259A 中断处理完成,通过 020h 或 0A0h 端口发送

当我们需要屏蔽或打开外部中断时,只需要设置好 OCW1,然后通过 out 021h, OCW1 或者 out 0A1h, OCW1 就可以实现主 8259A 或是从 8259A 某个或某几个中断的屏蔽或打开。
这个操作也同样被 8259A 芯片认为是一种中断,而此前我们通过 ICW4 设置了非自动 EOI 模式,所以需要在上述操作完成后通过 OCW2 发送 EOI 信号,OCW2 的 EOI 信号可以选择通过 020h 端口或 0A0h 端口发送给主或从 8259A 中的一个。
下图展示了 OCW1 和 OCW2 的字段含义:

保护模式下的中断和异常(上) -- 硬件原理篇

  • 下面的代码实现了仅开启定时器中断的功能:
mov    al, 0FEh    ; 主 OCW1,仅开启定时器中断
out    021h, al    ; 主 8259A,OCW1

mov    al, 0FFh    ; 从 OCW1,关闭所有中断
out    0A1h, al    ; 从 8259A,OCW1

mov    al, 20h        ; OCW2,发送 EOI 信号
out    020h, al    ; 主 8259A,OCW2

4. 结语

本文我们详细介绍了保护模式下的中断和异常与实地址模式下的不同之处,以及如何通过程序操作硬件 — 可编程中断控制器初始化、屏蔽或打开中断的响应,这些是理解硬件系统、操作系统的基础知识,也是硬件的部分。
本文涉及的实际开发内容比较少,你是否已经迫不及待的想要立即尝试一下如何通过程序让我们能够在保护模式下触发和响应中断呢?敬请期待下一篇文章中的实战吧。

5. 微信公众号

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤。

保护模式下的中断和异常(上) -- 硬件原理篇

6. 附录 — 系列历史文章

6.1. 准备工作

计算机是如何启动的?如何制作自己的操作系统
如何调试操作系统

6.2. 保护模式

操作系统的内存管理 — 分段与分页、虚拟地址、逻辑地址、线性地址、物理地址

6.2.1. 分段

详解 32 位保护模式与内存分段机制
进军保护模式
保护模式进阶 — 再回实模式
实战局部描述符表 LDT
利用调用门实现特权级间跳转(上) — 原理篇
利用调用门实现特权级间跳转(下) — 实战篇

6.2.2. 分页

详解操作系统分页机制与实战
实战分页机制实现 — 通过实际内存大小动态调整页表个数

7. 参考资料

《Orange’s 一个操作系统的实现》。
《linux 内核完全注释》。

来源:小脑斧科技博客,本文观点不代表自营销立场,网址:https://www.zyxiao.com/p/39554

发表评论

电子邮件地址不会被公开。 必填项已用*标注

侵权联系
分享本页
返回顶部