汇编语言编写int 9中断例程

上面的过程中,第1, 2, 3步都是由硬件系统完成的。我们能够改变的只有int 9中断处理程序。我们可以重新编写int 9中断例程,按照自己的意图来处理键盘的输入。但是,在课程中,我们不准备完整地编写一个键盘中断的处理程序,因为要涉及一些硬件细节,而这些内容脱离了我们的内容主线。
但是,我们却还要编写新的键盘中断处理程序,来进行一些特殊的工作,那么这些硬件细节如何处理呢?这点比较简单,因为BIOS提供的int 9中断例程已经对这些硬件细节进行了处理。我们只要在自己编写的中断例程中调用BIOS的int 9中断例程就可以了。
编程:
在屏幕中间依次显示“a”-“z”,并可以让人看清。在显示的过程中,按下Esc键后,改变显示的颜色。
我们先来看一下如何依次显示“a”-“z

assume cs:code
data segment	
data ends
code segment 
        mov ax , 0b800h
        mov es , ax
        mov ah , 'a'
s:     mov es:[160*12+40*2],ah
       inc ah
       cmp ah,'z'
       jna s
       mov ax,4c00hg
       int 21h
code ends
end strt

在上面的程序的执行过程中,我们无法看清屏幕上的显示。因为一个字母刚显示到屏幕上,CPU执行几条指令后,就又变成了另一个字母,字母之间切换得太快,无法看清。
应该在每显示一个字母后,延时一段时间,让人看清后,再显示下一个字母。那么如何延时呢?我们让CPU执行一段时间的空循环。因为现在CPU的速度都非常快,所以循环的次数一定要大,用两个16位寄存器来存放32位的循环次数。如下:

    mov dx,10h
    mov ax,0
s:  sub ax,1
    sbb dx,0
    cmp ax,0
    jne s
    cmp dx,0
    jne s

上面的程序,循环100000h次。我们可以将循环延时的程序段写为一个子程序。
现在,我们的程序如下:

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'
      jna s

       mov ax,4c00hg
       int 21h

delay: push ax
       push dx
       mov dx, 1000h              ;循环10000000h次
		mov ax, 0		;dx作高位,ax作低位,结合下面的循环,就会执行10000000h次
s1:		sub ax, 1
		sbb dx, 0
		cmp ax, 0
		jne s1
		cmp dx, 0
		jne s1
		pop ax
		pop dx
		ret
code ends
end strt

显示“a”-“z",并可以让人看清,这个任务己经实现。那么如何实现,按下Esc,键后,改变显示的颜色呢?
键盘输入到达60h端口后,就会引发9号中断,CPU则转去执行int 9中断例程。我
们可以编写int 9中断例程,功能如下。
(1)从60h端口读出键盘的输入;
(2)调用BIOS的int 9中断例程,处理其他硬件细节;
(3)判断是否为Esc的扫描码,如果是,改变显示的颜色后返回;如果不是则直接
返回。
下面对这些功能的实现一一进行分析。
1 从端口60h读出键盘的输入

in al,60h

2.调用sios的int 9中断例程
有一点要注意的是,我们写的中断处理程序要成为新的int 9中断例程,主程序必须要将中断向量表中的int 9中断例程的入口地址改为我们写的中断处理程序的入口地址。
则在新的中断处理程序中调用原来的int 9中断例程时,中断向量表中的int 9中断例程的入口地址却不是原来的int 9中断例程的地址。所以不能使用int指令直接调用。
要能在我们写的新中断例程中调用原来的中断例程,就必须在将中断向量表中的中断例程的入口地址改为新地址之前,将原来的入口地址保存起来。这样,在需要调用的时候,我们才能找到原来的中断例程的入口。
对于我们现在的问题,假设将原来int 9中断例程的偏移地址和段地址保存在ds:[0]和ds:[2]单元中。那么我们在需要调用原来的int 9中断例程时候,就可以在ds: [0] , ds: [2]单元中找到它的入口地址。
那么,有了入口地址后,如何进行调用呢?
当然不能使用指令int 9来调用。我们可以用别的指令来对int指令进行一些模拟,从而实现对中断例程的调用。
我们来看,int指令在执行的时候,CPU进行下面的工作
(1)取中断类型码n;
(2)标志寄存器入栈:
(3)IF=0, TF=0;
(4)CS, IP入栈;
(5)(IP)=(n*4), (CS)=(n*4+2)
(6)(IP)=(n*4), (CS)=(n*4+2)
取中断类型码是为了定位中断例程的入口地址,在我们的问题中,中断例程的入口地址己经知道。所以,我们用别的指令模拟int指令时候,不需要做第(1)步。在假设要调用的中断例程的入口地址在ds:0和ds:2单元中的前提下,我们将int过程用下面几步模拟。
(1)标志寄存器入栈:
(2)IF=0, TF=0;
(3)CS, IP入栈;
(4)(IP)=((ds)*16+0), (CS)=((ds)*16+2)
可以注意到第(3), (4)步和call dword ptr ds:[0]的功能一样,call dword ptr ds: [O]的功能也是:
(1)CS, IP入栈;
(4)(IP)=((ds)*16+0), (CS)=((ds)*16+2)
所以int过程的模拟过程变为:
(1)标志寄存器入栈:
(2)IF=0, TF=0;
(3)call dword ptr ds: [O]
对于(1),可用pushf实现;
对于(2),可用下面的指令实现:

pushf 
pop ax
and ah,11111100b   ;iF和TF为标志寄存器的第9位和第8位

push ax
popf

则模拟int指令的调用功能,调用入口地址在ds:0, ds:2中的中断例程的程序为:

pushf   ;标志寄存器

pushf 
pop ax
and ah,11111100b   ;iF和TF为标志寄存器的第9位和第8位

push ax
popf               ;if=0 tf=0
call dword ptr ds: [O];CS,IP入栈 :(IP)=((ds)*16+0), (CS)=((ds)*16+2)

3.如果是Esc的扫描码,改变显示的颜色后返回
如何改变显示的颜色?
显示的位置是屏幕的中间,即第12行40列,显存中的偏移地址为:160*12+40*2所以字符的ASCII码要送入段地址b800h,偏移地址160*12+40*2处。而段地址b800h,偏移地址160*12+40*2+1处是字符的属性,只要改变此处的数据就可以改变在段地址b800h,偏移地址160*12+40*2处显示的字符的颜色了。
该程序的最后一个问题是,要在程序返回前,将中断向量表中的int 9中断例程的入口地址恢复为原来的地址。否则程序返回后,别的程序将无法使用键盘。
经过分析,完整的程序如下

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,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]

        mov word ptr es:[9*4],offset int9
        mov es:[9*4+2],cs
  
        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              ;循环10000000h次
		mov ax, 0		;dx作高位,ax作低位,结合下面的循环,就会执行10000000h次
s1:		sub ax, 1
		sbb dx, 0
		cmp ax, 0
		jne s1
		cmp dx, 0
		jne s1
		pop ax
		pop dx
		ret
;-----------------------以下为新的int 9中断例程--------------------
int9:   push ax
        push bx
        push cx
        push es

        in al , 60h

        pushf
        pushf
        pop bx
        and bh,11111100b
        push bx
        popf
        call dword prs ds:[0] ;对int指令进行模拟
      
        cmp al,1
        jne int9ret
          
        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实模式下运行。在Windows 2000的DOS方式下运行,会出现一些和硬件工作原理不符合的现象。


发布日期:

所属分类: 易语言 标签: