⚠ 转载请注明出处:作者:ZobinHuang,更新日期:July 17 2021
本作品由 ZobinHuang 采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,在进行使用或分享前请查看权限要求。若发现侵权行为,会采取法律手段维护作者正当合法权益,谢谢配合。
目录
有特定需要的内容直接跳转到相关章节查看即可。
Section 1. 外中断:展示了外中断的概念和类型
1.1 概念:阐述了 CPU 外中断的概念
1.2 可屏蔽中断:解释了可屏蔽外部中断的概念,并且解释了 CPU 可以屏蔽该类中断的原理
1.3 不可屏蔽中断:解释了不可屏蔽外部中断的概念和原理
Section 2. BIOS 处理键盘中断和磁盘中断的过程:以 BIOS 的键盘中断和磁盘中断为例,体会了 8086 CPU 处理外部中断的过程
2.1 BIOS 键盘中断 int 9 例程: 处理键盘输入:展示了 8086 处理键盘输入中断的过程
2.2 BIOS 键盘中断 int 16h 例程: 处理键盘缓冲区:展示了 8086 处理键盘缓冲区数据的过程
2.3 BIOS 磁盘中断 int 13h 例程: 对磁盘进行读写:展示了 8086 处理磁盘读取中断的过程
1. 外中断
1.1 概念
在 内中断 一文中我们介绍了来自 CPU 自己内部运行的指令产生的中断。除此之外,CPU 还必须能够 handle 来自外设接口芯片发出的中断。外设芯片可以向 CPU 发出中断信息,这类来自外部设备的中断被统称为 外中断。
外设接口芯片什么时候会触发外中断呢?我们在 端口 一文中知道了 CPU 与外设接口芯片交互的方式是通过端口。当外部设备产生新的数据后,会把数据放到设备上的存储器或者端口上,然后就会通过中断 CPU 来使能 CPU 读取这些新数据并且进行处理。
外中断可以分为以下两类:
1.2 可屏蔽中断
顾名思义,可屏蔽中断就是 CPU 可以不响应的外部中断。CPU 响不响应一个外部中断,根据的是标志寄存器中的 IF(Interrupt Flag) 来决定的。如果 IF=1,则 CPU 在执行完当前的指令后响应外部中断;如果 IF=0,则 CPU 则忽略外部中断。
可屏蔽中断引发的中断过程如下所示:
- 从数据总线上获取中断类型码 n
- 标志寄存器入栈,IF=0, TF=0
- CS, IP 寄存器入栈
- IP=n*4, CS=n*4+2
回顾我们在 内中断 一文中描述的内部中断的引发过程,我们会看到有一个 "IF=0 TF=0" 的过程。在上面的可屏蔽中断过程中我们也看到了一样的操作,此时我们就能理解,把 IF 复位的原因就是在进入中断处理程序之后,禁止其他的可屏蔽中断。
当我们在中断处理程序中需要恢复对可屏蔽中断的响应的时候,我们应该使用 "sti" 指令来设置 IF=1,类似的我们也有指令 "cti" 指令来设置 IF=0。
1.3 不可屏蔽中断
顾名思义,此类中断就是不论 IF 标志位是置位状态还是复位状态,CPU 都必须响应的一类中断。不可屏蔽中断的中断类型码固定为 2,所以在中断的过程中不需要取类型码,因此不可屏蔽中断的中断过程为:
- 标志寄存器入栈,IF=0, TF=0
- CS, IP 寄存器入栈
- IP=n*4, CS=n*4+2
2. BIOS 处理键盘中断和磁盘中断的过程
2.1 BIOS 键盘中断 int 9 例程: 处理键盘输入
键盘的编码原理
纯粹觉得好玩,这里研究一下键盘输入是如何被编码的。
当有按键 被按下 或者 被松开时,键盘都会产生一个 扫描码 (scan code)。按下时产生的 扫描码 被称为 通码,松开时产生的 扫描码 被称为 断码。扫描码的长度为一个字节,对于同一个按键的通码和断码,其低 7 位是完全相等的,区别就在于第 8 位:通码第 8 位为 0,断码第 8 位为 1。于是有这样的关系:断码 = 通码 + 80H。
键盘上常见按键的通码如下所示,断码采用上面给出的关系即可以计算出来。


