中断

中断是一种使CPU中止正在执行的程序而转去处理特殊事件的操作。这些引起中断的事件称为中断源,它们可能是来自外部设备的输入/输出请求,也可能是计算机的一些异常事故或其它内部原因。由外设控制器或协处理器(8087/80287)引起的中断 一般称为硬件中断或外中断,由程序中安排的中断指令INT产生的中断,或由CPU的某些错误结果产生的中断称为软件中断或内中断。

  8086/8088的中断源如动画所示。图中引线端标示的数字为系统分配的中断类型号。


  连到CPU的非屏蔽中断(NMI)是为电源错、内存或I/O总线的奇偶等异常事件的中断保留的,它不受中断允许标志IF的屏蔽,而且在整个系统中只能有一个非屏蔽中断,其中断类型号为2。

  外部设备的中断是通过Intel 8259A可编程中断控制器(PIC)连到主机上。CPU通过一组I/O端口控制8259A,而8259A则通过INTR管脚给CPU传送中断信号。多个8259A PIC可以树形结构连在一起,从而使大量的外部设备顺序连接到CPU的中断系统上。外部设备和8259A PIC的连法是由设计人员规定好的,这种外中断类型的分配由硬件连线实现,因而软件不能对其修改。动画中外设与8259A PIC的连法是80X86的标准连法。内中断不是由连线接到硬件上的,中断20H到3FH用于调用DOS功能例行程序,其它中断号小于20H或大于3FH的中断,用于调用IBM PC ROM BIOS或一些应用软件,这些内容在以后的章节中将要陆续讲到。

  中断是CPU和外部设备进行输入/输出的有效方法。这种输入/输出方式一直被大多数计算机所采用,它可以避免因反复查询外部设备的状态而浪费时间,从而提高了CPU的效率。

8.3.1 8086的中断分类

 
8.3.1.1 软件中断
 
  软件中断又称为内中断,它通常由三种情况引起:
  (1) 由中断指令INT引起。
  (2) 由于CPU的某些错误而引起。
  (3) 为调试程序(DEBUG)设置的中断。
  

  单步是 一种很有用的调试方法。当标志位TF置为1时,每条指令执行后,CPU自动产生类型号为1的中断-单步中断。产生单步中断时,CPU同样自动地将 FLAGS、CS和IP的内容保存入栈,然后清除TF、IF,于是,当进入单步中断处理程序后,就不是处于单步方式了,它将按正常方式运行中断处理程序。 在单步处理程序结束时,原来的FLAGS从堆栈中取回,又把CPU重新置成单步方式。
  
  使用单步中断可以一条指令一条指令地跟踪程序的流程,观察CPU每执行一条指令后,各个寄存器及有关存储单元的变化,从而指出和确定产生错误的原因。

  断点中断也是供DEBUG调试程序使用的,通常调试程序时,把程序按功能分成几段,然后每段设一个断点。当CPU执行到断点时便产生中断,这时程序员可以检查各寄存器及有关存储单元的内容。
  在上述内中断中,INT指令和INTO指令产生的中断,以及除法错中断都不能被禁止,并且比任何外部中断的优先权都高
1. 中断指令INT引起的内中断
 
    CPU执行完一条INT n指令后,会立即产生中断,并且调用系统中相应的中断处理程序来完成中断功能,中断指令的操作数n指出中断类型号。

  例如,我们要对存储器的容量进行测试,可在程序中安排一条中断指令:
    
     INT 12H
  
  当CPU执行这条指令时,立即产生了一个中断,并从中断向量表的0:48H开始的四个字节单元中取出段地址和偏移地址,然后转去执行相应的中断处理程序以完成对存储器的测试,返回调用程序后,AX寄存器中的数据即为存储器的大小。
  
  INT指令可以指定0~0FFH中的任何类型号。除系统占用的类型号之外,用户还可利用为用户保留的类型号扩充新的中断处理功能。

  2. 处理CPU某些错误的中断
   
   (1) 除法错中断,中断类型号为0。
   在执行除法指令时,若发现除数为0或商超过了寄存器所能表达的范围,则立即产生一个类型为0的中断。

   (2) 溢出中断,中断类型号为4。
   INTO指令专门用来处理溢出中断,下例为测试加法溢出的指令:
   
   ADD AX,VALUE
   INTO

  如果溢出标志OF置1,指令INTO可中断发生溢出的算术操作,如OF为0,则INTO指令不产生中断,CPU继续运行原程序。溢出中断处理程序的主 要功能是打印出一个出错信息,在处理程序结束时,不返回原程序继续运行,而是把控制交给操作系统。

  3. 为调试程序(DEBUG)设置的中断

  (1) 单步中断,中断类型号为1。

  (2) 断点中断,中断类型号为3。
  断点可以设置在程序的任何地方,设置断点实际上是把一条断点指令INT 3插入程序中,CPU每执行到断点处的INT3指令便产生一个中断。

8.3.1.2 硬件中断

   硬件中断来自处理机的外部条件,如I/O设备或其它处理机等,以完全随机的方式中断现行程序而转向另一处理程序。硬件中断又称为外中断。
  
  硬件中断主要有两种来源,一种是非屏蔽中断(NMI),另一种是来自各种外部设备的中断。由外部设备的请求引起的中断也称为可屏蔽中断。

  微型计算机配置的外部设备一般有硬磁盘(DISK),软磁盘(FLOPPY DISK),显示器(CRT)和各种打印机(LINE PRINTER)等。这些外部设备通过8259A可编程中断控制器和CPU相连,8259A可编程中断控制器可接收来自外设的中断请求信号,并把中断源的中断类型号送CPU,如果CPU响应该外设的中断请求,就自动转入相应的中断处理程序。

  从外设发出中断请求到CPU响应中断,有两个控制条件是起决定性作用的:

  (1) 该外设的中断请求是否屏蔽,这个条件由8259A的中断屏蔽寄存器控制;中断屏蔽寄存器的某位为0表示允许某种外设中断请求,某位为1表示某种外设的中断请求被屏蔽(禁止)。
  (2) CPU是否允许响应中断,这由标志寄存器(FLAG)中的中断允许位IF控制。如果IF=0,CPU就禁止响应任何外设的中断,如果IF=1,则允许CPU响应外设的中断请求。有两条指令能设置和清除IF位。
  STI 设置中断允许位(IF=1)
  CLI 清除中断允许位(IF=0)
中断屏蔽寄存器的I/O端口地址是21H,它的8位对应控制8个外部设备(见图8.4(a)),通过设置这个寄存器的某位为0或为1来允许或禁止某外部设备的中断。某位为0表示允许某种外设中断请求,某位为1表示某种外设的中断请求被屏蔽(禁止)。
  
  例如,只允许键盘中断,可设置如下中断屏蔽字:
  MOV AL, 11111101B
  OUT 21H, AL
  
  如果系统重要新增设键盘中断,则可用下列指令实现:
  IN AL, 21H
  AND AL, 11111101B
  OUT 21H, AL

  在编写中断程序时,应在主程序的初始化部分设置好中断屏蔽寄存器,以确定允许用中断方式工作的外部设备。

  外部设备向CPU发出中断请求,CPU是否响应还与标志寄存器中的中断标志位IF有关。如果IF=0,CPU就禁止响应任何外设的中断,也就是 说,CPU将不会产生中断来处理外设的请求。如果IF=1,则允许CPU响应外设的中断请求, 允许CPU响应外设的中断请求(IF=1)也叫做开中断,反之叫做关中断(IF=0)。

  我们已经知道,当任何类型的中断发生时,当前的FLAGS要保存入栈,然后清除IF位进入中断处理程序。如果允许在一个中断处理程序的执行过程中发生 硬中断,则必须用一条STI指令开中断。当执行到中断返回指令IRET,又取出FLAGS先前的值,其中IF为1,CPU将允许硬中断再次发生。



非屏蔽中断 是一种特殊的硬件中断,它和IF标志位无关。非屏蔽中断的类型号为2,CPU不能禁止非屏蔽中断。

  对非屏蔽中断, CPU总会响应的,所以非屏蔽中断主要用于一些紧急的意外处理,如电源掉电等。另外计算机内部的实时钟希望能不停地计时,所以也可以把非屏蔽中断提供给实时钟。

  中断结束命令是由中断命令寄存器中的中断结束位(5位)控制的。当EOI位为1时,当前正在处理的中断请求就被清除。

  结束硬件中断用下面的指令:
  MOV AL, 20H
  OUT 20H, AL
  在一次中断处理结束之前,还应给8259A可编程中断控制器的中断命令寄存器发出中断结束命令 (End Of Interrupt -- EOI)。中断命令寄存器的I/O端口地址为20H(见图8.4(b)),它的各控制位可动态地控制中断处理过程,其中L2-L0三位指定IR0-IR7 中具有最低优先级的中断请求。6位(Set Level)和7位(Rotate)控制IR0-IR7的中断优先级的顺序。5位(EOI)是中断结束位,当EOI位为1时,当前正在处理的中断请求就被 清除,所以在中断处理完成后,必须把中断结束位置为1,否则以后将屏蔽掉对同级中断或低级中断的处理。当然在必要的时候,在中断处理程序中也可利用EOI 命令清除当前的中断请求,使得在中断处理的过程中又能响应同级和低级中断。

      
图8.4 中断屏蔽寄存器和中断命令寄存器   
 


8.3.2 中断向量表

  中断向量表是各类型中断处理程序的入口地址表。

  各类型中断处理程序的段地址和偏移地址在中断向量表中按中断类型号顺序存放,因此每类中断向量的地址可由中断类型号乘以4计算出来。

   动画以BIOS中断INT 4AH为例,表示出中断操作的5个步骤:
  (1) 取中断类型号
  (2) 计算中断向量地址
  (3) 取中断向量,偏移地址送IP,段地址送CS
  (4) 转入中断处理程序
  (5) 中断返回到INT指令的下一条指令
我们给每种中断都安排一个中断类型号。80x86中断系统能处理256种类型的中断,类型号为 0-0FFH。如图8.3所示的中断源,系统时钟的中断类型为08,键盘为09,软中断中的除法错误的中断类型为0,等等。每种类型的中断都由相应的中断 处理程序来处理,中断向量表就是各类型中断处理程序的入口地址表。
 
              
图8.5 中断向量表         
         
  我们知道存储器的低1.5K字节,地址从0段0000 ~ 5FFH为系统占用,其中最低的1K字节,地址从0000 ~ 3FFH存放中断向量。中断向量表中的256项中断向量对应256种中断类型,每项占用四个字节,其中两个字节存放中断处理程序的段地址(16位),另两 个字节存放偏移地址(16位)。因为各处理程序的段地址和偏移地址在中断向量表中按中断类型号顺序存放(如图8.5所示),所以每类中断向量的地址可由中 断类型号乘以4计算出来。例如,报警中断的中断类型为4AH,它的中断向量地址为4AH*4=128H,即128H、129H两字节存放的是报警中断处理 程序的偏移地址,12AH、12BH两字节存放的是报警中断处理程序的段地址,取出段地址和偏移地址放入CS和IP,CPU就可以转入相应的中断处理程 序。

  采用向量中断的方法,大大加快了中断处理的速度,因为计算机可直接通过中断向量表转向相应的处理程序,而不需要CPU去逐个检测和确定中断原因。  
表8.2列出了80X86各类型中断在中断向量表中的地址。
  例8.4 使用DOS功能调用存取中断向量
 
  为中断类型N设置中断向量的程序:
MOV AX, 0
MOV ES, AX    ;set to base interrupt vectors
MOV BX, N * 4   ;offset of type N interrupt
MOV AX, OFFSET INTHAND
MOV ES:WORD PTR [BX], AX  ;set addr of INTHAND
MOV AX, SEG INTHAND
MOV ES : WORD PTR[BX+2], AX
...
INHAND:  ;interrupt processing routine
...
IRET

   
           表8.2 中断向量表地址分配

地 址    中断类型号
0 - 7F     0 - 1F     BIOS中断向量
80 - FF    20 - 3F    DOS中断向量
100 - 17F   40 - 5F  扩充BIOS中断向量
180 - 19F   60 - 67    用户中断向量
1A0 - 1BF   68 - 6F    保留

地址    中断类型号
1C0 - 1DF  70 - 77   I/O设备中断向量
1E0 - 1FF  78 - 7F    保留
200 - 3C3  80 - FD    BASIC
3C4 - 3FF  F1 - FF    保留

   用 户可以利用保留的中断类型号扩充自己需要的中断功能,对新增加的中断功能要在中断向量表中建立相应的中断向量。如果新的中断功能只供自己使用,或用自己编 写的中断处理程序代替系统中的某个中断处理功能时,要注意保存原中断向量。在设置自己的中断向量时,应先保存原中断向量再设置新的中断向量,在程序结束之 前恢复原中断向量。

  实际上,我们在检查或设置任何中断向量时,总是避免直接使用中断向量的绝对地址,而是使用DOS功能调用(21H)存取中断向量。

  存取中断向量的DOS功能调用
 
  
设置中断向量
    把由AL指定的中断类型的中断向量DS : DX放在中断向量表中
  预置:AH = 25H
     AL = 中断类型号
     DS : DX = 中断向量
  执行:INT 21H

  取中断向量
    把由AL指定的中断类型的中断向量从中断向量表中取到ES : BX中
  预置:AH = 35H
     AL = 中断类型号


  执行:INT 21H
  返回时送:ES : BX = 中断向量


   
例8.4  
     
...
      MOV    AL, N          ;type N interrupt
      MOV    AH, 35H         ;get interrupt vector
      INT    21H
      PUSH    ES            ;save the old base and
      PUSH    BX           ; offset of interrupt N
      PUSH   DS
      MOV    AX, SEG INTHAND
      MOV    DS, AX          ;base of INTHAND in DS
      MOV    DX, OFFSET INTHAND   ;offset in DX
      MOV    AL, N          ;type N
      MOV    AH, 25H        ;set interrupt vector
      INT    21H
      POP    DS
      ...
      POP    DX           ;restore the old offset
      POP    DS           ; and base of interrupt
      MOV    AL, N          ;type N
      MOV    AH, 25H        ;set interrupt vector
      INT    21H
      RET               ;return
     
;
 INTHAND: ...                ;interrupt processing routine
      ...
      IRET

8.3.3 中断过程

  当中断发生时,由中断机构自动完成下列动作:
  1. 取中断类型号N
  2. 标志寄存器(FLAGS)内容入栈
  3. 当前代码段寄存器(CS)内容入栈
  4. 当前指令计数器(IP)内容入栈
  5. 禁止硬件中断和单步中断(IF=0, TF=0)
  6. 从中断向量表中取4*N的字节内容送IP,取4*N+2中的字节内容送CS
  7. 转中断处理程序
 
  中断过程如动画所示

  中断发生的过程很像我们所熟悉的子程序调用,不同的是在保护中断现场时,除了保存返回地址CS:IP之外,还保存了标志寄存器FLAGS的内容。因为 标志寄存器记录了中断发生时,程序指令运行的结果特征,当CPU处理完中断请求返回原程序时,要保证原程序工作的连续性和正确性,所以中断发生时的 FLAGS内容也要保存起来。另一个不同点是,在中断发生时,CPU还自动清除了IF位和TF位,这样设计的目的是使CPU转入中断处理程序后,不允许再 产生新的中断,如果在执行中断处理程序的过程中,还允许外部的中断,可通过STI指令再把IF置为1。

  编写中断处理程序和编写子程序一样,所使用的汇编语言指令没有特殊限制,只是中断程序返回时使用IRET指令。这条指令的工作步骤和中断发生时的工作 步骤正好相反。它首先把IP、CS和FLAGS的内容出栈,然后返回到中断发生时紧接着的下一条指令

8.3.4 中断优先级和中断嵌套

  1.中断优先级
  8086规定中断的优先级次序为:



  可屏蔽中断的优先权又分为八级,按图8.3的连接,在正常的优先级方式下,定时器的优先级最高,键盘其次,打印机的优先权最低。


 
 在 8086系统中,有软件中断、硬件中断等多个中断源,当多个中断源同时向CPU请求中断时,CPU应如何处理呢?办法是我们给各种中断源事先安排一个中断 优先级次序,当多个中断源同时申请中断时,CPU先比较他们的优先级(Priority),然后从优先级高到优先级低的次序来依次处理各个中断源的中断请 求。

  在8.3节中我们已经提到8259A的中断命令寄存器(图8.4)的6位和7位能控制各中断请求端的优先次序。在正常的优先级方式下,优先级次序是:
  IR0,IR1,IR2,IR3,IR4,IR5,IR6,IR7

  在发出一个EOI命令时,7位(R)和6位(SL)有四种组合,其含义如下:
  R SL
  0  0  正常优先级方式
  0  1  清除由 L2 - L0指定的中断请求
  1  0  各中断优先级依次左循环一个位置
  1  1  各中断优先级依次循环到由L2 - L0指定的中断请求到达最低优先级位置上。

  硬件中断的优先级次序一般在正常优先级方式下(R=0,SL=0),但在必要的情况下,设置中断命令寄存器能改变IR0-IR7的优先级次序,例 如,IR0-IR7原为正常的优先级次序,现在要使IR4成为最低级的中断请求,则给端口20H送命令码:11100100,即 R=1,SL=1,EOI=1,L2L1L0=100,这样,各中断优先级就依次循环到IR4为最低优先级的位置上:
  IR5,IR6,IR7,IR0,IR1,IR2,IR3,IR4,IR5

  如果再送一个命令码:10100000,则优先级次序再向左循环一个位置,成为:
  IR6,IR7,IR0,IR1,IR2,IR3,IR4,IR5

  在下面的描述中,如无特别说明,中断优先级是指正常方式下的中断优先级。



  2.中断嵌套
  中断嵌套是指正在运行的中断处理程序,又被其它中断源中断的情况。

  一个正在执行的中断处理程序,在开中断(IF=1)的情况下,能被优先级高于它的中断源中断,但如果要被同级或低级的中断源中断,则必须发出EOI命令,清除正在执行的中断请求,才能响应同级或低级的中断。


  80X86没有规定中断嵌套的深度(中断程序又被中断的层次),但在实际使用时,多重的中断嵌套要受到堆栈容量的限制,所以在编写中断程序时,一定要考虑有足够的堆栈单元来保存多次中断的断点及各寄存器的内容。

  首先,CPU响应优先级高的IR2,转去处理IR2的中断处理程序。进入IR2处理程序后,IF被置为1。当IR1的中断请求到达后,因IR1的优先 级高于IR2,CPU就立即中断IR2的程序,转去执行IR1的处理程序。在IR1处理程序中,由指令发出了EOI命令,结束了IR1的中断请求。返回 IR2处理程序后,同样由于发出EOI命令清除了IR2的中断请求,所以在较低级的中断请求IR4到达后,即转向处理IR4的中断请求。在IR4处理程序 的执行过程中,IR3的中断请求到达,当判断IF已被置为1,则又中断了IR4的程序,转去执行IR3的程序。在IR3程序中,也发出了开中断指令 (STI)和中断结束命令(EOI),最后IRET指令使其返回到IR4程序,IR4在返回IR2之前也发出了EOI命令,结束了IR4的中断请求。

  IR2中断请求在前面已被清除,所以IR4执行完后,IR2继续执行直到返回主程序。

  中断嵌套举例:
  该动画的例子是在正常优先级方式下,优先级中断和中断嵌套发生时的处理过程。该例子假定在主程序的执行过程中,IR2和IR4的中断请求同时发生, 而后IR1的中断请求又到达,最后IR3的中断请求也到达。

多级8259A 中断系统:
  硬件中断的扩充使用多级的8259A系统。在多级中断系统中,从属的8259A连接到主8259A的哪一端上,它就具有那一端的中断优先级别,如图8.6所示。


  在图8.6的连接方式下,各中断请求端的优先级排列顺序如下:


  对于主8259A和从属8259A的中断屏蔽寄存器,禁止相应端中断请求的原理和单级8259A是一样的。


8.3.5 中断处理程序
 
  通过前面几节对中断的介绍,我们对如何编写中断程序已经有一些了解了,现在把它们归纳在一起就更清楚了。

  左面是主程序为响应中断所作的准备工作以及硬件(包括CPU和外设接口)自动完成的动作:


  中断处理程序的编写方法和标准子程序很类似,下面是编写中断处理子程序的步骤,请注意与子程序编写的一些不同之处。

  


  至此,CS和IP寄存器取得了中断处理程序的段地址和偏移地址,CPU就把控制转给中断处理程序。

  这里要注意的是设备发到CPU的中断请求信号在时间上是随机的,只要未被屏蔽的设备本身的状态是准备好或空闲的,它就会向CPU请求中断,如果此时 CPU正在执行一条指令,那么就要等这条指令执行完后,才响应中断。对加封锁的指令(如LOCK MOV AX, BX)应看作为一条指令处理;对加重复前缀的串指令(如REP MOVSB),也要作为一个整体来处理,但不是把串操作全部重复执行完,而是执行一次重复和串指令即可响应中断。对MOV指令和POP指令,如果处理对象 是段寄存器时,那么本条指令执行完后,接着再执行一条指令才响应中断。对开中断指令STI和中断返回指令IRET,也是要在STI或IRET指令执行完 后,再执行一条指令才响应中断。以上是几种特殊情况,对一般指令,只要一条指令的执行周期结束即可响应中断。  

  (1) 保存寄存器内容
  (2) 如允许中断嵌套,则开中断(STI)
  (3) 处理中断
  (4) 关中断 (CLI)
  (5) 送中断结束命令(EOI)给中断命令寄存器
  (6) 恢复寄存器内容
  (7) 返回被中断的程序(IRET)

  进入中断处理程序时,IF和TF已经被清除,这样在执行中断处理程序的过程中,将不再响应其它外设的中断请求,如果这个中断处理程序允许其它设备中 断,则需用STI指令把IF位置1。中断结束命令(EOI)在程序的什么地方发出,这要看程序员是否要求在其处理过程中允许同级或低级中断。一般设备希望 一次中断的处理过程最好是完整的,所以只在中断处理结束之前发出EOI命令。

  处理中断部分是中断处理程序的主体部分,它要完成的任务是各种各样的,这与实际应用有关。如果它的任务是处理某种错误的,一般要求显示输出一系列出错 信息。如果它是对一个I/O设备进行服务的,就按其端口地址接收或发送一个单位(字节或字)的数据。要注意的是CPU产生一次中断,I/O设备只完成一个 字节(或字)的输入/输出,所以中断处理程序所用的指针变量或数据变量一般应设置存储单元来保存。

8.3.6 中断程序举例

  例8.5 编写一个中断处理程序,要求在主程序运行过程中,每隔10秒响铃一次,同时在屏幕上显示出信息“The bell is ring!”

 在系统定时器(中断类型为8)的中断处理程序中,有一条中断指令 INT 1CH,时钟中断每发生一次(约每秒中断18.2次)都要嵌套调用一次中断类型1CH的处理程序。在ROM BIOS例程中,1CH的处理程序只有一条IRET指令,实际上它并没有做任何工作,只是为用户提供了一个中断类型号。如果用户有某种定时周期性的工作需 要完成,就可以利用系统定时器的中断间隔,用自己设计的处理程序来代替原有的1CH中断程序。

  1CH作为用户使用的中断类型,可能已被其它功能的程序所引用,所以在编写新的中断程序时,应作下述工作:
  (1) 在主程序的初始化部分,先保存当前1CH的中断向量,再置新的中断向量。
  (2) 在主程序的结束部分恢复保存的1CH中断向量。

 
Purpose: ring and display a message every 10 seconds.
  ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  ;eg8-5.asm
  ;Purpose: ring and display a message every 10 seconds.
  ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

     .model small
;-------------------------------------------------------------------------
     .stack
;-------------------------------------------------------------------------
     .data
  count   dw   1
  msg    db   'The bell is ringing!', 0dh, 0ah, '$'
;-------------------------------------------------------------------------
     .code
; Main   program
  main    proc far
  start:
  mov    ax, @data   ;allot data segment
  mov    ds, ax

; save old interrupt vector
  mov    al, 1ch    ;al<=vector number
  mov    ah, 35h    ;to get interrupt vector
  int    21h      ;call DOS
  push   es       ;save registers for restore
  push   bx
  push   ds

;set new interrupt vector
  mov    dx, offset ring  ;dx<=offset of procedure ring
  mov    ax, seg ring    ;ax<=segment of procedure ring
  mov    ds, ax       ;ds<=ax
  mov    al, 1ch      ;al<=vector#
  mov    ah, 25h      ;to set interrupt vector
  int    21h        ;call DOS

  pop ds            ;restore ds
  in     al, 21h      ;set interrupt mask bits
  and    al, 11111110b
  out    21h, al
  sti

   mov    di, 20000
delay: mov si, 30000
delay1:dec si
   jnz    delay1
   dec   di
   jnz   delay

;restore old interrupt vector
   pop    dx        ;restore registers
   pop    ds
   mov   al, 1ch      ;al<=vector#
   mov    ah, 25h      ;to restore interrupt
   int   21h        ;call DOS

   mov    ax, 4c00h     ;exit
   int   21h
main endp


ring proc   near
   push   ds         ;save the working registers
   push   ax
   push   cx
   push   dx

   mov    ax, @data     ;allot data segment
   mov    ds, ax
   sti


;siren if it is time for ring
  dec    count       ;count for ring interval
  jnz    exit        ;exit if not for ring time

  mov    dx, offset msg   ;dx<=offset of msg
  mov    ah, 09h      ;to display msg
  int    21h        ;call DOS

  mov    dx, 100      ;dx<=turn on/off times(100)
  in     al, 61h      ;get port 61h
  and    al, 0fch      ;mask bits 0,1


sound:
  xor    al, 02       ;toggle bit 1
  out    61h, al      ;output to port 61h
  
mov    cx, 1400h     ;value of wait

wait1:
  loop   wait1
  dec    dx        ;control turn on/off 10 times
  jne    sound
  mov    count, 182    ;control ring interval delay(10s)


