• 汇编15:外中断


    外中断

    可屏蔽中断与不可屏蔽中断

    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模式下运行。

  • 相关阅读:
    20150603_Andriod 多个窗体数据回调
    onActivityResult传值的使用
    20150602_Andriod 向窗体传递参数
    20150601_Andriod 打开新窗体
    C# 添加.DLL 出错的解决方法
    c# 中crystal report输出PDF文件
    参考_Android中,如何新建一个界面,并且实现从当前界面切换到到刚才新建的(另外一个)界面
    andriod 新建 Activity_ Form (详细设置)
    sql in
    如何取得GridView被隐藏列的值
  • 原文地址:https://www.cnblogs.com/yinyunmoyi/p/12811591.html
Copyright © 2020-2023  润新知