zoukankan      html  css  js  c++  java
  • 第15章 外中断

    引言

    以前我们讨论的都是cpu对指令的执行。我们知道,cpu在计算机系统中,除了能够执行指令,进行运算以外,还应该能够对外部设备进行控制,接受他们的输入,向他们进行输出。

    也就是说,cpu除了有运算能力外,还要有I/O能力。

    15.1接口芯片和端口

    第14章我们讲过,在pc系统的接口卡和主板上,装有各种接口芯片。这些外设接口芯片的内部有若干寄存器,cpu将这些寄存器当作端口来访问。

    外设的输入不直接送入内存和cpu,而是送入相关的接口芯片的端口中。

    cpu向外设的输出也不是直接送入外设,而是先送入端口中,再由相关的芯片送到外设。

    cpu还可以向外设输出控制命令,而这些控制命令也是先送到相关芯片的端口中,然后再由相关的芯片根据命令对外设实施控制。

    可见,cpu通过端口和外部设备进行联系。

    cpu在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入。

    在pc系统中,外中断源一共有两类:

    1、可屏蔽中断

    2、不可屏蔽中断

    可屏蔽中断是cpu可以不响应的外中断。cpu是否相应可屏蔽中断,要看标志寄存器的IF位的设置。

    当cpu检测到可屏蔽中断信息时:

    *如果IF=1,则cpu在执行完当前指令后响应中断,引发中断过程。

    如果IF=0,则不响应可屏蔽中断。

    我们回忆一下内中断所引发的中断过程:

    1)取中断类型码n

    2)标志寄存器入栈,if=0,TF=0

    3)cs、ip入栈

    4)ip=n*4,cs=n*4+2

    由此转去执行中断处理程序。

    可屏蔽中断所引发的中断过程,除在第一步的实现上有所不同外,基本上和内中断的中断过程相同。

    因为可屏蔽中断信息来自于cpu外部,中断类型码是通过数据总线送入cpu的。

    而内中断的中断类型码是在cpu内部产生的。

    现在,我们可以解释中断过程中将IF置为0的原因了,将IF置0的原因就是,在进行中断处理程序后,禁止其他的可屏蔽中断。

    当然,如果在中断处理程序中需要处理可屏蔽中断,可以用指令将IF置1.

    8086cpu提供的设置IF的指令如下:

    sti  ,用于设置IF=1

    cli   ,用于设置IF=0.

    不可屏蔽中断是cpu必须响应的外中断。当cpu检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程。

    对于8086cpu不可屏蔽中断的中断类型码固定为2.所以中断过程中,不需要取中断类型码。

    不可屏蔽中断的中断过程:

    1)标志寄存器入栈,if=0,tf=0

    2)cs、ip入栈

    3)ip=8、cs=0aH

    几乎所有由外设引发的外中断,都是可屏蔽中断。当外设有需要处理的事情(比如说键盘输入)发生时,相关芯片向cpu发出可屏蔽终端信息。

    不可屏蔽中断是在系统中有必须处理的紧急情况发生时用来通知cpu的中断信息。

    15.3 pc机键盘的处理过程

    下面我们看一下键盘输入的处理过程,并以此来体会一下pc机处理外设输入的基本方法。

    1、键盘输入

    2、引发9号中断

    3、执行int 9  中断例程

    键盘上的每一个键相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描。

    按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码说明了按下的键在键盘上的位置。扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60h

    松开按下的键时,也产生一个扫描码,扫描码说明了松开的键在键盘上的位置。松开按键时产生的扫描码也被送入60h端口中。

    一般将按下一个键时产生的扫描码为通码,松开一个键产生的扫描码为断码。

    扫描码长度为1个字节,通码的第7位为0,断码的第7位为1,即:

    断码=通码+80h

    键盘的输入到达60h端口时,相关的芯片就会向cpu发出中断类型码为9的可屏蔽中断信息。

    cpu检测到该中断信息后,如果IF=1,则响应中断,引发中断过程,转去执行int  9中断例程。

    BIOS提供了int 9中断例程,用来进行基本的键盘输入处理,主要的工作如下:

    1)读出60h端口中的扫描码

    2)如果是字符键的扫描码,将该扫描码和它所对应的字符码(即ASCII码)送入内存中的BIOS键盘缓冲区:

    如果是控制键(比如ctrl)和切换键(比如CapsLock)的扫描码,则将其转变为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储状态字节的单元。

    3)对键盘系统进行相关的控制,比如说,向相关芯片发出应答信息。

    15.3 PC机键盘的处理过程

    BIOS键盘缓冲区是系统启动后,BIOS用于存放int 9中断例程所接收的键盘输入的内存区。

    该内存区可以存储15个键盘输入,因为int 9中断例程除了接受扫描码外,还要产生和扫描码对应的字符码,所以在BIOS键盘缓冲区中,一个键盘输入用一个字节存放,高位字节存放扫描码,低位字节存放字符码。

    0040:17单元存储键盘状态字节,该字节记录了控制键和切换键的状态。键盘状态字节各位记录的信息如下:

     从上面的内容中,我们可以看出键盘输入的处理过程:

    1)键盘产生扫描码

    2)扫描码送入60h端口

    3)引发9号中断

    4)cpu执行int  9中断例程处理键盘输入。

    上面的过程中,第1、2、3步都是由硬件系统完成的。我们能够改变的只有int  9中断处理程序。

    我们可以重新编写int 9中断例程,按照自己的意图来处理键盘的输入。

    但是,在课程中,我们不准备完整地编写一个键盘中断地处理程序,因为要涉及到一些硬件细节,而这些内容脱离了我们地内容主线。

    但是,我们却还要编写新的键盘中断处理程序,来进行一些特殊地工作,那么这些硬件细节如何处理呢?

    这点比较简单,因为BIOS提供地int  9中断例程已经对这些硬件细节进行了处理。

    我们只要在自己编写地中断例程中调用BIOS的int 9中断例程就可以了。

     

    在上面的程序的执行过程中我们无法看清屏幕上的显示。

    因为一个字母刚显示到屏幕上,cpu执行几条指令后,就又变成了另一个字母,字母之间切换得太快,无法看清。

    此时,我们让cpu执行一段时间得空循环,这样就能延时一段时间。

     显示a~z,并可以让人看清,这个任务已经实现。

    那么如何实现,按下Esc键后,改变显示的颜色呢?

    键盘输入到达60h端口后,就会引发9号中断,cpu则转去执行int 9中断例程。

    我们可以编写int 9中断例程,功能如下:

    1)从60h端口读出键盘的输入

    2)调用BIOS的int9中断例程,处理其他硬件细节

    3)判断是否为Esc的扫描码,如果是,改变显示的颜色后返回,如果不是则直接返回。

    下面一一实现

    1、从端口60h读出键盘的输入。

    in  al,60h

    2、调用BIOS的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

    取中断类型码是为了定位中断例程的入口地址,在我们的问题中,中断例程的入口地址已经知道。

    所以,我们用别的指令模拟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:[0]的功能也是:

    1)cs、ip入栈

    2)ip=ds*16+0

       cs=ds*16+2

    所以int 过程的模拟过程变为:

    1)标志寄存器入栈

    2)IF=0,TF=0

    3)call  dword  ptr  ds:[0]

    对于1)可用pushf实现

    对于2),可用下面的指令实现

    pushf

    pop ax

    and ah,11111100b

    push ax

    pop f

    这样模拟int指令的调用功能,调用入口地址在ds:0、ds:2中的中断例程的程序如下

    pushf

     

    pushf

    pop  ax

    and ah,11111100b

    push ax

    popf 

    call dword ptr ds:[0]

    3、如果是Esc键的扫描码,改变显示的颜色后返回

    如何改变显示的颜色

    显示的位置是屏幕的中间,即第12行40列,显存中的偏移地址为:160*12+40*2

    所以字符的ASCII码要送入b800:160*12+40*2处。

    而b800:160*12+40*2+1处是字符的属性,我们只要改变此处的数据就可以该百年在b800:160*12+40*2处显示的字符的颜色了。

    该程序的最后一个问题是,要在程序返回前,将中断向量表中的int  9中断例程的入口地址恢复为原来的地址。否则程序返回后,别的程序将无法使用键盘。

    完整代码如下:

    ;编程:在屏幕中间依次显示“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:[9*4]
        pop ds:[0]
        push es:[9*4+2]
        pop ds:[2]        ;将原来的int 9中断例程的入口地址保存在ds:0、ds:2单元中
    
        mov word ptr es:[9*4],offset int9
        mov es:[9*4+2],cs    ;在中断向量表中设置新的int 9中断例程的入口地址
    
        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]       ;将中断向量表中int 9中断例程的入口恢复为原来的地址
    
        mov ax,4c00h
        int 21h
    
    delay:    push ax
        push dx
        mov dx,10h
        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
    
    ;------以下为新的int 9中断例程--------------------
    
    int9:    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指令进行模拟,调用原来的int 9中断例程
    
        cmp al,1
        jne int9ret
    
        mov ax,0b800h
        mov es,ax
        inc byte ptr es:[160*12+40*2+1]  ;属性增加1,改变颜色
    
    int9ret:pop es
        pop bx
        pop ax
        iret
    
    code ends
    end start

    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            ;设置ds:si指向源地址
        mov di,204h                ;设置es:di指向目的地址
        mov cx,offset int9end - offset int9    ;设置cx为传输长度
        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]     ;当此中断例程执行时(CS)=0
    
        cmp al,3bh            ;F1的扫描码为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
  • 相关阅读:
    (Java实现) 洛谷 P1603 斯诺登的密码
    (Java实现) 洛谷 P1036 选数
    (Java实现) 洛谷 P1036 选数
    (Java实现) 洛谷 P1012 拼数
    (Java实现) 洛谷 P1012 拼数
    (Java实现) 洛谷 P1028 数的计算
    (Java实现) 洛谷 P1028 数的计算
    (Java实现) 洛谷 P1553 数字反转(升级版)
    8.4 确定两个日期之间的月份数或年数
    (Java实现) 洛谷 P1553 数字反转(升级版)
  • 原文地址:https://www.cnblogs.com/fate-/p/12988514.html
Copyright © 2011-2022 走看看