exit:
  cli
  mov    al,20h      ;set EOI
  mov    20h,al
  pop    dx        ;restore the reg.
  pop    cx
  pop    ax
  pop    ds
  iret            ;interrupt return
ring endp

;-------------------------------------------------------------------------
  end start          ;end assemble

例8.6 在配置了键盘输入(中断类型09)和打印机输出(中断类型0FH)两种外部设备的8086中断系统中,要求从键盘上接收字符,同时对32字节的输入缓冲区进行测试,如果缓冲区已满,则键盘挂起(禁止键盘中断输入),由打印机输出一个提示信息。

  键盘和打印机分别由中断屏蔽寄存器(21H)的1位和7位控制。键盘的输入寄存器端口地址为60H,控制寄存器的端口地址为61H。打印机输出寄存器的端口地址为378H,打印机控制寄存器的端口地址为37AH。

  在这种特定情况下,只要求打印机在键盘输入缓冲区满了后,打印出提示信息,因此它可以在屏蔽键盘中断的同时,设置打印机的中断屏蔽位。另外,在中断处 理程序中用到的一些指针及计数值要保存在指定的存储单元中,每次进入中断,取出指针及计数值,退出中断时,再把修改后的指针及计数值保存起来。


  这个中断程序包括以下几部分:
  MAIN 初始化部分,保存09和0FH的原中断向量,设置新的中断向量。主程序用有限循环来模拟。主程序结束时,恢复原中断向量。
  KBDINT 键盘中断处理程序。接收按键的扫描码并保存在缓冲区中,如果输入的字符数超过32,则屏蔽键盘中断,允许打印机中断,并调用INIT-PRT子程序初始化打印机。
  INIT-PRT初始化打印机,启动适配器,发出选通信号。
  PRTINT 打印机中断处理程序。按照指针取出打印机字符送到输出寄存器,发出选通信号。
  DISPLAY-HEX 用十六进制显示AL中的代码。

Purpose: accept keyboard input and print messages on the printer 
  ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  ;eg8-6.asm
  ;Purpose: accept keyboard input and print messages on the printer
  ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *


     .model tiny
;-------------------------------------------------------------------------
     .stack
;-------------------------------------------------------------------------
     .data
  old_ip09   dw ?
  old_cs09    dw ?
  old_ip0f   dw ?
  old_cs0f    dw ?
  count     dw ?
  buffer     db 20h dup(' ')
  buf_p     dw ?
  start_msg   db 0ah, 0dh, 'RUN', 0ah, 0dh, '$'
  end_msg    db 0ah, 0dh, 'END', 0ah, 0dh, '$'
  full_msg    db 0ah, 0dh, 'Buffer full!$'

;-------------------------------------------------------------------------
     .code
; Main   program
  main    proc far
start:
  mov    ax, @data           ;ds<=data segment
  mov    ds, ax

; initialize
  lea    ax, buffer          ;buf_p<=buffer address
  mov    buf_p, ax
  mov    count, 0           ;count<=0

; save old interrupt 09h
  mov    al, 09h            ;al<=vector#
  mov    ah, 35h            ;to get interrupt vector
  int    21h              ;call DOS
  mov    old_cs09, es          ;save registers for restore
  mov    old_ip09, bx
  push    ds               ;save ds


;set new interrupt 09h
  lea    dx, kbdint           ;dx<=offset of procedure kbdint
  mov    ax, seg kbdint        ;ax<=segment of procedure kbdint
  mov    ds, ax            ;ds<=ax
  mov    al, 09h            ;al<=intrrupt number
  mov    ah, 25h            ;to set interrupt vector
  int    21h              ;call DOS
  pop    ds               ;restore ds

;set keyboard interrupt mask bits
  in     al, 21h
  and    al, 0fdh
  out    21h, al

;save old interrupt 0fh
  mov    al, 0fh            ;al<=vector#
  mov    ah, 35h            ;to get interrupt vector
  int    21h               ;call DOS
  mov    old_cs0f, es          ;save registers for restore
  mov    old_ip0f, bx
  push    ds               ;save ds

;set new interrupt 0fh
  lea    dx, prtint           ;dx<=offset of procedure prtint
  mov    ax, seg prtint         ;ax<=segment of procedure prtint
  mov    ds, ax            ;ds<=ax
  mov    al, 0fh            ;al<=vector#
 
  mov    ah, 25h            ;to set interrupt vector
  int    21h              ;call DOS
  pop    ds              ;restore ds

  mov    ah, 09h            ;print start message
  lea    dx, start_msg
  int    21h
  
  sti
  mov    di,20000            ;main process
mainp: mov   si,30000
mainp1:dec  si
  jnz    mainp1
  dec    di
  jnz    mainp

  mov    ah, 09h            ;print end msg of main process
  lea    dx, end_msg
  int    21h

  cli
;restore old interrupt 09h
  push    ds              ;save ds
  mov    dx, old_ip09          ;ds:dx<=old handler address
  mov    ax, old_cs09
  mov    ds, ax
  mov    al, 09h            ;al<=vector#
  mov    ah, 25h            ;to restore interrupt vector
  int    21h              ;call DOS
  pop    ds               ;restore ds

; restore old interrupt 0fh
  push   ds               ;save ds
  mov    dx, old_ip0f          ;ds:dx<=old address
  mov    ax, old_cs0f
  mov    ds, ax
  mov    al, 0fh            ;al<=vector#
  mov    ah, 25h            ;to restore interrupt
  int    21h              ;call DOS
  pop    ds              ;restore ds

