外中断
可屏蔽中断与不可屏蔽中断
CPU除了能执行指令运算以外,还需要对外部设备进行控制,接受它们的输入,或者向它们输出,也就是I/O能力。CPU通过端口与外部设备进行联系,无论是发送和接受数据,还是输出控制命令,都是先将数据和指令送入相关芯片的端口中,然后再通过芯片对外设进行控制,或者传输到CPU。
外设的输入随时都有可能到达,CPU利用中断机制来及时处理这些信息,这种来自CPU外部的中断信息就被称为外中断。CPU在执行完当前指令后,检测到中断信息,就会引发中断过程,处理外设的输入。
在PC系统中,外中断源一共有以下两类:可屏蔽中断和不可屏蔽中断。
1、可屏蔽中断是CPU可以不响应的外中断,标志寄存器IF决定了CPU能否响应。当CPU检测到可屏蔽中断信息时,如果IF=1,则CPU在执行完当前指令后响应中断,引发中断过程;如果IF=0,则不响应可屏蔽中断。
这也就是为什么在中断时要先把IF位置为0,这是为了在进入中断处理程序后,禁止其他的可屏蔽中断。如果要在中断处理程序中打开处理可屏蔽中断的开关,可以用指令将IF置为1,8086CPU提供如下指令:
sti 设置IF为1
cli 设置IF为0
可屏蔽中断的过程:
(1)通过数据总线将中断类型码n送入CPU中
(2)标志寄存器入栈,IF=0,TF=0
(3)CS和IP入栈
(4)IP=n*4,CS=n*4+2
2、不可屏蔽中断是CPU必须响应的外中断,不可屏蔽中断类型码固定为2,几乎所有由外设引发的外中断都是可屏蔽中断,如键盘输入,不可屏蔽中断是在系统中有必须处理的紧急情况发生时用来通知CPU的中断信息。
不可屏蔽中断的过程:
(1)标志寄存器入栈,IF=0,TF=0
(2)CS和IP入栈
(3)IP=8,CS=0AH
PC机键盘的处理过程
键盘上的每一个键相当于一个开关,键盘中有一个芯片对键盘上每一个键的开关状态进行扫描。
按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60h。松开按下的键时,也会产生一个扫描码,说明了松开的键在键盘上的位置,该扫描码也会被送入60h端口中。
一般将按下一个键时产生的扫描码称为通码,松开一个键时产生的扫描码称为断码。同一个按键的通码和断码的关系:
断码=通码+80h
相同按键的通码的第7位是0,断码的第7位是1。下面是键盘上部分键的通码:
然后键盘的输入到达60h端口后,相关的芯片就会向CPU发出中断类型码为9的可屏蔽中断信息。检查IF是否为1,如果为1则响应中断,引发中断过程。
中断处理程序的工作如下:
1、读出60h端口中的扫描码
2、如果是字符键的扫描码,就将扫描码和它对应的字符码(ASCII码)送入内存中的BIOS键盘缓冲区;如果是控制键和切换键的扫描码,则将其转变为状态字节写入内存中存储状态字节的单元。
BIOS键盘缓冲区是系统启动后,BIOS用于存放键盘输入的内存区,它可以存储的键盘输入是有限的。
存储键盘状态字节的内存单元是0040:17,它的8位每个位都有特殊的含义:
3、向相关芯片发出应答信息。
案例:显示字母后按esc改变颜色
要求:在屏幕中间依次显示a-z,并可以让人看清,在显示的过程中,按下esc键后,改变显示的颜色。
显示a-z的程序:
assume cs:code
code segment
start: mov ax,0b800h
mov es,ax
mov ah,'a' 字母从a开始
s: mov es:[160*12+40*2],ah 设置显示位置
inc ah
cmp ah,'z' 字母到z结束
jna s 比较指令cmp和jna组合使用意思是不高于则转移
mov ax,4c00h
int 21h
code ends
end start
上面这段程序中字母的显示速度太快了,以至于无法看清。我们应该在每显示一个字母后延时一段时间,让人看清后再显示下一个字母,延时的方法就是让CPU执行一段时间的空循环:
mov dx,10h
mov ax,0 用dx+ax的方式存储32位循环次数
s: sub ax,1
sbb dx,0 循环次数减一
cmp ax,0
jne s
cmp dx,0 如果ax或dx不为0都要跳转到s处
jne s
然后我们把这两段程序组合一下,把循环延时的部分组成一个子程序:
assume cs:code
stack segment
db 128 dup (0)
stack ends
start: mov ax,stack
mov ss,ax
mov sp,128
mov ax,0b800h
mov es,ax
mov ah,'a'
s: mov es:[160*12+40*2],ah 设置显示位置
call delay 调用延时子程序
inc ah
cmp ah,'z' 字母到z结束
jna s 比较指令cmp和jna组合使用意思是不高于则转移
mov ax,4c00h
int 21h
delay: push ax 子程序开始
push dx
mov dx,1000h
mov ax,0
s1: sub ax,1
sbb dx,0
cmp ax,0
jne s1
cmp dx,0
jne s1
pop dx
pop ax
ret
现在字母已经在屏幕上显示出来了,接下来就是让按下esc键后改变颜色了。我们必须要对原来的9号中断例程进行修改,如果我们要写的中断处理程序称为新的int9中断例程,那么就必须修改中断向量表,但是在新中断例程中又要模拟原来中断例程的工作,所以我们要把原来int9中断例程的程序地址记录下来,在需要调用的时候找到该程序的入口。
有了原来int9的程序入口后,假设入口程序的偏移地址和段地址保存在ds:[0]和ds:[2]中,我们要模仿执行该中断处理程序,因为已经知道中断处理程序的入口,所以无需获得中断类型码,只需要执行下列工作:
1、标志寄存器入栈
2、IF=0,TF=0
3、CS和IP入栈
4、IP=ds*16+0,CS=ds*16+2
综上,调用中断例程的程序如下:
pushf 标志寄存器入栈
pushf
pop ax
and ah,11111100b IF=0,TF=0
push
ax
popf
call dword ptr ds:[0] 模拟调用子程序,相当于执行CS、IP入栈及其跳转
完整的程序如下:
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
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] 将原来的int9中断例程的入口地址保存在ds:0和ds:2中
mov word ptr es:[9*4],offset int9
mov es:[9*4+2],cs 将中断向量表中的int9中断例程的入口地址更新
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] 将改变的中断向量表恢复,否则其他程序将无法使用键盘
mov ax,4c00h
int 21h 程序返回
delay: push ax 延迟子程序
push dx
mov dx,1000h
mov ax,0
s1: sub ax,1
sbb dx,0
cmp ax,0
jne s1
cmp dx,0
jne s1
pop dx
pop ax
ret
int9: push ax
push bx
push es
in al,60h 接受60h端口的扫描码,将其存入al中
pushf
pushf
pop ax
and ah,11111100b
push
ax
popf
call dword ptr ds:[0] 调用原来的int9例程
cmp al,1
jne int9ret 不等于1即转移到int9ret,只有扫描码是1时才向下执行
mov ax,0b800h
mov es,ax
inc byte ptr es:[160*12+40*2+1] 改变颜色
int9ret:pop es
pop bx
pop bx
iret
code ends
end start
这个程序因为直接访问硬件,所以必须要在DOS模式下运行。