汇编语言(王爽)-外中断


CPU除了有运算能力外,还要有I/O能力

要及时处理外设的输入,显然需要解决两个问题:

  1. 外设的输入随时可能发生,CPU如何得知?

  2. CPU从何处得到外设的输入?


接口芯片和端口

外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的端口中

CPU向外设的输出也不是直接送入外设,而是先送入端口中,再由相关的芯片送到外设

CPU还可以向外设输出控制命令,这些控制命令先送到相关芯片的端口中,然后再由相关的芯片根据命令对外设实施控制

可见,CPU通过端口和外部设备进行联系


外中断信息

除了内中断外,还有一种中断信息,来自于CPU外部,当CPU外部有需要处理的事情发生时,相关芯片会向CPU发出相应的中断信息。CPU在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的中断

PC系统中,外中断源分为:

  1. 可屏蔽中断

  2. 不可屏蔽中断

可屏蔽中断

CPU可以不响应的外中断

CPU是否响应可屏蔽中断,取决于标志寄存器IF位的设置

CPU检测到可屏蔽中断信息时,若IF=1,则CPU在执行完当前指令后响应中断,引发中断过程;若IF=0,则不响应可屏蔽中断

可屏蔽中断的中断过程:

  1. 通过数据总线将中断类型码n送入CPU

  2. 标志寄存器入栈,IF=0,TF=0

  3. CSIP入栈

  4. (IP)=(n*4)(CS)=(n*4+2)

中断过程将IF置为0的原因是:在进入中断处理程序后,禁止其他的可屏蔽中断

若在中断处理程序中需要处理可屏蔽中断,可用指令将IF置1

8086CPU提供的设置IF的指令为:

1
2
sti		;IF置为1
cli ;IF置为0

不可屏蔽中断

不可屏蔽中断是CPU必须响应的外中断

CPU检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程

对于8086CPU,不可屏蔽中断的中断类型码固定为2

不可屏蔽中断的中断过程:

  1. 标志寄存器入栈,IF=0,TF=0

  2. CSIP入栈

  3. (IP)=(8)(CS)=(0AH)

几乎所有由外设引发的外中断,都是可屏蔽中断

当外设有需要处理的事件发生时,相关芯片向CPU发出可屏蔽中断信息

不可屏蔽中断是在系统中有必须处理的紧急情况发生时用来通知CPU的中断信息


PC机键盘的处理过程

键盘输入

键盘上的每一个键相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描

按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码说明了按下的键在键盘上的位置。扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60h

松开按下的键时,也产生一个扫描码,扫描码说明了松开的键在键盘上的位置。松开按键时产生的扫描码也被送入60h端口中

按下一个键时产生的扫描码称为通码,松开一个键产生的扫描码称为断码

扫描码长度为一个字节,通码的第7位为0,断码的第7位为1,即:断码=通码+80h

键盘中部分键的扫描码通码:

1.png

2.png

引发9号中断

键盘的输入到达60h端口时,相关芯片就会向CPU发出中断类型码为9的可屏蔽中断信息

CPU检测到该中断信息后,若IF=1,则响应中断,引发中断过程,转去执行int 9中断例程

执行int 9中断例程

BIOS提供了int 9中断例程,用于进行基本的键盘输入处理

int 9中断例程主要工作:

  1. 读出60h端口中的扫描码

  2. 若是字符键的扫描码,将该扫描码和所对应的字符码(ASCII码)送入内存中的BIOS键盘缓冲区

    若是控制键和切换键的扫描码,则将其转变为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储状态字节的单元

  3. 对键盘系统进行相关的控制,例:向相关芯片发出应答信息

BIOS键盘缓冲区是系统启动后,BIOS用于存放int 9中断例程所接收的键盘输入的内存区。该内存区可以存储15个键盘输入。

BIOS键盘缓冲区中,一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码

0040:17单元存储键盘状态字节,该字节记录了控制键和切换键的状态

键盘状态字节各位记录的信息为:

表示 信息
0 右shift状态 置1表示按下
1 左shift状态 置1表示按下
2 Ctrl状态 置1表示按下
3 Alt状态 置1表示按下
4 ScrollLock状态 置1表示Scroll指示灯亮
5 NumLock状态 置1表示小键盘输入的是数字
6 CapsLock状态 置1表示输入大写字母
7 Insert状态 置1表示处于删除态

编写int 9中断例程

键盘输入的处理过程:

  1. 键盘产生扫描码

  2. 扫描码送入60h端口

  3. 引发9号中断

  4. CPU执行int 9中断例程处理键盘输入

第1-3步由硬件系统完成,可控的部分仅为int 9中断处理程序

程序需调用原int 9中断例程去处理硬件细节

编程:在屏幕中间依次显示”a”–”z”,在显示过程中,按下Esc键,改变显示颜色

依次显示”a”–”z”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code
code segment

start: mov ax,0b800h
mov es,ax
mov ah,'a'
s: mov es:[160*12+40*2],ah ;显示
inc ah
cmp ah,'z' ;比较当前字符与z
jna s ;不高于z则转移,继续循环

mov ax,4c00h
int 21h
code ends
end start

程序执行,无法看清屏幕上的显示。因CPU执行过快,一个字母刚显示,CPU执行几条指令后,就会变为另一个字母

程序执行,设置显示延后

当显示一个字母后,让CPU执行一段时间的空循环

CPU速度较快,所以空循环的次数一定要大,需要使用16位寄存器来存放32位的循环次数

1
2
3
4
5
6
7
8
	mov dx,10h
mov ax,0
s: sub ax,1 ;(ax)=(ax)-1 不带借位减法
sbb dx,0 ;(dx)=(dx)-0-CF 带借位减法
cmp ax,0 ;比较
jne s ;不等于则转移
cmp dx,0
jne s

以上程序,循环100000h

实现按Esc键后,改变显示颜色

键盘输入到达60h端口后,引发9号中断,CPU转去执行int 9中断例程

重新编写int 9中断例程,功能:

  1. 60h端口中读出键盘的输入

  2. 调用BIOSint 9中断例程,处理其他硬件细节

  3. 判断是否为Esc的扫描码,若是,改变颜色后返回;若不是,直接返回

从端口60h读出键盘的输入

1
in al,60h		;in指令只可使用al或ax接收数据

调用BIOS的int 9中断例程

重新写的中断处理程序要成为新的int 9中断例程,主程序需将中断向量表中的int 9中断例程的入口地址改为新中断处理程序的入口地址。则在新中断例程调用原int 9中断例程时,中断向量表中的int 9中断例程并非原中断例程,所以不可使用int指令直接调用

必须在将中断向量表中的中断例程入口地址更改之前,将原入口地址保存

不能使用int 9指令调用原中断例程,使用另外的指令模拟int指令,实现对原中断例程的调用

int指令执行时,CPU的工作:

  1. 取中断类型码n

  2. 标志寄存器入栈

  3. IF=0,TF=0

  4. CSIP入栈

  5. (IP)=(n*4)(CS)=(n*4+2)

取中断类型码是为了定位中断例程的入口地址,此时我们保存了原中断例程的入口地址,所以不需要第一步

使用pushf替代第二步

使用以下程序替代第三步

1
2
3
4
5
pushf
pop ax
and ah,11111100b ;将IF和TF(第9位与第8位)置为0
push ax
popf

使用call dword ptr xxx替代第四步,第五步

若是Esc的扫描码,改变显示的颜色后返回

显示的位置是屏幕的中间,即第12行40列,所以字符的ASCII码送入段地址b800h,偏移地址160*12+40*2处。而段地址b800h,偏移地址160*12+40*2+1处为字符的属性,改变此处的数据即可改变字符的颜色

在此程序返回前,将中断向量表中int 9中断例程的入口地址恢复为原来的地址

完整程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
assume cs:code

stack segment
db 128 dup (0)
stack ends

data segment
dw 0,0
data ends

code segment
start: mov ax,stack
mov ss,ax
mov sp,128 ;ss:sp指向栈顶

mov ax,data
mov ds,ax ;用于存放原中断例程入口地址

mov ax,0
mov es,ax

push es:[9*4]
pop ds:[0]
push es:[9*4+2]
pop ds:[2] ;通过栈将原int 9中断例程入口地址保存在ds:0、ds:2内存单元中

mov word ptr es:[9*4],offset int9 ;在中断向量表中设置新int 9中断例程偏移地址
mov es:[9*4+2],cs ;在中断向量表中设置新int 9中断例程段地址

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 ;不高于则转移

mov ax,0
mov es,ax

push ds:[0]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2] ;通过栈恢复原int 9中断例程的入口地址

mov ax,4c00h
int 21h

delay: push ax ;设置显示延后
push dx
mov dx,1000h
mov ax,0
sl: sub ax,1
sbb dx,0
cmp ax,0
jne s1
cmp dx,0
jne s1
pop dx
pop ax
ret

int9: push ax ;新int 9中断例程
push bx
push es

in al,60h ;取键盘输入