; enable keyboard interrupt
  in    al, 21h
  and    al, 0fdh

; enable keyboard interrupt
  in     al, 21h
  and    al, 0fdh
  out    21h, al

  sti
  mov   ax, 4c00h
  int   21h
main endp


;-----------------------------------------------------
; Interrupt Handler kbd
;  Purpose: fill buffer until full when substituted for interrupt 09h
  kbdint proc near
  push   ax            ; save registers

  push   bx

  cld                ;direction: forward
  in    al, 60h         ;read a character
  push   ax            ;save it
  in    al, 61h          ;get the control port
  mov    ah, al          ;save the value in ah
  or    al, 80h          ;reset bits for kbd
  out    61h, al         ;send out
  xchg   ah, al          ;restore control value
  out    61h, al          ;kdb has been reset

  pop   ax            ;restore scan code
  test   al, 80h          ;press or release?
  jnz    return1         ;ignore when release

  mov    bx, buf_p        ;bx<=buffer pointer
  mov    [bx], al         ;store in buffer
  call   display_hex        ;display in hex
  inc    bx            ;move pointer
  inc    count          ;count characters
  mov    buf_p, bx        ;save the pointer

check:
  cmp    count, 20h ;judge whether full
  jb    return1
  in    al, 21h
  or    al, 02          ;mask kdb bits
  and   al, 7fh         ;enable prt bits
  out   21h, al
  call   init_prt         ;initiate printer

return1:
  cli
  mov   al, 20h         ;end of interrupt
  out    20h, al

;restore registers
  pop   bx
  pop   ax
  iret               ;interrupt return
kbdint endp

;------------------------------------------------------------------------

; Interrupt Handler prtint
; Purpose: print characters when substituted for interrupt 0fh
  prtint proc near
  push   ax            ;save registers
  push   bx
  push   dx

  mov    bx, buf_p        ;bx<=buffer pointer
  mov    al, [bx]         ;get from buffer
  mov    dx, 378h         ;printer data port
  out    dx, al          ;output a character

  push   ax            ;save ax
  mov    dx, 37ah         ;printer control port
  mov    al, 1dh          ;al<=control code
  out    dx, al          ;send out
  jmp    $+2           ;slight delay
  mov    al, 1ch          ;al<=control code
  out    dx, al          ;send out
  pop    ax            ;restore ax

  inc    bx            ;move pointer
  mov    buf_p, bx        ;save the pointer
  cmp    al, 0ah         ;end of message?
  jnz    return2
  in     al, 21h         ;disable printer
  or    al, 80h         ; interrupt
  out    21h, al

return2:
  mov    al, 20h         ;end of interrupt
  out    20h, al

  pop    dx            ;restore registers
  pop    bx
  pop    ax
  iret                ;interrupt return
prtint endp

;------------------------------------------------------------------------

; Procedure init_prt
  init_prt proc near
  push    ax            ;save registers
  push   bx
  push   dx

  cli
  lea    bx, full_msg       ;bx<=offset of full_msg
  mov    buf_p, bx         ;save full_msg address

  mov    dx, 378h         ;printer data port
  mov    al, 0dh         ;CR
  out    dx, al          ;output a character

  mov    dx, 37ah         ;printer control port
  mov    al, 1dh         ;al<=control code
  out    dx, al          ;send out
  jmp    $+2            ;slight delay
  mov    al, 1ch          ;al<=control code
  out    dx, al          ;send out

  pop    dx            ;restore registers
  pop    bx
  pop    ax

  ret
init_prt endp
;------------------------------------------------------------------------


display_hex proc   near       ;display char with hex
    push     ax 
    push     cx
    push     dx
    mov     ch, 2       ;number of digits
    mov      cl, 4
nextb:
    rol      al, cl      ;highest of bits to lowest
    push     ax
    mov     dl, al
    and     dl, 0fh     ;mask off left digit

   or      dl, 30h     ;convert to ASCII
    cmp     dl, 3ah
    jl      dispit
    add      dl,7h       ;digit is A to F

dispit:
    mov      ah, 2       ;display character
    int     21h
    pop     ax
    dec      ch        ;done 2 digits?
    jnz      nextb      ;not yet
    mov      ah, 2
    mov     dl,','      ;display ','
    int      21h
    pop      dx
    pop     cx
    pop      ax
    ret              ;return from display_hex
display_hex endp

;------------------------------------------------------------------------
    end start