键盘的输入默认是到达 60H 端口。当有键盘数据输入,并且数据存储在 60H 端口时,键盘接口芯片会发出中断类型码为 9 的可屏蔽中断。CPU 收到中断之后,如果 IF 为 1,则就会响应中断。BIOS 提供了 int 9 号中断如下:
- 读取 60H 端口的扫描码
- 如果是普通字符键的扫描码,则放入内存中的 BIOS 键盘缓冲区 (i.e. 一个环形队列);如果是控制按键 (e.g. Ctrl) 或者 切换键(e.g. CapLock),则修改内存中相应的存储状态的字节单元。
比如,当按下 "A" 键时,CPU 检测到此时只有 "A" 键按下,没有其它控制按键按下,因此就向 BIOS 键盘缓冲区中写入 "A" 键的扫描码 1EH 和对应字母 "a" 的 ASCII 码 61H 的组合 1E61。
当按下 "Shift" 键时,CPU 检测到 "Shift" 键是一个功能按键,因此转而置位 0040:17 处的状态字节的第一位,代表 "shift" 按键被按下的状态。
此时再按下 "A" 键时,CPU 检测到此时 "A" 键按下且 "shift" 按键状态置位,因此就向 BIOS 键盘缓冲区中写入 "A" 键的扫描码 1EH 和对应字母 "A" 的 ASCII 码 41H 的组合 1E41。
当松开 "Shift" 键时,CPU 检测到 "Shift" 键是一个功能按键,因此转而复位 0040:17 处的状态字节的第一位,代表 "shift" 按键被松开的状态。
此时再按下 "A" 键时,CPU 检测到此时只有 "A" 键按下,没有其它控制按键按下,因此就向 BIOS 键盘缓冲区中写入 "A" 键的扫描码 1EH 和对应字母 "a" 的 ASCII 码 61H 的组合 1E61。
2.2 BIOS 键盘中断 int 16h 例程: 处理键盘缓冲区
在BIOS 提供给程序员的 16h 中断例程中,最重要的就是 0 号功能:从键盘缓冲区读取最早的一个输入,并且将其从缓冲区中删除。也即,运行以下指令:
1
2mov ah, 0
int 16h
上面指令的效果就是:(1) 如果 BIOS 键盘缓冲区中有数据,则就会把数据读取到 AL 和 AH 寄存器中,其中 AL 存储 ASCII 码,AH 存储 扫描码,并且将键盘缓冲区中的相应数据删除。 (2) 如果 BIOS 键盘缓冲区中没有数据,则该中断处理程序阻塞,知道有数据到来为止。
2.3 BIOS 磁盘中断 int 13h 例程: 对磁盘进行读写
HDD 的硬件原理

还是纯粹觉得好玩,这里研究一下机械硬盘的结构。
如上图所示,磁盘实际上由多个 磁盘面 (platter) 组成。在每个 磁盘面中由从里到外的若干条 磁道 (track) 组成。根据硬盘的规格不同,磁道数可以从几百到成千上万不等。最外圈的磁道是0号磁道,向圆心增长依次为1磁道, 2磁道, ...。磁盘的数据存放就是从最外圈开始的。每个磁道可以存储数 Kb 的数据。在每条 磁道 中,又可以分为若干个 扇区 (sector)。现在每个扇区可存储 512 Bytes 数据已经成了业界的约定。我们又把位于不同 磁盘面 的具有相同编号 (i.e. 位置相同) 的 磁道 称为 柱面 (cylinder)。
 需要注意的是,磁盘读写数据是按 柱面 进行的,磁头读写数据时首先在同一柱面内从 0 磁头开始进行操作,依次向下在同一柱面的不同盘面(即磁头上)进行操作,只有在同一 柱面 所有的磁头全部读写完毕后磁头才转移到下一 柱面。因为选取磁头只需通过磁盘控制器电子切换即可,而选取柱面则必须通过机械切换。数据的读写是按柱面进行的,而不是按盘面进行,所以把数据存到同一个柱面是很有价值的。
在本节的例程中,我们来看看 软盘 的读写汇编原理,时代的眼泪 :-)
软盘与 HDD 类似,不同的是它只有两个盘面 (i.e. 上下两面),每面有 80 个磁道,每个磁道有 80 个扇区,每个扇区的大小同样为 512 Bytes。
BIOS 提供了 13H 中断例程用于访问磁盘。我们下面以读入数据为例,展示入口参数如下所示:
寄存器 | 含义 |
---|---|
AH | int 13H 的功能号 (2 表示功能为读入扇区) |
AL | 读取的扇区数 |
CH | 磁道号 |
CL | 扇区号 |
DH | 磁头号 (i.e. 面号) |
DL | 驱动器号 (i.e. 选择驱动器) |
ES:BX | 指向接收从扇区读入数据的内存区 |
返回值如下所示:
寄存器 | 含义 |
---|---|
AH | 操作成功时为 0;操作失败时存储出错代码 |
AL | 操作成功时,存储读入的扇区数 |
通过下面的代码,我们就能够通过 13H BIOS 中断来读取数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 设置数据在内存中的存储位置
mov ax, 0
mov es, ax
mov bx, 200H
# 设置入口参数
mov al, 1 # 读取的扇区数
mov ch, 0 # 读取的磁道号
mov cl, 1 # 读取的起始扇区号
mov dl, 0 # 读取的驱动器号
mov dh, 0 # 读取的磁头号
# 触发中断
mov ah, 2 # 设置中断功能号为 2
int 13h # 触发中断