《汇编语言》- 读书笔记 - 第15章-外中断
CPU
通过中断机制
得知外部
输入事件,并通过I/O接口
与数据总线
实现与外设的数据交换
,从而实现对外设输入的有效处理。
CPU 对外设输入的通常处理方法:
- 外设的输入送入端口;
- 向 CPU 发出外中断(可屏蔽中断)信息;
CPU
检测到可屏蔽中断信息;
3.1. 如果IF=1
,CPU 在执行完当前指令后响应中断,执行相应的中断例程;- 可在中断例程中实现对外设输入的处理。
端口和中断机制,是 CPU 进行 I/O 的基础。
15.1 接口芯片和端口
CPU
通过端口
和外部设备
进行联系。
15.2 外中断信息
外部设备
在完成一系列自己的工作后,需要将产生的数据
或状态
发给CPU
处理时,就会触发外中断
- 中断信息的发送过程大致如下:
-
中断请求信号:
外设芯片在适当时候会通过自身的中断输出引脚向主板上的中断控制器发送中断请求信号,这通常表现为一个电信号的变化。例如在8086体系结构中,外设可以通过INTR(Interrupt Request)线向CPU发出中断请求。 -
中断控制器:
主板上的中断控制器(如Intel 8259A可编程中断控制器)负责接收来自多个外设的中断请求,并根据它们的优先级和中断向量进行管理。中断控制器会将有效的中断请求打包并通过IRQ(Interrupt Request Line)线传输到CPU。 -
CPU响应中断:
当CPU在执行完当前指令后检查中断请求线时,如果发现有中断请求存在,它会暂停当前任务,保存现场,然后通过读取中断向量表(IVT)获取中断服务程序(ISR)的地址,跳转到相应的处理程序执行中断处理。 -
线路连接:
中断请求信号的传送是通过主板上的硬件连线完成的。例如在8086/8088系统中,外设与CPU间的中断请求线和中断响应线(INTA#)是主板上的物理连接线,它们属于系统总线的一部分,通过这些线路上的电信号变化来进行中断请求和响应的通讯。
注意,随着技术的发展,更现代的计算机系统可能采用了更为复杂和高级的中断控制器和总线标准,但核心原理仍然是外设通过特定线路发送中断信号给CPU,并由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可编程中断控制器)负责管理和维持中断请求的排队。 CLI
与STI
结合运用,构建短暂的中断防护区域,确保其间代码免受外部中断影响,待关键操作完成后通过STI
恢复中断响应,保障系统并发处理能力和实时响应性能。在多任务OS中,内核常在切换进程环境、管控硬件资源等关键场景调用这两条指令。
2. 不可屏蔽中断(Non-Maskable Interrupt)
当不可屏蔽中断发生时,CPU会立即停止当前的执行流程,保存必要的上下文信息,然后跳转到预设的NMI处理程序去执行相应的处理代码。
即使在 CPU 执行 CLI 指令 禁止了可屏蔽中断 的情况下也不例外。
由于NMI的重要性,其处理过程一般要求快速、简洁,尽量减少对系统运行的影响。
- NMI触发的原因可能包括但不限于以下几种情况:
- 硬件故障:如电源故障、内存错误、严重硬件故障等;
- 系统调试需求:在调试环境中,开发者可能通过人工或硬件手段触发NMI,以便快速进入调试模式;
- 监控程序:某些嵌入式系统或服务器中,监控程序可能会通过NMI报告严重的系统错误或警告;
- 性能计数器溢出:某些高性能处理器中,性能监控单元(PMU)的计数器溢出可能导致NMI。
设计思想
可屏蔽中断:几乎都是外设触发的。告诉CPU来活了。
不可屏蔽中断:是发生了必须紧急处理的事件。CPU要先解决它了再说别的。
15.3 PC 机键盘的处理过程
1. 键盘输入
键盘上每个键分:按下
、松开
两种状态都会产生相应的扫描码
。
按下时的叫通码
,松开时的叫断码
。
它会被送入主板上的相关接口芯片的寄存器中,该寄存器的端口
地址为 60h
断码 = 通码 + 80h
比如: g 键的通码为 22h,断码为 a2h。
表 15.1 是键盘上部分键的扫描码,只列出通码。
2. 引发 9 号中断
60h
端口收到信号,相关的芯片就会向 CPU
发出中断类型码
为9
的可屏蔽中断
信息。
CPU
收到后,如果 IF=1
,则响应中断,引发中断过程
,转去执行 int 9
中断例程。
3. 执行 int 9 中断例程
BIOS 提供 int 9
中断例程,处理基本的键盘输入:
- 首先,中断例程通常会从60h端口读取扫描码。
- 处理扫描码
2.1. 若是字符键
,则将扫描码转换为ASCII
码,存入键盘缓冲区
。
2.2. 若是功能键
或控制键
,则可能更新内存中的键盘状态字节
或其他内部状态。 - 向键盘控制器发送应答信号,表明中断已被处理。
BIOS键盘缓冲区:
BIOS键盘缓冲区
是系统启动时BIOS设置的一块内存区域,用于存储15
个键盘输入事件,每个事件占用2字节
,其中高位
字节为扫描码
,低位
字节为字符码
。- 这个缓冲区通过
int 9
中断机制收集并暂存键盘输入,确保在操作系统尚未完全加载或正忙时也能记录用户的按键动作。
字单元(1) | 字单元(2) | … | 字单元(15) |
---|---|---|---|
高位-扫描码 | 高位-扫描码 | … | 高位-扫描码 |
低位-字符码 | 低位-字符码 | … | 低位-字符码 |
0040:17
单元存储键盘状态字节,该字节记录了控制键和切换键的状态。字节各位信息如下。
位 | 状态 |
---|---|
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
端口; - 引发
9
号中断; - CPU 执行
int 9
中断例程处理键盘输入。
前三步都是硬件系统的活,我们能插手的就只有第4步。但因为 int 9
中断处理程序要与一些硬件细节打交到。所以我们只对 int 9
做下封装扩展。
编程
在屏幕中间依次显示“a”~“z”
,并可以让人看清。在显示的过程中,按下 Esc
键后,改变显示的颜色。
思路
- 先循环显示
a ~ z
。(为了看清,书上给的方案是空循环模拟延迟效果) - 自己写一个
int 9
代替原版。
2.1. 我们自己的int 9
中调用原版 int 9
。
2.2. 拿到原版int 9
返回的扫描码。
2.3. 判断如果是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+