例8.7 除数为0时的软件中断(类型0)处理程序
  此程序分成两个主要部分:初始化部分和中断处理部分。


  初始化部分(Init)设置新的0型中断向量,显示一条信息,然后完成终止和驻留后退出程序。这种特殊的退出是用INT 21H的功能31H,它将保留程序所占的内存,从而使这些内存单元不被以后的应用程序破坏。

  中断处理程序(Zdiv)在发生一个被零除中断时接收控制。中断处理程序先保存有关寄存器的值,然后打印出信息询问用户是退出程序(Quit)还是继 续(Continue)。若键入"C"要求继续执行程序,则处理程序恢复所有寄存器并执行IRET返回主程序(显示一个标记符#),当然此时除法的操作结 果应是无效的。若键入"Q"要求退出,则从处理程序直接返回DOS(无标记符显示)。这里返回DOS,是用INT 21H的功能4CH,该功能是唯一不依赖于任何段寄存器内容的终止功能,例如,CS寄存器不必指向PSP所在的段。该功能的另一个优点是能在AL中返回一 个表明程序是否正常终止的出口代码。系统出口代码的含义为:00 - 正常终止;01 - 用Ctrl-C终止;02 - 严重设备错误引起终止;03 - 用功能调用31H终止。左侧是处理除数为0错误的中断处理程序清单。

Purpose: zero_division handler  
  ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  ;eg8-7.asm
  ;Purpose: zero_division handler
  ;* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *


     .model small
;-------------------------------------------------------------------------
     .stack
;-------------------------------------------------------------------------
     .code
; Main program
main    proc   near
;reset  interrupt  vector 0
      lea   dx, zdiv    ;set interrupt vector
      mov   ax, seg zdiv
      mov   ds, ax
      mov   al, 0     ;interrupt number
      mov   ah, 25h     ;to reset interrupt vector
      int   21h       ;call DOS

;print introduction message
      mov   ax, @code    ;ds<=code segment
      mov   ds, ax
      mov   dx, ok_msg   ;print introduction
      mov   ah, 9
      int   21h

;simulate zero_division condition
      mov   ax, 1
      mov   dl, 0
      div   dl

; display '# 'after return from interrupt handler zdiv
      mov   ah, 2      ;to display a character
      mov   dl, '#'     ;dl<='#'
      int   21h       ;call DOS

;exit and reside in memory
      mov   ah, 31h     ;to exit and reside
      mov   al, 0     ;return code: 0
      mov   dx, ((prog_len+15)/16)+16   ;dx<=memory paragraph

; for residence
      int   21h      ;call DOS
main endp





;-------------------------------------------------------------------------
; Interrupt Handler zdiv
zdiv   proc   far
     push   ax    ;save registers
     push   bx
     push   cx
     push   dx
     push   si
     push   di
     push   bp
     push   ds
     push   es
     sti

prt_warn:
     mov   ax, @code
     mov   ds, ax
     mov   dx, offset warn_msg   ;print warning
     mov   ah, 9
     int   21h


input:
     mov   ah, 1           ;to accept keyboard input
     int    21h           ;call DOS
     cmp    al, 'c'          ;judge whether 'c'
     je    continue
     cmp    al, 'q'         ;judege whether 'q'
     je    exit

     mov    dx, offset beep      ;beep when illegal input
     mov    ah, 09
     int    21h
     jmp   prt_warn


exit:
     mov    ax, 4cffh
     int    21h
continue:
     mov    dx, offset crlf      ;print CR & LF
     mov    ah, 09
     int   21h
     cli


     pop    es            ;restore registers
     pop    ds
     pop    bp
     pop   di
     pop   si
     pop   dx
     pop   cx
     pop   bx
     pop    ax
     iret               ;interrupt return
zdiv   endp

;------------------------------------------------------------------------

; Data area
ok_msg  db 0dh, 0ah, 'Zero-division Handler installed!'
     db 0dh, 0ah, '$'
warn_msg db 'Zero-division detected, ', 07h
     db 'Continue or Quit(c/q)?$'
beep   db 07h, '$'
crlf   db 0dh, 0ah, '$'
prog_len equ $-main


;------------------------------------------------------------------------
     end main

【本章小结】

 
1. 程序直接控制I/O的方式:
  这是一种使用I/O指令直接在端口级上进行数据传送的编程方式,这种方式有时需要查询外设的状态,如果外设处于准备好或空闲状态,则CPU通过接口中 的数据寄存器进行输入或输出,如果外设没有准备好或是忙状态,CPU就查询等待,不再作有效的工作。

  2. 中断程序的设计方法:
  对于要求以中断方式工作的I/O设备,它们的中断类型已由硬件连线确定(如图8.3)。主程序为中断所做的准备工作如下:
  (1) 保存原中断向量(INT 21H的35H功能),设置新的中断向量(INT 21H的25H功能);
  (2) 设置设备的中断屏蔽位(仅对可屏蔽中断);
  (3) 设置CPU的中断允许位(开中断);
  (4) 在主程序结束前,恢复原中断向量。

  主程序完成了上述准备工作后,I/O设备即以完全随机的方式产生中断。当CPU响应了中断请求,中断系统将自动完成以下工作:
  (1) CPU接收外设的中断类型号;
  (2) 当前的FLAGS、CS、IP的内容入栈保存;
  (3) 清除IF、TF;
  (4) 根据中断类型号取出中断向量送CS和IP;
  (5) 转中断处理子程序。

  中断处理子程序的编写方法:
  (1)保存工作寄存器内容;
  (2)如允许中断嵌套,则开中断(STI);
  (3)处理中断任务;
  (4)关中断 (CLI);
  (5)送中断结束命令(EOI)给中断命令寄存器(仅对硬件中断);
  (6)恢复工作寄存器内容;
  (7)返回被中断的程序(IRET)。

<script type=text/javascript charset=utf-8 src="http://static.bshare.cn/b/buttonLite.js#style=-1&uuid=&pophcol=3&lang=zh"></script> <script type=text/javascript charset=utf-8 src="http://static.bshare.cn/b/bshareC0.js"></script>
阅读(1458) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
评论热议
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值