汇编语言中的外中断——键盘事件

时间:2022-10-27 01:19:27

汇编语言中的外中断和内中断差不多

1、取中断类型码n

2、标致寄存器入栈,IF=0,TF=0

2、CS、IP入栈

3、(IP)=(n*4),(CS)=(n*4+2)

但是外中断和内中断的中断类型码的来源不同,所以第一步肯定是不一样的,其他相同

下面来讲一下键盘事件下的处理方式

只要有键盘事件发生,就会触发int 9,这个int 9是硬件扫描过程,其实就是CPU对按键的IO口进行扫描的过程

这里问题就出现了,因为键盘事件发生必定会引发int 9,所以如果要对按按键后进行处理的话,需要将中断程序写在int 9中,但是如果需要扫描键盘的按键,就必须要调用BIOS提供的int 9

解决这种冲突的办法就是先将BIOS提供的int 9的IP和CS储存起来,用模拟int指令的方式来调用原来的int 9,当然新建的int 9需要将其入口的IP和CS放到向量表中

任务:显示a~z,按下ESC结束

代码:

assume cs:code

data segment ;储存原int9的IP和CS
dw 0, 0
data ends

code segment

start:
mov ax, 0 ;将原int9的IP和CS放到data空间中
mov ds, ax
mov ax, data
mov es, ax
push ds:[9*4]
pop es:[0]
push ds:[9*4 + 2]
pop es:[2]

mov ax, 0 ;将新的int9中断的IP,CS写入中断向量表中
mov ds, ax
mov word ptr ds:[9*4], offset do9h
mov word ptr ds:[9*4 + 2], cs

mov ax, 0B800H
mov ds, ax
mov al, 'a'
mov cl, 2
s:mov ds:[10*160 + 8*2], al
mov byte ptr ds:[10*160 + 8*2 + 1], cl

call delay ;调用延时的子程序,方便显示

cmp al, 'z'
je s0
inc al
jmp s

s0:mov al, 'a'
jmp s

send:mov ax, 0 ;将原int9中断的IP,CS恢复
mov ds, ax
mov ax, data
mov es, ax
push es:[0]
pop ds:[9*4]
push es:[2]
pop ds:[9*4 + 2]
mov ax, 4C00H
int 21H

do9h:push ax
push dx
push ds

in al, 60H ;从键盘端口读出键盘的输入
mov dl, al

pushf ;模仿int指令,进行原int9的调用

pushf ;将标致寄存器的IF和TF置零
pop ax
and ah, 11111100B
push ax
popf

mov ax, data;保存下一条指令的IP和CS
mov ds, ax
call dword ptr ds:[0]

cmp dl, 1 ;ESC的扫描码为1
jne ok
jmp send ;跳到循环外,不再执行循环指令

ok:pop ds
pop dx
pop ax
iret

delay:push dx
push ax

mov dx, 10H
mov ax, 0
s1:sub ax, 1 ;-1的反码是65535
sbb dx, 0 ;此时CF寄存器是1,则dx-1
cmp ax, 0 ;内循环
jne s1
cmp dx, 0 ;外循环
jne s1

pop ax
pop dx
ret

code ends
end start

运行结果:

汇编语言中的外中断——键盘事件


按下ESC键后的显示结果:

汇编语言中的外中断——键盘事件


为了避免在修改int 9的IP和CS时就发生外部中断,那么会让中断的地址混乱,所以可以改进一下程序

指令:cli:设置IF=0,屏蔽中断

            sti:设置IF=1,不屏蔽中断

将这两条指令分别加在修改入口地址的前后,那么就可以避免上面说的那种情况了

例:

cli                                 ;设置IF=0,屏蔽中断
mov ax, 0 ;将新的int9中断的IP,CS写入中断向量表中
mov ds, ax
mov word ptr ds:[9*4], offset do9h
mov word ptr ds:[9*4 + 2], cs
sti ;设置IF=1,不屏蔽中断

这样就避免了在修改IP,CS时发生外部中断


这里还有个问题,这里的jmp是有范围的,如果在程序中超出了jmp的范围,那么就不能完美的跳出程序了,为了解决这个问题,还需要模拟利用中断模拟jmp指令,让jmp指令的范围得到扩充

因为进入到外部中断时,会将CS和IP入栈,只要改变一下IP,在跳回程序后,就能跳到指定的位置

在中断中:

mov bp, sp                                  ;模拟jmp指令,跳出循环
mov word ptr [bp + 8], offset send

这样就能跳出循环了

完整的代码:

assume cs:code

data segment ;储存原int9的IP和CS
dw 0, 0
data ends

code segment

start:
mov ax, 0 ;将原int9的IP和CS放到data空间中
mov ds, ax
mov ax, data
mov es, ax
push ds:[9*4]
pop es:[0]
push ds:[9*4 + 2]
pop es:[2]

mov ax, 0 ;将新的int9中断的IP,CS写入中断向量表中
mov ds, ax
mov word ptr ds:[9*4], offset do9h
mov word ptr ds:[9*4 + 2], cs

mov ax, 0B800H
mov ds, ax
mov al, 'a'
mov cl, 2
s:mov ds:[10*160 + 8*2], al
mov byte ptr ds:[10*160 + 8*2 + 1], cl

call delay ;调用延时的子程序,方便显示

cmp al, 'z'
je s0
inc al
jmp s

s0:mov al, 'a'
jmp s

send:mov ax, 0 ;将原int9中断的IP,CS恢复
mov ds, ax
mov ax, data
mov es, ax
push es:[0]
pop ds:[9*4]
push es:[2]
pop ds:[9*4 + 2]
mov ax, 4C00H
int 21H

do9h:push ax
push dx
push ds

in al, 60H ;从键盘端口读出键盘的输入
mov dl, al

pushf ;模仿int指令,进行原int9的调用

pushf ;将标致寄存器的IF和TF置零
pop ax
and ah, 11111100B
push ax
popf

mov ax, data;保存下一条指令的IP和CS
mov ds, ax
call dword ptr ds:[0]

cmp dl, 1 ;ESC的扫描码为1
jne ok
mov bp, sp ;模拟jmp指令,跳出循环
mov word ptr [bp + 6], offset send

ok:pop ds
pop dx
pop ax
iret

delay:push dx
push ax

mov dx, 10H
mov ax, 0
s1:sub ax, 1 ;-1的反码是65535
sbb dx, 0 ;此时CF寄存器是1,则dx-1
cmp ax, 0 ;内循环
jne s1
cmp dx, 0 ;外循环
jne s1

pop ax
pop dx
ret

code ends
end start

效果是一样的