外中断
接口芯片和端口
在PC系统的接口卡和主板上,装有各种接口芯片。
这些外设接口芯片的内部有若干寄存器,CPU将这些寄存器当作端口来访问。
外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的端口中。
CPU向外设输出也不是直接送入外设,而是先送入端口中,再由相关的芯片送到外设。
CPU还可以向外设输出控制命令,而这些控制命令也是先送到相关芯片的端口中,然后再由相关的芯片根据命令对外设实施控制。
CPU通过端口和外部设备进行联系。
外中断信息
在PC系统中,外中断源一共有两类:
1) 可屏蔽中断
可屏蔽中断中CPU可以不响就的外中断。
IF=1,则CPU在执行完当前指令后响应中断,引发中断过程。
IF=0,则不响应可屏蔽中断。
IF位于FLAG第9位。
关于中断所引发的中断过程:
1) 取中断类型码N
2) 标志寄存器入栈 IF=0,TF=0
3) CS、IP入栈
4) (IP)=(N*4), (CS)=(N*4+2)
可屏蔽中断所引发的中断过程,除与第1步实现上有所不同外,基本和内中断的中断过程相同。
因为可屏蔽中断信息来自于CPU外部,中断类型是通过数据总线送入CPU的。
而内中断的中断类型码是在CPU内部产生的。
将IF置0的原因:在进入中断处理程序后,禁止其他的可屏蔽中断。
如果在中断处理程序中需要处理可屏蔽中断,可以用指令将IF置1:
8086CPU提供设置IF指令:
STI 用于设置IF=1 (允许响应中断)
CLI 用于设置IF=0 (不响应中断)
2) 不可屏蔽中断
不可屏蔽中断是CPU必须响应的外中断。
对于8086CPU,不可屏蔽中断的中断类型码固定为2。
所以中断过程中,不需要取中断类型码。
不可屏蔽中断的中断过程为:
1) 标志寄存器入栈 IF=0, TF=0;
2) CS、IP入栈
3) (IP)=8, (CS)=(0AH)
几乎所有由外设引发的外中断,都是可屏蔽中断。
PC机键盘的处理过程
键盘输入的处理过程:
1) 键盘产生扫描码
2) 扫描码送入60H端口
3) 引发9号中断
4) CPU执行INT9中断例程处理键盘输入
上面第1、2、3步均由硬件完成。
BIOS键盘缓冲区是系统启动后,BIOS用于存放INT9中断例程所接收键盘输入的内存区,可存储15个键盘输入。
INT9中断例程除了接收扫描码外,还要产生和扫描码对应的字符码,所以BIOS键盘缓冲区中,一个键盘输入用一个字单元存放。
高位字节存放扫描码,低位字节存放字符码。
0040:17 单元存储键盘状态字节,该字节记录了控制键和切换键的状态。
显示从 a ~~ z :
assume cs:code
code segment
start: mov ax,0b800h
mov es,ax
mov ah,'a'
s: mov es:,ah
inc ah
cmp ah,'z'
jna s
mov ax,4c00h
int 21h
code ends
end start
简单延时-循环
assume cs:code
code segment
start: mov dx,0afffh
mov ax,0ffffh
s: sub ax,1
sbb dx,0
cmp ax,0
jne s
cmp dx,0
jne s
mov ax,4c00h
int 21h
code ends
end start
显示从 a ~~ z 字母,每隔一定时间显示下一个字母
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,0b800h
mov es,ax
mov ah,'a'
s: mov es:,ah
call delay
inc ah
cmp ah,'z'
jna s
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
code ends
end start
键盘输入到达60H端口后,就会引发9号中断,CPU则转去执行INT9中断例程。要求编写INT9中断例程,功能如:
1) 从60H端口读出键盘的输入
2) 调用BIOS的INT9中断例程,处理其他硬件细节
3) 判断是否为ESC键扫描码,如果是,改变显示的颜色后返回;
如果不是则直接返回。
编写自己的INT9中断例程,但原INT9有一些复杂的硬件细节处理,所以在我们自己的INT9中断例程中还要调用原INT9。
所以,在中断向量表内将INT9中断例程入口地址改写为我们自己的中断例程前,需要先保存原INT9。
当保存原INT9中断例程的偏移地址和段地址后,再调用它时就不能使用INT9来调用了。需要用别的指令来对INT指令进行模拟。
在用其它指令对INT指令进行模拟前,需要先看一下INT指令在执行的时候CPU是如何工作的:
1) 取中断类型码 N
2) 标志寄存器入栈
3) IF=0、 TF=0
4) CS、IP入栈
5) (IP)=(N*4), (CS)=(N*4+2)
取中断类型码是为了定位中断例程的入口地址。
但是,当我们将原INT9中断入口地址保存到另一个内存区域时,我们就不需要中断类型码了,因为我们知道那块自己指定的在内存在哪。
假设已将原INT9中断例程的入口地址保存到了 ds:0 与 ds:2 单元中。现在模拟int过程:
1) 标置寄存器入栈
2) IF=0、TF=0
3) CS、IP入栈
4) (IP)=((DS)*16+0) , (CS)=((CS)*16+2)
上面第3步是CS、IP入栈,因为如果不入栈调用完中断或是子程序就回不到调用者了。
不过,如果是引发INT中断例程的话,CPU是会帮我们做保存CS与IP(入栈)的,所以不用我们自己来。
可是,现在不是要调用INT中断例程,因为原INT9已被我们备份到另一个地址(它的入口地址已经不在中断向量表项中了)。所以我们如果需要调用INT9(就是要执行它,然后再返回到调用者处)就必须使用call命令。
而CALL命令有一个特点:执行CALL命令时,IP指向CALL命令下一条指令(为的是将来返回调用者继续执行CALL后面的指令)。如果CALL是段内CALL,则CPU将IP压栈(不用我们压,这个过程是CPU自动完成的);如果CALL是段间(即:CS:IP)方式,则CPU会自动压入CS、IP(都要压入,不然回不来了)。
所以,上面的CALL的过程和INT调用过程在压入CS、IP入栈的过程是一模一样的。
CALL与调用中断例程不一样的地方是:调用中断例程时CPU自动先压标志寄存器,然后……
而CALL执行时CPU压IP或是CS:IP,但它并不压标志寄存器入栈。
所以如果想用CALL来模拟调用中断例程的方式使用存在另一块内存地址的 DS:0 和 DS:2 的原INT9中断例程,
则需要我们:
(1) 自己压入标志寄存器
(2) 自己设置IF=0,TF=0(这些得自己做,CALL指令不会帮我们做的,只有INT调用CPU才会帮我们做)
(3) 使用 call dword ptr ds: 的方式(4字)调用原INT9入口地址,执行原INT9例程。
如何将标志寄存器入栈?
pushf指令可以实现。
如何将标志寄存器的IF与TF的值设置为0?
IF 标志寄存器的第9位
TF 标志寄存器的第8位
关于IF与TF的含义:
IF 表示可屏蔽中断
TF 表示单步中断
为什么要禁止单步中断?
当TF=1(允许单步中断)时,CPU在执行完一条指令后将此发单步中断,转去执行中断处理程序。
而中断处理程序也是由一条条指令组成,如果在执行中断处理程序之前,TF=1,则CPU在执行完中断处理程序的第一条指令后又要产生单步中断,则又要转去执行单步中断的中断处理程序,在执行完中断处理程序的第一条指令后,又要产生单步中断,则又要转去执行单步中断的中断处理程序……无穷无尽的下去……
所以,有时候必须将TF=0(设置为禁卡单步中断),这样才能正确的连续的执行需要的过程。
不可屏蔽的中断类型码固定为:2
中断过程中不需要取中断类型码。不可屏蔽中断过程的寻址部分:
(IP)=(8), (CS)=(0AH)
其实上面的计算过程如下原理:
(IP)=(2*4)
(CS)=(2*4+2)
不可屏蔽中断是在系统中有必须处理的紧急情况发生时用来通知CPU的中断信息。
编程:在屏幕中间依次显示 a~~z ,延时处理显示在屏幕上。
在显示过程中,按下 Esc 键后,改变显示颜色。
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: ; 备份原int 9中断例程入口地址
pop ds:
push es:
pop ds:
mov word ptr es:,offset int9
mov es:,cs
mov ax,0b800h ; 显示从 'a'~'z' 字母
mov es,ax
mov ah,'a'
s: mov es:,ah
call delay ; 延时
inc ah
cmp ah,'z'
jna s
mov ax,0
mov es,ax
push ds: ; 恢复原int9中断向量表项中的入口地址
pop es:
push ds:
pop es:
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 ; 当 ax 与 dx 值都为0时才算递减尽
pop dx
pop ax
ret
int9: push ax ; ------------------------------
push bx ; 自定义 int 9 中断例程
push es ; ------------------------------
in al,60h
pushf ; 备份标志寄存器的值
pushf ; 压入标志寄存器用来修改IF、TF值
pop bx
and bh,11111100b
push bx
popf ; 弹出修改好的flag值,修改当前标志寄存器值
call dword ptr ds:
cmp al,1 ; 比对是否按了 Esc 键 (Esc 键扫描码: 01 )
jne int9ret
mov ax,0b800h
mov es,ax
inc byte ptr es: ; 改变颜色
int9ret: pop es
pop bx
pop ax
iret
code ends
end start
) 分析上面int9中断例程,精简指令
进入中断例程后,IF和TF都已经置0。
pushf
pushf
pop ax
and ah,11111100b
push ax
popf
call dword ptr ds:
上面批令可以精简为:
pushf
call dword ptr ds:
精减后的全部代码如下:
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: ; 备份原int 9中断例程入口地址
pop ds:
push es:
pop ds:
mov word ptr es:,offset int9
mov es:,cs
mov ax,0b800h ; 显示从 'a'~'z' 字母
mov es,ax
mov ah,'a'
s: mov es:,ah
call delay ; 延时
inc ah
cmp ah,'z'
jna s
mov ax,0
mov es,ax
push ds: ; 恢复原int9中断向量表项中的入口地址
pop es:
push ds:
pop es:
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 ; 当 ax 与 dx 值都为0时才算递减尽
pop dx
pop ax
ret
int9: push ax ; ------------------------------
push bx ; 自定义 int 9 中断例程
push es ; ------------------------------
in al,60h
pushf ; 备份标志寄存器的值
call dword ptr ds:
cmp al,1 ; 比对是否按了 Esc 键 (Esc 键扫描码: 01 )
jne int9ret
mov ax,0b800h
mov es,ax
inc byte ptr es: ; 改变颜色
int9ret: pop es
pop bx
pop ax
iret
code ends
end start
) 分析上面主程序
在主程序中,如果执行设置INT9中断例程的段地址和篇移地址的指令之间发生了键盘中断,
则CPU将转去一个错误的地址并执行,将发生错误。
排除潜在错误的完全代码:
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
cli ; 不响应可屏蔽中断
push es: ; 备份原int 9中断例程入口地址
pop ds:
push es:
pop ds:
mov word ptr es:,offset int9
mov es:,cs
sti ; 响应可屏幕中断
mov ax,0b800h ; 显示从 'a'~'z' 字母
mov es,ax
mov ah,'a'
s: mov es:,ah
call delay ; 延时
inc ah
cmp ah,'z'
jna s
mov ax,0
mov es,ax
cli
push ds: ; 恢复原int9中断向量表项中的入口地址
pop es:
push ds:
pop es:
sti
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 ; 当 ax 与 dx 值都为0时才算递减尽
pop dx
pop ax
ret
int9: push ax ; ------------------------------
push bx ; 自定义 int 9 中断例程
push es ; ------------------------------
in al,60h
pushf ; 备份标志寄存器的值
call dword ptr ds:
cmp al,1 ; 比对是否按了 Esc 键 (Esc 键扫描码: 01 )
jne int9ret
mov ax,0b800h
mov es,ax
inc byte ptr es: ; 改变颜色
int9ret: pop es
pop bx
pop ax
iret
code ends
end start
编程:安装新的 int 9 中断例程,使得原 int 9 中断例程的功能得到扩展。
在DOS下,按F1键后改变当前屏幕的显示颜色,其他的键照常处理。
assume cs:code
stack segment
db 128 dup (0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
push cs
pop ds
mov ax,0
mov es,ax
mov si,offset int9
mov di,204h
mov cx,offset int9end - offset int9
cld
rep movsb
push es:
pop es:
push es:
pop es:
cli
mov word ptr es:,204h
mov word ptr es:,0
sti
mov ax,4c00h
int 21h
int9: push ax
push bx
push cx
push es
in al,60h
pushf
call dword ptr cs:
cmp al,3bh
jne int9ret
mov ax,0b800h
mov es,ax
mov bx,1
mov cx,2000
s: inc byte ptr es:
add bx,2
loop s
int9ret: pop es
pop cx
pop bx
pop ax
iret
int9end: nop
code ends
end start
在 Windows CMD 下调试先运行安装程序。然后进入一个16位程序界面(DEBUG、EDIT等),然后在里面按F1键有效。
此代码正常运行环境: MS-DOS
编程:安装新的 int 9 中断例程
在DOS下,按下 “A” 键后,除非不再松开,如果松开,就显示满屏幕 “A” ,其他键照常处理。
按下一个键时产生的扫描码称为
通码。
松开一个键时产生的扫描码称为
断码。
断码=通码+80H
assume cs:code
stack segment
db 128 dup (0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
push cs
pop ds
mov si,offset int9
mov ax,0
mov es,ax
mov di,204h
mov cx,offset int9end - offset int9
cld
rep movsb
push es:
pop es:
push es:
pop es:
cli
mov word ptr es:,204h
mov word ptr es:,0
sti
mov ax,4c00h
int 21h
int9: push ax
push bx
push cx
push es
in al,60h
pushf
call dword ptr cs:
add al,80h
cmp al,1eh
jne int9iret
mov ax,0b800h
mov es,ax
mov bx,0
mov cx,2000
s: mov byte ptr es:,'A'
add bx,2
loop s
int9iret: pop es
pop cx
pop bx
pop ax
iret
int9end: nop
code ends
end start
上面的代码原理从个人理解角度讲,已经可以做好玩的东东了~:)
指令系统总结
1 数据传送指令
2 算术运算指令
3 逻辑指令
4 转移指令
5 处理机控制指令
6 串处理指令
[
Last edited by redtek on 2007-1-13 at 11:06 PM ]