汇编语言笔记15外中断
汇编语言笔记15外中断
0x00 外中断简介
在计算机系统中,cpu除了能够执行指令进行运算外,还能够对外部设备进行控制,接受他们的输入。然而外部设备的输入随时可能会发生,所以由外中断来处理
外设的输入不直接送入内存和cpu,而是送入相关接口芯片的端口中(实际是芯片上的寄存器);cpu向外设的输出也不是直接送入外设,而是先送入端口中,再由相关芯片送到外设。同样cpu向外设输出的控制命令也是先送到相关芯片的端口中,再由芯片根据命令对外设实施控制。
外中断信息
cpu外部有需要处理的事情发生时,比如外设的输入到达,相关芯片将向cpu发出相应的中断信息。cpu在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入。
外中断分为两类:(1)可屏蔽中断:cpu可以不响应的外中断。是否响应由标志寄存器IF决定(DF 决定串传送 di si 递增还是递减 TF决定的是单步中断,别搞混了),如果IF为1,则响应可屏蔽中断,否则不响应可屏蔽中断
8086cpu可以用sti 置IF=1 cli 置IF=0 (clear if set if)
(2)不可屏蔽中断:cpu必须响应的外中断,终端类型码为2
外中断的中断过程和内中断一样:
(1)标志寄存器flag入栈 pushf
(2)IF=0 TF=0
(3)cs ip 入栈
(4)ip=4 * n cs=4 * n +2(n为中断类型码)
几乎所有由外设引发的中断都是可屏蔽中断
0x01 pc机键盘的处理过程
1 键盘输入
键盘上的每一个键相当于一个开关,按下键,开关导通,产生一个扫描码,扫描码标识了按下的键的位置,扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60h
松开按键时,也产生一个扫描码,同样送入60h端口 ,按下产生的扫面码称为通码,松开称为断码。
通码的第7位为0,断码为1:即断码=通码+80h
2 引发9号中断
键盘的输入到达60h端口时,相关的芯片就会向cpu发出中断类型码为9的可屏蔽中断信息。cpu检测到该中断信息后,如果IF=1,则响应中断,引发中断过程,执行int9的中断例程
3 执行int9中断例程
BIOS提供了int 9中断例程,用来经行基本的键盘输入输出处理,主要工作如下:
(1)读出60h端口中的扫描码
(2)如果时字符串的扫描码,将扫描码和它对应的ascill码送入内存中的BIOS键盘缓冲区;如果时控制键和切换键(比如crl shift)的扫描码,则将其转换为状态字节(用一个固定内存字节单元的各个位来表示控制键和切换键的状态,类似flag的做法)写入内存中储存状态字节的单元
(3)对键盘系统进行相关的控制,比如向相关芯片发送应答信息。
BIOS键盘缓冲区是系统启动后,BIOS用于存放int9中断例程所接受的键盘输入的内存区。该内存区可以存储15个键盘输入,键盘缓冲区中,一个字 的高字节存放扫描码,低字节存放对应的ascill码
而键盘状态字节时0040:17,该字节记录了控制键和切换键的状态。各位代表的信息如下:
0x02 编写int9中断
1键盘产生扫描码 2扫描码送入60h端口 3 引发9号中断 4cpu执行9号中断例程处理键盘输入 前三个过程我们都无法改变,只能改变int9中断处理程序。
我们不用完整地编写一个键盘中断的处理程序,因为要涉及一些硬件细节。但是我们可以自己写一个新的int9 做一些自己的事情,然后调用BIOS提供的int9中断处理例程
assume cs:code
code segment
start: mov ax,0b800h
mov es,ax
mov ah,'a'
s: mov es:[2000],ah
inc ah
cmp ah,'z'
jna s
mov ax,4c00h
int 21h
code ends
end start
上述代码由于cpu高速运转,无法看清字母切换,因此直接显示‘z’,那么我们可以延时一段时间再切换,怎么延时呢?用两个16位寄存器来存放32位的循环次数:
assume cs:code
code segment
start: mov ax,0b800h
mov es,ax
mov ah,'a'
s: mov es:[2000],ah
call sleep
inc ah
cmp ah,'z'
jna s
mov ax,4c00h
int 21h
sleep: push ax
push bx
mov ax,10h
mov bx,0
s1: sub bx,1
sbb ax,0;巧妙的用sbb ax,0运算让每当 sub bx,1运行65536次 ax才减1
cmp bx,0
jne s1
cmp ax,0
jne s1
pop bx
pop ax
ret
code ends
end start
成功达到了延时的目的,再就是实现按下Esc键改变显示的颜色,中断处理程序如下:
1从60h端口读出键盘的输入 2调用BIOS的int9中断例程,处理其他硬件细节 3 判断是否为Esc的扫面码,如果是,改变显示的颜色后返回;如果不是,则直接返回
针对2调用BIOS的int9中断,也就是我们在修改中断向量表之前要保存原来的int9的中断向量,在我们的中断处理程序中模拟中断的方式,(不能直接用int 9来调用,int 9被我们新的中断向量替代了)调用原来的中断处理程序,最后我们还要把int9的中断向量改回来。
如何模拟int 调用原来中断呢:
我们要把这个过程实现了 也就是:
pushf;标志寄存器入栈
;后面这一串操作都是为了修改标志寄存器
pushf;再次入栈为了复制一份拿出来改
pop ax;拿出来
and ah,11111100b;IF TF=0;改
push ax
popf;改好入栈出栈送入标志寄存器
完整代码如下:
assume cs:code,ds:data,ss:stack
data segment
db 16 dup (0)
data ends
stack segment
db 128 dup (0)
stack ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov ax,0
mov es,ax
mov ax,es:[4*9]
mov ds:[0],ax
mov ax,es:[4*9+2]
mov ds:[2],ax;保存到ds
mov word ptr es:[4*9],offset do9
mov ax,cs
mov word ptr es:[4*9+2],ax;安装do9
mov ax,0b800h
mov es,ax
mov ah,'a'
s: mov es:[2000],ah
call sleep
inc ah
cmp ah,'z'
jna s
mov ax,0
mov es,ax
mov ax,ds:[0]
mov es:[4*9],ax
mov ax,ds:[2]
mov es:[4*9+2],ax
mov ax,4c00h
int 21h
sleep: push ax
push bx
mov ax,5h
mov bx,0
s1: sub bx,1
sbb ax,0;巧妙的用sbb ax,0运算让每当 sub bx,1运行65536次 ax才减1
cmp bx,0
jne s1
cmp ax,0
jne s1
pop bx
pop ax
ret
do9: push ax
push bx
push es
in al,60h
pushf
pushf
pop bx
and bh,11111100b
push bx
popf
call dword ptr ds:[0];模拟int 调用原来的中断
cmp al,1
jne do9ret
mov ax,0b800h
mov es,ax
inc byte ptr es:[2001];修改颜色,每次按下+1
do9ret: pop es
pop bx
pop ax
iret
code ends
end start
上述代码运行正确,上述代码没有把do9 放到 0:200h这里
由于我们在自己的中断处理程序调用原来的中断处理程序,在进入我们的中断时,已经设置了TF IF=0所以 不用在调用原中断时又设置,所以简化为 pushf 和 call dword ptr ds:[0]
我们是依次修改9号中段的中断向量的偏移地址和段地址,如果修改完偏移地址时即发生中断,那么将会跑飞(很危险),所以我们在修改中断向量的偏移地址之前应该屏蔽中断 修改完段地址后
再解除屏蔽,也就是 先cli 指令置IF为0 再 sti 指令置IF为1
0x03 安装新的int9中断
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset do9
mov ax,0
mov es,ax
mov di,204h;准备把200-203h四个字节用来储存原来的int9中断向量204用来保存当前颜色
mov cx,offset do9e-offset do9
cld
rep movsb
mov ax,es:[9*4]
mov es:[200h],ax
mov ax,es:[9*4+2]
mov es:[202h],ax;把原来的中断向量写到0:200
cli;屏蔽外设中断;
mov word ptr es:[9*4],204h
mov word ptr es:[9*4+2],0
sti;安装完成
mov ax,4c00h
int 21h
do9: push ax
push es
push bx
push cx
mov ax,0
mov es,ax
in al,60h
pushf
call dword ptr es:[200h]
cmp al,3bh ;f1
jne fin;不等于就跳到结束
mov ax,0b800h
mov es,ax
mov bx,1
mov cx,2000;一页2000字符80 25
s: inc byte ptr es:[bx]
add bx,2
loop s
fin: pop cx
pop bx
pop es
pop ax
iret
do9e: nop
code ends
end start
上面是我的代码运行就挂了,下面是书上给的参考代码,晚上再分析(还是头一次写错了代码)
//2020-6-8晚修正,上述代码已经修改正确 ,运行无误 ,错误之处在于,1串传送指令rep movsv 前没有设置flag DF位(应该用cld设置位0 di si同递增)2do9中调用原中断处理程序的200h写成了200 3中断的返回应该用iret 返回到之前的现场而不是 mov ax,4c00h int 21 程序返回
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:[9*4]
pop es:[200h]
push es:[9*4+2]
pop es:[202h]
cli
mov word ptr es:[9*4],204h
mov word ptr es:[9*4+2],0
sti
mov ax,4c00h
int 21h
int9: push ax
push bx
push cx
push es
in al,60h
pushf
call dword ptr cs:[200h]
cmp al,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