内中断
产生
中断源
- 除法错误
- 单步执行
- 执行 into 指令
-
中断类型码
中断类型码表示几号中断源
除法错误:0
- 单步执行:1
- into 指令:4
-
向量表
根据 8 位中断类型码,在中断向量表中查找中断处理程序的入口地址
8086CPU 最多支持256
个中断源
中断向量表存放在内存地址0000:0000~0000:03FF
处,一个表项包含中断处理程序的段地址和偏移地址,因此占用4
个字节
高地址存放段地址,低地址存放偏移地址
中断源从0
号开始
过程
获取中断类型
N
pushf
标志寄存器入栈TF=0``IF=0
(TF=1
就会产生单步中断,因此处理中断的时候要将TF=0
避免中断程序执行时陷入无限中断中)push CS
push IP
- 获取中断处理程序内存地址(
[N×4+2]:[N×4]
) - 执行中断处理程序
iret 指令
用于恢复中断处理程序前的状态,等价于pop IP
pop CS
popf
除法中断处理
``` assume cs:code
code segment start:mov ax,1000h mov bl,1 div bl
mov ax,4c00h
int 21h
code ends
end start
上面的程序会产生除法溢出,因为除数是 8 位的,因此结果保存在`ax`里,其中商`1000h`保存在`al`里,出现溢出,余数`0`保存在`ah`里,无溢出
<a name="iXZuI"></a>
### 处理 0 号中断程序
assume cs:code
code segment
start:mov ax,cs mov ds,ax mov si,offset do0
mov ax,0
mov es,ax
mov di,200h
mov cx,offset do0end - offset do0 ; 获取 do0 程序的字节数
cld
rep movsb ; 将 do0 程序送入内存地址 0000:0200 处
; 设置中断向量表连接到 0000:0200
mov ax,0
mov es,ax
mov word ptr es:[0*4],200h
mov word ptr es:[0*4+2],0
mov ax,4c00h
int 21h
; 以下都是 do0 程序
do0:jmp short do0start
db 'overflow!'
do0start:mov ax,cs mov ds,ax mov si,202h ; jmp 占两个字节, 202h 链接到字符串的地址
mov ax,0b800h
mov es,ax
mov di,12*160+36*2 ; es:di 指向显存空间的中间位置
; 把字符串一个个取出来放到显存空间
mov cx,9
s:mov al,[si]
mov es:[di],al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
do0end:nop
code ends
end start
上述程序解析:
1. 处理程序`do0`
1. 将`do0`送入`0:200`
1. 将中断向量表`0`号中断源的处理程序地址改为`0:200`
1. 将字符串`overflow`输入到显存上
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2327383/1642689268161-d76248b4-5dd9-44f3-b778-bafdd799e5bb.png#clientId=ubcf4126b-6445-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=293&id=u5fa8eea3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=439&originWidth=644&originalType=binary&ratio=1&rotation=0&showTitle=true&size=10382&status=done&style=none&taskId=u2b4264bb-34dd-4e4d-a3a4-05d940968bc&title=%E7%BB%93%E6%9E%9C%E4%BC%9A%E5%9C%A8%E4%B8%AD%E9%97%B4%E6%98%BE%E7%A4%BA%20overflow&width=429.3333333333333 "结果会在中间显示 overflow")
<a name="KBD5z"></a>
## 单步中断
执行完一条指令后,寄存器`TF`被设置成`1`产生单步中断(`1`号中断源),并将寄存器的状态显示在屏幕上,等待下一条指令<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2327383/1642686958381-af7eec27-0591-45a5-86d4-434610f432f7.png#clientId=u9910ed90-4d2b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=111&id=u1fcea68b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=396&originWidth=2063&originalType=binary&ratio=1&rotation=0&showTitle=true&size=20714&status=done&style=none&taskId=u61ccf450-21b9-4d3f-aa03-c1fc8d8fdfd&title=%E5%8D%95%E6%AD%A5%E4%B8%AD%E6%96%AD%E6%A1%88%E4%BE%8B&width=579.3333740234375 "单步中断案例")
<a name="JBNOq"></a>
## 特殊情况
设置`ss`和`sp`的时候不产生单步中断
mov ax,2000 mov ss,ax mov sp,10 mov bx,1000
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2327383/1642687743202-e87603b9-75d7-4913-ba5a-e28d1890f18c.png#clientId=u9910ed90-4d2b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=231&id=u254b0bbb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=539&originWidth=1599&originalType=binary&ratio=1&rotation=0&showTitle=false&size=185709&status=done&style=none&taskId=u73bd5556-6ac8-473a-9d17-2899855ac74&title=&width=685.3333740234375)<br />因此设置`ss`和`sp`应该是连续的指令
<a name="oDfUK"></a>
# int 指令
<a name="dcsh6"></a>
## 求平方中断程序
assume cs:code
code segment start:mov ax,3456 ; 0D80H int 7ch ; 产生中断, 去获取 ax 的平方, 并且低位存在 ax 里, 高位存在 dx 里 add ax,ax adc dx,dx ; 最后结果 016C8000H mov ax,4c00h int 21h code ends end start
```
;中断安装程序
assume cs:code
code segment
start:mov ax,cs
mov ds,ax
mov si,offset sqr
; 安装程序
mov ax,0
mov es,ax
mov di,200h
mov cx,offset sqrend - offset sqr
cld
rep movsb
; 修改向量表
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0
mov ax,4c00h
int 21h
sqr:mul ax
iret
sqrend:nop
code ends
end start
转换大写
assume cs:code
data segment
db "conversation",0
data ends
code segment
start:mov ax,data
mov ds,ax
mov si,0
int 7ch
mov ax,4c00h
int 21h
code ends
end start
;中断安装程序
assume cs:code
code segment
start:mov ax,cs
mov ds,ax
mov si,offset capital
; 安装程序
mov ax,0
mov es,ax
mov di,200h
mov cx,offset capitalend - offset capital
cld
rep movsb
; 修改向量表
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0
mov ax,4c00h
int 21h
capital:push si
push cs
change:mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111B
inc si
loop change
ok:pop si
pop cx
iret
capitalend:nop
code ends
end start
模拟 loop 指令
assume cs:code
code segment
start:mov ax,0b800h
mov es,ax
mov di,160*12
mov bx,offset s - offset se ; 注意此时的偏移差是反向偏移
mov cx,80
s:mov byte ptr es:[di],'!' ; 把 ! 送入显存空间
add di,2 ; 地址加一个字
int 7ch ; 跳转到中断程序, 此时入栈的 IP 为 se 的偏移地址
se:nop
mov ax,4c00h
int 21h
code ends
end start
;中断安装程序
assume cs:code
code segment
start:mov ax,cs
mov ds,ax
mov si,offset lp
; 安装程序
mov ax,0
mov es,ax
mov di,200h
mov cx,offset lpend - offset lp
cld
rep movsb
; 修改向量表
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0
mov ax,4c00h
int 21h
lp:push bp
mov bp,sp ; 获取栈顶偏移地址
dec cx ; cx 先 -1
jcxz lpret ; 再判断 cx 是否为 0, 为 0 则直接跳出中断程序
add [bp+2],bx ; 不为 0 则将 se 的偏移地址 + 之前的偏移差获得 s 的偏移地址
; [bp] 是原 bp [bp+2] 是 se 的 IP, [bp+4] 是 se 的 CS
lpret:pop bp
iret ; 如果前面的 add 执行了, 则 cs:ip 就指向 s, 实现循环
lpend:nop
code ends
end start
BIOS 中的中断程序
int 10h
是包含多个与屏幕输出相关子程序的中断程序
assume cs:code
code segment
mov ah,2 ; 2 号子程序, 设置光标位置
mov bh,0 ; 第 0 页
mov dh,5 ; 第 5 行
mov dl,12 ; 第 12 列
int 10h
mov ah,9 ; 9 号子程序, 在光标位置显示字符
mov al,'a' ; 显示的字符
mov bl,11001010B ; BL(闪烁) RGB(背景) I(高亮) RGB(前景)
mov bh,0 ; 第 0 页
mov cx,3 ; 字符重复 3 次
int 10h
mov ax,4c00h
int 21h
code ends
end
DOS 中的中断程序
;mov ax,4c00h
mov ah,4ch ; 第 4c 号子程序
mov al,0 ; 返回值
int 21h
光标处显示字符串
assume cs:code
data segment
db 'Welcome to masm','$'
data ends
code segment
start:mov ah,2
mov bh,0
mov dh,5
mov dl,12
int 10h ; 这里设置光标
mov ax,data
mov ds,ax
mov dx,0 ; ds:dx 指向字符串
mov ah,9 ; 21h 的第 9 号子程序
int 21h
mov ax,4c00h
int 21h
code ends
end start
外中断
外设的输入到达后,相关芯片将向CPU
发出相应的中断信息,CPU
执行完当前指令后就会检测到发送过来的中断信息
可屏蔽中断
IF=1
则CPU
会在执行完当前指令后响应中断,IF=0
则不会响应,因此内中断过程中需要将IF
设置成0
sti ; 设置 IF=1
cli ; 设置 IF=0
几乎所有的外设中断都是可屏蔽的
键盘输入
按下一个键,开关接通,芯片产生一个扫描码
,说明了该键在键盘上的位置,称为通码
松开一个键,也会产生扫描码,称为断码
通码和断码都是一个字节
通码最高位为0
断码最高位为1
,即通码+80h=断码
主板上相关接口端口地址60h
中断执行过程
- 读出
60h
端口的扫描码 - 字符按键,将对应的
ASCII
码送入BIOS
的键盘缓冲区,控制按键(ctrl
)或切换按键(CapsLk
),转成状态字节
,写入内存中存储状态字节的单元(0040:17
)BIOS
键盘缓冲区可存储15
个键盘输入 一个键盘输入使用一个字
存放,高位扫描码,低位字符码(ASCII码
)
位置 | 按键 | 值 |
---|---|---|
0 | 右 shift |
1 按下 |
1 | 左 shift |
|
2 | ctrl |
|
3 | alt |
|
4 | ScrollLock |
1 指示灯亮 |
5 | NumLock |
1 键盘输入数字 |
6 | CapsLock |
1 大写 |
7 | Insert |
1 删除状态 |
int9 中断程序
assume cs:code,ss:stack
stack segment
db 128 dup (0)
stack ends
data segment
dw 0,0 ; 这里要保存系统自带 int9 程序的内存地址
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
; 将自带的 int9 程序内存地址保存起来
push es:[9*4]
pop ds:[0]
push es:[9*4+2]
pop ds:[2]
; 中断向量表中设置新的 int9 程序
mov word ptr es:[9*4],offset int9
mov es:[9*4+2],cs
mov ax,0b800h
mov es,ax
mov ah,'a'
s: mov es:[160*12+40*2],ah
call delay
inc ah
cmp ah,'z'
jna s ; 如果不等于'z'就继续
mov ax,0
mov es,ax
; 中断向量表恢复成原来的
; 不恢复别的程序无法使用键盘
; 这里也可以使用下面这种写法
; mov ax,ds:[0]
; mov es:[9*4],ax
push ds:[0]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2]
mov ax,4c00h
int 21h
delay:push ax
push dx
mov dx,10h ; 循环 10/0000h次
mov ax,0
s1: sub ax,1
sbb dx,0
cmp ax,0
jne s1
cmp dx,0 ; 必须 dx 和 ax 都为 0 才退出
jne s1
pop dx
pop ax
ret
int9:push ax
push bx
push es
in al,60h ; 读取60h端口的扫描码
; 模拟调用原来的 int9 程序
; 不能直接使用 int9 因为程序入口已经改成新的 int9 程序了
pushf ; 这里要用两次 pushf 因为 pushf 操作一个字节, 而 pop bx 操作两个字节
pushf
pop bx
and bx,11111100B ; 设置 TF=0 IF=0
push bx
popf
call dword ptr ds:[0]
; 这里其实可以不需要设置 TF=0 IF=0
; 因为能进入新的 int9 程序, 说明当前处于中断中, TF/IF 已经被设置成 0 了
cmp al,1 ; esc 扫描码 01
jne int9ret
mov ax,0b800h
mov es,ax
inc byte ptr es:[160*12+40*2+1] ; 属性+1,改变颜色
int9ret:pop es
pop bx
pop ax
iret
code ends
end start
不可屏蔽中断
中断类型码为2
,因此CS=0AH``IP=8H