;模拟int功能,调用原int 9中断例程
pushf ;标志寄存器入栈
pushf ;标志寄存器入栈,用于修改
pop bx
and bh,11111100b ;修改IF、TF的值
push bx
popf ;修改后的标志寄存器出栈
call dword ptr ds:[0] ;CS、IP入栈;(IP)=((ds)*16+0),(CS)=((ds)*16+2)

cmp al,1 ;Esc扫描码为01,比较键盘是否为Esc扫描码
jne int9ret ;不等于则结束新int 9中断例程

mov ax,0b800h ;改变字符颜色
mov es,ax
inc byte ptr es:[160**12+40*2+1]

int9ret:pop es
pop bx
pop ax
iret

code ends
end start

关于键盘的程序,因直接访问真实的硬件,需在DOS实模式下运行


安装新的int 9中断例程

安装一个新的int 9中断例程,使得原int 9中断例程功能得到扩展

功能:在DOS下,按F1键改变当前屏幕的显示颜色,其他键无改变

改变屏幕的显示颜色

改变从b8000h开始的4000个字节中的所有奇地址单元中的内容,当前屏幕显示颜色即发生改变

1
2
3
4
5
6
7
	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中断处理程序,用于处理其他的键盘输入

原int 9中断例程入口地址的保存

因新编写的int 9中断例程中需使用到原int 9中断例程,所以,要保存原int 9中断例程的入口地址

不可保存在安装程序中,因安装程序返回后地址将丢失。将地址保存在0:200单元中

新int 9中断例程的安装

将新的int 9中断例程安装在0:204处

完整程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
assume cs:code

stack segment
db 128 dup (0)
stack ends

code segment
start: mov ax,stack
mov ss,ax
mov sp,128 ;ss:sp指向栈顶

push cs
pop ds ;将cs的内容通过栈赋给ds

mov ax,0
mov es,ax

mov si.offset int9 ;ds:si指向源地址
mov di,204h ;es:di指向目的地址
mov cx,offset int9end-offset int9 ;cx为传输长度
cld ;正向传输
rep movsb ;串传送

push es:[9*4]
pop es:[200h]
push es:[9*4+2]
pop es:[202h] ;通过栈将原int 9中断例程入口地址保存

cli ;IF置为0,禁止其他可屏蔽中断
mov word ptr es:[9*4],204h
mov word ptr es:[9*4+2],0 ;在中断向量表中写入新int 9入口地址
sti ;IF置为1,允许其他可屏蔽中断

mov ax,4c00h
int 21h

int9: push ax ;保存需使用的寄存器
push bx
push cx
push es

in al,60h ;获取键盘输入

;模拟int功能,调用原int 9中断例程
pushf ;标志寄存器入栈
pushf ;标志寄存器入栈,用于修改
pop bx
and bh,11111100b ;修改IF、TF的值
push bx
popf ;修改后的标志寄存器出栈
call dword ptr cs:[200h] ;CS、IP入栈;(IP)=((cs)*16+200h+0),(CS)=((cs)*16+200h+2)。此中断例程执行时(CS)=0

cmp al,3bh ;F1的扫描码为3bh,比较
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

code ends
end start

CPU对外设输入的通常处理方法:

  1. 外设的输入送入端口

  2. CPU发出外中断(可屏蔽中断)信息

  3. CPU检测到可屏蔽中断信息,若IF=1,CPU在执行完当前指令后响应中断,执行相应的中断例程

  4. 可在中断例程中实现对外设输入的处理


指令系统总结

数据传送指令

实现寄存器和内存、寄存器和寄存器之间的单个数据传送

例:movpushpoppushfpopfxchg

算术运算指令

实现寄存器和内存中的数据的算数运算

执行的结果影响标志寄存器的SFZFOFCFPFAF

例:addsubabcsbbincdeccmpimulidivaaa

逻辑指令

例:andornotxortestshlshrsalsarrolrorrclrcr

not指令外,其他指令所执行的结果都影响标志寄存器的相关标志位

转移指令

可以修改IP,或同时修改CSIP的指令统称为转移指令

  1. 无条件转移指令,例:jmp

  2. 条件转移指令,例:jcxzjejbjajnbjna

  3. 循环指令,例:loop

  4. 过程,例:callretretf

  5. 中断,例:intiret

处理机控制指令

对标志寄存器或其他处理机状态进行设置

例:cldstdclistinopclccmcstchltwaitesclock

串处理指令

对内存中的批量数据进行处理

例:movsbmovswcmpsscaslodsstos

要方便的进行批量数据的处理,需和repreperepne等前缀指令配合使用

---------------The End---------------
0%