《汇编语言》- 读书笔记 - 第15章-外中断
《汇编语言》- 读书笔记 - 第15章-外中断
- 15.1 接口芯片及连接端口
-
15.2 外部中断信息
-
- 1. 可屏蔽式中断(Maskable Interrupt)
-
2. 不可屏蔽式中断(Non-Maskable Interrupt)
-
设计理念
-
15.3 PC 机键盘的处理过程
-
- 1. 键盘输入
- 2. 引发 9 号中断
- 3. 执行 int 9 中断例程
-
15.4 编写int9 中断例程
-
- 编程
- 思路
- 代码
- 检测点 15.1
-
15.5 安装新的 int 9 中断例程
-
- 分析
- 代码
- 运行效果
-
实验 15 安装新的 int9 中断例程
-
指令系统总结
-
int 9h 测试
-
CPU利用中断机制识别外部输入事件,并借助I/O接口向其发送指令以完成数据传输,在此过程中能够有效识别并处理来自外设的所有输入信号。
CPU 对外设输入的通常处理方法:
-
外设输入进入指定端口;
-
外设通过 CPU 接收外设置备的可被屏蔽的中断信息;
-
CPU 检测到了可被屏蔽的中断信息;
3.1. 如果 IF 状态标志为 1,在完成当前操作后响应相应的中断并执行相应的中断服务程序; -
可在中断例程中实现对外设输入的处理。
端口和中断机制,是 CPU 进行 I/O 的基础。
15.1 接口芯片和端口
CPU 通过端口和外部设备进行联系。
15.2 外中断信息
外围设备在执行完一系列自身的工作后,在生成的数据或状态被发送至中央处理器进行处理时就会引发外设中断事件
- 中断信息的发送过程大致如下:
中断请求信号 :
外设芯片会在触发时机通过自身的中断输出引脚向主板上的中断控制器传递中断请求信号。这种信号通常表现为特定的电平变化。例如,在8086体系结构中(即正确的名称),外设会被INTR线驱动并发出相应的中断请求。
中断控制器 :
主板上的中断控制器(如Intel 8259A可编程间隔控制器)负责处理来自多种外设的间隔请求,并依据它们的优先级以及相应的间隔向量进行管理。这些有效的间隔请求会被整合后通过IRQ(间隔请求线)线传输至CPU。
当CPU响应中断时,在完成当前指令后会检查是否存在未被处理的中断请求。若检测到存在此类请求,则该处理器会暂时搁置当前任务并保存其工作状态。随后系统会通过引用中断向量表(IVT)来定位相应的中段服务程序(ISR),并跳转至相应的处理程序以执行相关的中段处理。
线路连接 :
中断请求信号的传输 是通过 主板上的 硬件连线 实现 的 。例如 在 8086 / 8088 系统 中 外设与 CPU 间的 中断 请求 线 和 中断 响应 线 ( INTA #) 是 主板 上 的 物理 连接 线 , 它们 属于 系统 总线 的一部分 , 通 过 这些 线 路 上 的 电 信 号 变 化 来 实现 中断 请求 和 响 应 的 通 讯 。
伴随着技术的进步, 更为先进地采用了更为复杂的硬件配置, 该类计算机系统仍沿用传统的中断处理机制, 即外设通过专用引脚发送中断信号至中央处理器, CPU则依据预设的优先级顺序依次处理这些中断。
1. 可屏蔽中断(Maskable Interrupt)
可屏蔽中断 | 在处理可屏蔽中断时, CPU会根据CF的状态来决定: 在执行完当前指令后是否响应.
| IF 状态 | 响应 | 不响应 |
|---|---|---|
| IF = 1 | ✅ | |
| IF = 0 | ✅ |
- 中止过程
可抑制中断的中止过程主要区别在于其中一种中断类型码是以数据总线从外部设备传递至CPU进行处理;其余部分与常规内中断的行为一致。
我们可以通过指令(STI, CLI)设置IF来控制是否响应可屏蔽中断。
| ———— 指 令 ———— | — 功 能 — | 描述 |
|---|
| cli
(Clear Interrupt Flag)| IF=0
禁止中断| CPU执行cli将IF=0后,仅保留对不可屏蔽中断(NMI)的响应,直至遇到STI指令为止。
此操作有助于保护在执行重要且不能被打断的代码时,不被中断处理程序打乱。
例如: 在更改栈指针(SS:SP)时,需要防止因中断处理程序介入而破坏栈的状态。 |
| sti
(Set Interrupt Flag)| IF=1
允许中断| CPU执行sti将IF=1后,重新开启中断响应功能。CPU会再次响应挂起的中断请求,恢复正常的中断处理流程。这对于完成关键区域操作后恢复系统的正常中断响应至关重要。 |
在IF=0时间段内保证了所有中断请求的完整性,并非立即执行而是在CPU重新恢复并启动相应的中断响应机制之后按既定队列顺序依次执行这些指令具体是否发生超时及其后续处理方案则受到设备自身性能特性和操作系统调度策略的影响硬件化的中断控制器(例如Intel 8259A可编程型设计)负责对所有待发指令进行排队管理与优先级分配
改写说明
2. 不可屏蔽中断(Non-Maskable Interrupt)
当不可屏蔽中断发生时,在CPU被发现停止当前执行流程的过程中(即CPU被发现停机),它会记录必要的上下文信息,并随后跳转到预设的NMI处理程序中进行相应的处理代码执行。
即使在CPU执行CLI指令 禁止了可屏蔽中断 的情况下也同样会发生。
鉴于NMI的重要性(或者说因为NMI对于系统的稳定性如此关键),其处理过程通常要求快速且简洁的操作步骤,并尽量减少对系统运行的影响。
- NMI触发的原因可能涉及以下几个方面:
- 硬件问题常见类型包括电源失效、内存异常以及严重的硬件损坏等;
- 系统调试场景下时态中断通常由开发者主动触发或借助额外硬件实现;
- 嵌入式系统中的监控程序可能会向系统报告可能出现的重大错误;
- 高性能处理器中的性能监控单元可能出现计数器溢出的情况。
设计思想
可屏蔽中断:大多数情况是由外设触发的,并将向CPU发出通知以使其处于活跃状态;不可屏蔽中断:是指在紧急情况下必须立即处理的事件;CPU必须优先处理该事件后再进行其他工作
15.3 PC 机键盘的处理过程
1. 键盘输入
键盘上每个键分为按下与松开两种状态都会产生相应的扫描码;当按键处于按下状态时会产生通码;当处于松开状态时会产生断码;该设备会将数据传输至主板上的相关接口芯片对应的寄存器中;其中该寄存器中的端口地址具体为60h位置。
断码 = 通码 + 80h
例如:g键的通码是22h,则其断码对应于a2h。
如表15.1所示的部分键扫描编码表中仅列出通码数值。

2. 引发 9 号中断
端口通信模块接收到来自相关芯片的信号后(即当端口通信模块接收到来自相关芯片的信号时),相关芯片会将中断类型码设置为9并发送给CPU一段可屏蔽中断信息
3. 执行 int 9 中断例程
BIOS 提供 int 9 中断例程,处理基本的键盘输入:
当中断程序运行时通常会从60h端口读取扫描码,并执行相应的处理流程。该流程包括两个主要部分:其一是当检测到字符键时将扫描码转换为ASCII码,并存入键盘缓冲区;其二是当遇到功能键或控制键时则可能更新内存中的键盘状态字节或其他相关内部状态。
- 向键盘控制器发送应答信号,表明中断已被处理。
BIOS键盘缓冲区:
在系统启动过程中,“BIOS中的键盘缓冲区”是一块内存区域,在这些设置下存放着15个键盘输入事件的信息。每个事件占用2个字节的空间,在这两位二进制数据中,“其中高位字节用于扫描码编码”,而低位则用于编码实际的字符信息。
该缓冲区主要通过中断机制捕获并暂时存储键盘输入信息。即使在操作系统的预 loading阶段或当前任务繁忙时也能有效捕捉按键行为。
| 字单元(1) | 字单元(2) | … | 字单元(15) |
|---|---|---|---|
| 高位-扫描码 | 高位-扫描码 | … | 高位-扫描码 |
| 低位-字符码 | 低位-字符码 | … | 低位-字符码 |
0040:17 单元中存储了键盘状态的字节。该字节则记录了各种控制键与切换键的使用状态。其中各bit位的信息如下:
| 位 | 状态 |
|---|---|
| 0 | 右 Shift 状态,置1表示按下右 Shift 键: |
| 1 | 左 Shitt 状态,置1表示按下左 Shit 键; |
| 2 | Ctrl 状态,置1表示按下 Ctrl 键; |
| 3 | Alt 状态,置1表示按下 Alt 键; |
| 4 | ScrollLock状态,置1表示 Scroll 指示灯亮; |
| 5 | NumLock 状态,置1表示小键盘输入的是数字; |
| 6 | CapsLock 状态,置1表示输入大写字母: |
| 7 | Insert 状态,置1表示处于删除态。 |
15.4 编写int9 中断例程
键盘输入的处理过程:
- 键盘生成扫描码序列;
- 将生成的扫描码数据传输至指定的 60h 端口;
- 致使系统启动中断处理流程;
- 中央处理器接收并执行由中断标志触发的事件处理程序
前三步均属于硬件系统的操作范畴,在此阶段我们仅有能力介入的是第四步。然而由于 int 9 的中断处理程序需要与一些硬件细节进行交互作用,在此情况下我们仅对该中断处理程序进行了封装设计。
编程
在屏幕中间按顺序展示字母'a'到'z'的同时, 人能够清楚地观察到这些字母: a"~z" : 。当在显示过程中按下 Esc` 键时, 颜色会发生变化。
思路
开始循环展示字母表中的每个字母。(为了便于观察分析,教材中建议采用空循环模拟时间延迟的效果)
自行编写一个对应的版本替代方案。
在我们的版本中嵌入并调用对应功能。
获取该版本返回的具体编码信息。
检测到按键后进行显存修改并将颜色设置为相应状态。
代码
assume cs:code
stack segment
db 128 dup(0)
stack ends
data segment
oint9 dw 0,0
data ends
code segment
start: mov ax,stack ; 设置栈段和栈顶位置
mov ss,ax
mov sp,128
mov ax,data ; 设置数据段
mov ds,ax
mov ax,0 ; 设置附加段
mov es,ax
; ------------ 保存原 int 9 中断列和入口到 ds:0, ds:2 ------------
push es:[9*4]
pop ds:[0]
push es:[9*4+2]
pop ds: [2]
; ---------------- 将我们的新 int 9 写入中断向量表 ----------------
mov word ptr es:[9*4],offset int9
mov es:[9*4+2],cs
; ------------------------- 显示 a 到 z -------------------------
mov ax,0b800h ; 设置显存段
mov es,ax
mov ah,'a' ; 要显示的字符串,从 a 开始
s: mov es:[160*12+40*2],ah ; 显示字符
call delay ; 调用子程序:延时
inc ah ; 下一个字符
cmp ah,'z' ; 如果不是z继续循环
jna s
; ------------- 将中断向量表中 int 9恢复为原来的地址 -------------
mov ax,0
mov es,ax
push ds:[0]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2]
mov ax,4c00h
int 21h
; =======================================================
; --------------------- 子程序 delay -------------------
; 让CPU空循环,模拟延时效果
; -------------------------------------------------------
; 参数: 无
; 返回: 无
; -------------------------------------------------------
delay:
push ax ; 备份寄存器
push dx
mov dx,2h ; 循环 2h 次,可以自己把握
mov ax,0
delays: sub ax,1
sbb dx,0 ; 10000000h 循环递减
cmp ax,0 ; 直到 ax, dx 都为 0 才跳出循环
jne delays
cmp dx,0
jne delays
pop dx ; 还原寄存器
pop ax
ret ; 返回
; ---------------------- 子程序 delay -------------------
; =======================================================
; =======================================================
; --------------------- 子程序 int 9 -------------------
; 调用原 int 9 获取扫描码,实现按 Esc 变色
; -------------------------------------------------------
; 参数: 无
; 返回: 无
; -------------------------------------------------------
int9:
push ax ; 备份寄存器
push dx
push es
in al,60h ; 从60h端口读取数据
pushf
pushf
pop bx
and bh,11111100b
push bx
popf
call dword ptr ds:[0] ; 调用原来的 int 9 中断例程
cmp al,1
jne int9ret
mov ax,0b800h
mov es,ax
inc byte ptr es:[160*12+40*2+1]
int9ret:pop es ; 还原寄存器
pop dx
pop ax
iret ; 返回
; --------------------- 子程序 int 9 ------------------
; =======================================================
code ends
end start
检测点 15.1
标题
各章检测点汇总 - 检测点 15.1
15.5 安装新的 int 9 中断例程
| 任务 | 安装一个新的 int9 中断例程。 |
|---|---|
| 功能 | 在 DOS下,按F1键后改变当前屏幕的显示颜色,其他的键照常处理 |
分析
调整屏幕的画面色彩
mov ax,0b800h
mov es,ax
mov bx,1
mov cx,2000
s: inc byte ptr es:[bx]
add bx,2
loop s
按键常规处理
直接调用原 int 9
原 int9 入口地址
不能保存在安装程序中。我们把它放在0:0200。
安装新 int9 中断例程
0:0200 ~ 0:0203用来保存原 int9 的地址了。
我们保存新 int9 时从 0:0204 开始。
代码
assume cs:code
stack segment
db 128 dup(0)
stack ends
code segment
start: mov ax,stack ; 设置栈段和栈顶位置
mov ss,ax
mov sp,128
; -------- 安装: 复制中断例程到目标内存 -------
mov ax,cs ;设置 ds:si 指向源地址
mov ds,ax
mov si,offset int9
mov ax,0 ;设置 es:di 指向目的地址
mov es,ax
mov di,204h
mov cx,offset int9end-offset int9 ;设置 cx为传输长度
cld ;设置传输方向为正。movsb中si,di递增
rep movsb ;重复复制数据次数由 cx 控制
; -------- 安装: 复制中断例程到目标内存 -------
; ---------- 备份原 int9 入口到 [0:200~0203] ----------
push es:[9*4]
pop es:[200h]
push es:[9*4+2]
pop es:[202h]
; ---------- 备份原 int9 入口到 [0:200~0203] ----------
; ---------- 设置中断向量表 ----------
cli ; 临时屏蔽中断
mov word ptr es:[9*4],204h ; 设置的偏移地址(0~3用来存原int9地址了)
mov word ptr es:[9*4+2],0 ; 设置的段地址
sti ; 恢复中断
; ---------- 设置中断向量表 ----------
ok: mov ax,4c00h
int 21h
; =======================================================
; --------------------- 子程序 int 9 -------------------
; 调用原 int 9 获取扫描码,实现按 Esc 变色
; -------------------------------------------------------
; 参数: 无
; 返回: 无
; -------------------------------------------------------
int9:
push ax ; 备份寄存器
push bx
push cx
push es
in al,60h ; 从60h端口读取数据
; 模拟 int 指令,用 call 调用原 int 9
pushf ; 进入中断后 IF、TF已经是0 直接入栈即可
call dword ptr cs:[200h] ; 调用原来的 int 9 中断例程
cmp al,3bh ; 判断是否 F1 键
jne int9ret ; 如果不是直接结束
mov ax,0b800h ; 设置显存
mov es,ax
mov bx,1
mov cx,2000
s: inc byte ptr es:[bx]
add bx,2
loop s
int9ret:pop es ; 还原寄存器
pop cx
pop bx
pop ax
iret ; 返回
int9end:nop
; --------------------- 子程序 int 9 ------------------
; =======================================================
code ends
end start
运行效果

实验 15 安装新的 int9 中断例程
《汇编语言》读书笔记 第15章外中断实验15 配置新的int9中断程序
指令系统总结
int 9h 测试
仅响应编号为1、2和3的键:在第1行第1个字符处显示测试结果。
按下q键将退出程序。
使用自定义int9替代原有版本,并以'stop: jmp short stop'指令阻止程序退出。
由于未将‘自定义int9’复制到目标地址空间中指定位置(如0:200),因此若当前程序退出,则该自定义参数将丢失。
assume cs:code,ds:data
data segment
old_int9 dw 0,0 ; 备份的原 int 9 入口地址
data ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,0
mov es,ax
; ---------- 备份原 int9 入口到 [0:200~0203] ----------
push es:[9*4]
pop old_int9[0]
push es:[9*4+2]
pop old_int9[2]
; ---------- 备份原 int9 入口到 [0:200~0203] ----------
; ---------- 设置中断向量表 ----------
cli ; 临时屏蔽中断
mov word ptr es:[9*4],offset int9 ; 设置的偏移地址
mov word ptr es:[9*4+2],cs ; 设置的段地址
sti ; 恢复中断
; ---------- 设置中断向量表 ----------
stop: jmp short stop
exit:
; ---------- 还原中断向量表 ----------
mov ax,0
mov es,ax
cli ; 临时屏蔽中断
mov ax,old_int9[0]
mov word ptr es:[9*4],ax ; 设置的偏移地址
mov ax,old_int9[2]
mov word ptr es:[9*4+2],ax ; 设置的段地址
sti ; 恢复中断
; ---------- 还原中断向量表 ----------
mov ax,4c00h
int 21h
; =======================================================
; --------------------- 子程序 int 9 -------------------
; 调用原 int 9 获取扫描码,松开 A 显示满屏幕的 A
; -------------------------------------------------------
; 参数: 无
; 返回: 无
; -------------------------------------------------------
int9:
push ax ; 备份寄存器
push bx
push cx
push es
in al,60h ; 从60h端口读取数据
; 模拟 int 指令,用 call 调用原 int 9
pushf ; 进入中断后 IF、TF已经是0 直接入栈即可
call dword ptr old_int9 ; 调用原来的 int 9 中断例程
mov dx,0b800h
mov es,dx
; 全局热键
cmp al,10h ; 按 q 退出(调试时用)
je exit
cmp al,02h
je key1
cmp al,03h
je key2
cmp al,04h
je key3
jmp int9_ok
key1:
; 打印调试信息
mov al,1+48
mov es:[0],al
jmp int9_ok
key2:
; 打印调试信息
mov al,2+48
mov es:[0],al
jmp int9_ok
key3:
; 打印调试信息
mov al,3+48
mov es:[0],al
int9_ok:pop es ; 还原寄存器
pop cx
pop bx
pop ax
iret ; 返回
int9end:nop
; --------------------- 子程序 int 9 ------------------
; =======================================================
code ends
end start
