内中断

任何一个通用的CPU,比如8086,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从CPU外部发送过来的或内部产生的一种特殊信息,并且可以立即对所接收到信息进行处理。这种特殊的信息,我们可以称其为:中断信息。

中断的意思是指,CPU不再接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。

注意,这里所说的中断信息,是为了便于理解而采用的一种逻辑上的说法。它是对几个具有先后顺序的硬件操作所产生的事件的统一描述。

“中断信息”是要求CPU马上进行某种处理,并向所要进行的该种处理提供了必备的参数的通知信息。

中断信息可以来自CPU的内部和外部。

内中断的产生

对于8086CPU,当CPU内部有下面的情况发生的时候,将产生相应的中断信息:

1) 除法错误,比如:执行div指令产生的除法溢出;

2) 单步执行;

3) 执行int0指令;

4) 执行int指令。

8086CPU用称为中断类型码的数据来标识中断信息的来源。中断类型码为一个字节数据,可以表示256种中断信息的来源。以后,我们将产生中断信息的事件,即中断信息的来源,简称为中断源,上述的4种中断源,在8086CPU中的中断类型码如下:

1) 除法错误:0

2) 单步执行:1

3) 执行int0指令:4

4) 执行int指令,该指令的格式为int n,指令中的n为字节型立即数,是提供给CPU的中断类型码。

中断处理程序

CPU收到中断信息后,需要对中断信息进行处理。

而如何对中断信息进行处理,可以由我们编程决定。

我们编写的,用来处理中断信息的程序被称为中断处理程序。

一般来说,需要对不同的中断信息编写不同的处理程序。

CPU在收到中断信息后,应该转去执行该中断信息的处理程序。

若要8086CPU执行某处的程序,就要将CS:IP指向它的入口(即程序第一条指令的地址)。

可见首要的问题是,CPU在收到中断信息后,如何根据中断信息确定其处理程序的入口。

CPU的设计者必须在中断信息和其处理程序的入口地址之间建立某种联系,使得CPU根据中断信息可以找到要执行的处理程序。

中断信息中包含有标识中断源的类型码。根据CPU的设计,中断类型码的作用就是用来定位中断处理程序。

若要定位中断处理程序,需要知道它的段地址和偏移地址,而如何根据8位的中断类型码得到中断处理程序的段地址和偏移地址呢?

中断向量表

CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。

中断向量表就是中断向量的列表,中断向量,就是中断处理程序的入口地址。

中断向量表,就是中断处理程序入口地址的列表。

中断向量表在内存中保存,其中存放着256个中断源所对应的中断处理程序的入口。

CPU只要知道了中断类型码,就可以将中断类型码作为中断向量表的表项号,定位相应的表项,从而得到中断处理程序的入口地址。

中断向量表在内存中存放,对于8086PC机,中断向量表指定放在内存地址0处。从内存0000:0000到0000:03e8的1000个单元中存放着中断向量表。这是规定。

一个表项存放一个中断向量,也就是一个中断处理程序的入口地址,对于8086CPU,这个入口地址包括段地址和偏移地址,所以一个表项占两个字,四个字节,高地址字存放段地址,低地址字存放偏移地址。

存储N号中断源对应的中断处理程序入口的偏移地址的内存单元的地址为:N*4;

存储N号中断源对应的中断处理程序入口的段地址的内存单元的地址为:N*4+2。

(N从0开始。)

中断过程

可以用中断类型码,在中断向量表中找到中断处理程序的入口。

找到这个入口地址的最终目的是用它设置CS和IP,使CPU执行中断处理程序。

用中断类型码找到中断向量,并用它设置CS和IP,这个工作是由CPU的硬件自动完成的。

CPU硬件完成这个工作的过得被称为中断过程。

CPU收到中断信息后,要对中断信息进行处理,首先将引发中断过程。硬件在完成中断过程后,CS:IP将指向中断处理程序的入口,CPU开始执行中断处理程序。

8086CPU在收到中断信息后,所引发的中断过程:

1)(从中断信息中)取得中断类型码;

2)标志寄存器的值入栈;(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中。)

3)设置标志寄存器的第8位TF和第9位IF的值为0;

4)CS的内容入栈;

5)IP的内容入栈;

6)从内存地址为中断类型码*4和中断类型码*4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS。

CPU在收到中断信息之后,到处理该中断信息,就完成一个由硬件自动执行的中断过程(程序员无法改变这个过程中所要做的工作)。

中断过程的主要任务就是用中断类型码在中断向量表中找到中断处理程序的入口地址,设置CS和IP。

因为中断处理程序执行完成后,CPU还要回过头来继续执行被中断的程序,所以要在设置CS、IP之前,先将它们的值保存起来。

因为在执行中断处理程序后,需要恢复在进入中断处理程序之前的CPU现场(某一时刻,CPU中各个寄存器的值)。所以应该在修改标记寄存器之前,将它的值入栈保存。

我们更简洁地描述中断过程,如下:

1) 取得中断类型码N;

2) pushf

3) TF=0,IF=0

4) push CS

5) push IP

6) (IP)=(N*4), (CS)=(N*4+2)

在最后一步完成后,CPU开始执行由程序员编写的中断处理程序。

中断处理程序

由于CPU随时随地可能检测到中断信息,也就是说,CPU随时随地可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段究竟之中。而中断处理程序的入口地址,即中断向量,处理存储在对应的中断向量表表项中。

中断处理程序的编写方法和子程序的比较相似,常规步骤如下:

1) 保存用到的寄存器。

2) 处理中断。

3) 恢复用到的寄存器。

4) 用iret指令返回。

iret指令的功能用汇编语法描述为:

pop IP

pop CS

popf

iret通常和硬件自动完成的中断过程配合使用。

iret指令执行后,CPU回到执行中断处理程序前的执行点继续执行程序。

除法错误中断的处理

当CPU执行div等除法指令的时候,如果发生了除法溢出错误,将产生中断类型码为0的中断信息,CPU将检测到这个信息,然后引发中断过程,转去执行0号中断所对应的中断处理程序。

mov ax,1000h

mov bh,1

div bh

编程处理0号中断

编程:当发生除法溢出时,在屏幕中间显示“overflow!”,返回DOS。

分析:

(1)当发生除法溢出的时候,产生0号中断信息,从而引发中断过程。

此时,CPU将进行以下工作:

1)取得中断类型码0;

2)标志寄存器入栈,TF、IF设置为0;

3)CS、IP入栈;

4)(IP)=(0*4), (CS)=(0*4+2)。

(2)可见,当中断0发生时,CPU将转去执行中断处理程序。

只要按如下步骤编写中断处理程序,当中断0发生时,即可显示“overflow!”。

1)相关处理。

2)向显示缓冲区送字符串“overflow!”。

3)返回DOS。

我们将这段程序称为:do0。

(3)现在的问题是:do0应存放在内存中。因为除法溢出随时可能发生,CPU随时都可能将CS:IP指向do0的入口,执行程序。

那么do0应该放在哪里呢?

由于我们是在操作系统之上使用计算机,所有的硬件资源都在操作系统的管理之下,所以我们要想得到一块内存区存放do0,应该向操作系统申请。

n         但在这里出于两个原因我们不想这样做:

n         过多地讨论申请内存将偏离问题的主线;

我们学习汇编的一个重要目的就是要获得对计算机底层的编程体验。所以,在可能的情况下,我们不去理会操作系统,而直接面向硬件资源。

问题变得简单而直接,我们只需找到一块别的程序不会用到的内存区,将do0传送到其中即可。

内存0000:0000~0000:03E8,大小约1KB的空间是系统存放中断处理程序入口地址的中断向量表。8086支持256个中断,但是,实际上,系统中要处理的中断事件远没有达到256个。所以在中断向量表中,有许多单元是空的。

中断向量表是PC系统中最重要的内存区,只用来存放中断处理程序的入口地址,DOS系统和其他应用程序都不会随便使用这段空间。我们可以利用中断向量表中的空闲单元来存放我们的程序。一般情况下,从0000:0200至0000:0300的256个字节。

结论:我们可以将do0传送到内存0000:0200处。

(4)我们将中断处理程序do0放到0000:0200后,若要使得除法溢出发生的时候,CPU转去执行do0,则必须将do0的入口地址,即0000:0200登记在中断向量表的对庆表项中。因为除法溢出对应的中断类型码为0,它的中断处理程序的入口地址应该从0×4字单元开始存放,段地址存放在0×4+2字单元中,偏移地址存放在0×4字单元中。也就是说要将do0的段地址0存放在0000:0002字单元中,将偏移地址200H存放在0000:0000字单元中。

总结上面的分析,我们要做以下几件事情:

(1)编写可以显示“overflow!”的中断处理程序:do0;

(2)将do0送入内存0000:0200处;

(3)将do0的入口地址0000:0200存储在中断向量表0号表项中。

程序的框架如下:

assume cs:code

code segment

start: do0安装程序

设置中断向量表

mov ax,4c00h

int 21h

do0: 显示字符串“overflow!”

mov ax,4c00h

int 21h

code ends

end start

上面的程序分为两部分:

(1)安装do0,设置中断向量的程序;

(2)do0。

do0的代码是不执行的,它只是作为do0安装程序所要传送的数据。

首先执行do0安装程序,将do0的代码拷贝到内存0:200处,然后设置中断向量表,将do0的入口地址,即偏移地址200H和段地址0,保存在0号项中。这两部分工作完成后,程序就返回了。

程序的目的就是在内存0:200处安装do0的代码,将0号中断处理程序的入口地址设置为0:200。do0的代码虽然在程序中,却不在程序执行的时候执行。它是在除法溢出发生的时候才得以执行的中断处理程序。

do0部分代码的最后两条指令是依照我们的编程要求,用来返回DOS的。

现在,我们再反过来从CPU的角度看一下,什么是中断处理程序?我们来看一下do0是如何变成0号中断的中断处理程序的:

(1)主程序在执行时,被加载到内存中,此时do0的代码在程序所在的内存空间中,它只是存放在主程序代码段中的一段要被传送到其他单元中的数据,我们不能说它是0号中断的中断处理程序;

(2)主程序中安装do0的代码执行完后,do0的代码被从主程序的代码段中拷贝到0:200处。此时,我们也不能说它是0号中断的中断处理程序,它只不过是存放在0:200处的一些数据;

(3)主程序中设置中断向量表的代码执行完后,在0号表项中填入了do0的入口地址0:200,此时0:200处的信息,即do0的代码,就变成了0号中断的中断处理程序。因为当除法溢出(即0号中断)发生时,CPU将执行0:200处的代码。

回忆以下:

如何让一个内存单元成为栈顶?将它的地址放入SS、SP中;

如何让一个内存单元中的信息被CPU当作指令来执行?将它的地址放入CS、IP中;

如何让一段程序成为N号中断的中断处理程序?将它的入口地址放入中断向量表的N号表项中。

安装

使用movsb指令,将do0的代码送入0:200处。程序如下:

assume cs:code

code segment

start:    设置es:di指向目的地址

设置ds:si指向源地址

设置cx为传输长度

设置传输方向为正

rep movsb

设置中断向量表

mov ax,4c00h

int 21h

do0: 显示字符串“overflow!”

mov ax,4c00h

int 21h

code ends

end start

用rep movsb指令的时候要确定的信息如下:

(1)传送的原始位置,段地址:code,偏移地址:offset do0;

(2)传送的目的位置:0:200;

(3)传送的长度:do0部分代码的长度;

(4)传送的方向:正向。

更明确的程序如下:

assume cs:code

code segment

start:    mov ax,cs

mov ds,ax

mov si,offset do0          ;设置ds:si指向源地址

mov ax,0

mov es,ax

mov di,200h                 ;设置es:di指向目的地址

mov cx do0部分代码的长度              ;设置cx为传输长度

cld                                             ;设置传输方向为正

rep movsb

设置中断向量表

mov ax,4c00h

int 21h

do0:     显示字符串“overflow!”

mov ax,4c00h

int 21h

code ends

end start

如何知道do0代码的长度?

可以利用编译器来计算do0的长度,如下所示:

assume cs:code

code segment

start:    mov ax,cs

mov ds,ax

mov si,offset do0          ;设置ds:si指向源地址

mov ax,0

mov es,ax

mov di,200h                 ;设置es:di指向目的地址

mov cx,offset do0end-offset do0         ;设置cx为传输长度

cld                                             ;设置传输方向为正

rep movsb

设置中断向量表

mov ax,4c00h

int 21h

do0:     显示字符串“overflow!”

mov ax,4c00h

int 21h

do0end: nop

do0end:

code ends

end start

“-”是编译器识别的运算符号,编译器可以用它来进行了两个常数的减法。比如:

指令:mov ax,8-4,被编译器处理为指令:mov ax,4

汇编编译器可以处理表达式,比如:

指令:mov ax,(5+3)*5/10,被编译器处理为指令:mov ax,4

do0

do0程序的主要任务是显示字符串,程序如下:

do0: 设置ds:si指向字符串

mov ax,0b800h

mov es,ax

mov di,12*150+36*2            ;设置es:di指向显存空间的中间位置

mov cx,9                             ;设置cx为字符串长度

s:   mov al,[si]

mov es:[di],al

inc si

add di,2

loop s

do0end:nop

mov ax,4c00h

int 21h

程序写好了,可要显示的字符串放在哪里呢?

assume cs:code

data:segment

db “overflow!”

data ends

code segment

start:    mov ax,cs

mov ds,ax

mov si,offset do0          ;设置ds:si指向源地址

mov ax,0

mov es,ax

mov di,200h                 ;设置es:di指向目的地址

mov cx, offset do0end-offset do0 ;设置cx为传输长度

cld                               ;设置传输方向为正

rep movsb

设置中断向量表

mov 4c00h

int 21h

do0:     mov ax,data

mov ds,ax

mov si,0                ;设置ds:si指向字符串

mov ax,0b800h

mov es,ax

mov di,12*160+36*2     ;设置es:di指向显存空间中的中间位置

mov cx,9                      设置cx为字符串长度

s:     mov al,[si]

move s:[di],al

inc   si

add di,2

loop s

mov ax,4c00h

int 21h

do0end:nop

code ends

end start

上述程序有一处大错,注意字符串“overflow!”,是在data段中。程序执行完成后返回,它所占用的内存空间被系统释放,而在其中存放的“overflow!”也将很可能被别的信息覆盖。

而do0程序被放到了0:200处,随时都会因发生了除法溢出而被CPU执行,很难保证do0程序从原来程序所处的空间中取得的是要显示的字符串“overflow”。

因为do0程序随时可能被执行,而它要用到字符串“overflow”,所以该字符串也应该放在一段不会被覆盖的空间中。正确的程序如下:

assume cs:code

code segment

start:    mov ax,cs

mov ds,ax

mov si,offset do0          ;设置ds:si指向源地址

mov ax,0

move s,ax

mov di,200h                 ;设置es:di指向目的地址

mov cx,offset do0end-offset do0         ;设置cx为传输长度

cld                               ;设置传输方向为正

rep movsb

设置中断向量表

mov 4c00h

int 21h

 do0:    jmp short do0start

              db “overflow!”

do0start:     mov ax,cs

mov ds,ax

mov si,202h           ;设置ds:si指向字符串

mov ax,0b800h

mov es,ax

mov di,12*160+36*2     ;设置es:di指向显存空间的中间位置

mov cx,9                      ;设置cx为字符串长度

s:     mov al,[si]

mov es:[di],al

inc si

add di,2

loop s

mov ax,4c00h

int 21h

do0end:nop

code ends

end start

上面的程序,将字符串“overflow!”放到了do0程序中,在程序执行时,将标号do0到标号do0end之间的内容送到0000:0200处。

注意,因为在do0程序开始处的“overflow!”不是可以执行的代码,所以在“overflow!”之前加上一条jmp指令,转移到正式的do0程序,当除法溢出发生时,CPU执行0:200处的jmp指令,路过后面的字符串,转到正式的do0程序执行。

do0程序执行过程中必须要找到“overflow!”,那么它在哪里呢?首先来看段地址,”overflow!”和do0的代码处于同一个段中,而除法溢出发生时,CS中必须存放do0的段地址,也就是“overflow!”的段地址;再来偏移地址,0:200处的指令为jmp short do0start,这条指令占两个字节,所以“overflow!”的偏移地址为202h。

设置中断向量

将do0的入口地址0:200,写入中断向量表的0号表项中,使do0成为0号中断的中断处理程序。

0号表项的地址为0:0,其中0:0字单元存放偏移地址,0:2字单元存放段地址。程序如下:

mov ax,0

mov es,ax

mov word ptr es:[0*4],200h

mov word ptr es:[0*4+2],0

单步中断

CPU在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1,则它所引发的中断过程如下:

(1)取得中断类型码1;

(2)标志寄存器入栈,TF、IF设置为0;

(3)CS、IP入栈;

(4)(IP)=(1*4),(CS)=(1*4+2)。

如果TF=1,则执行一条指令后,CPU就要转去执行1号中断处理程序。

在进入单步中断处理程序之前,设置TF=0,从而避免CPU在执行中断处理程序的时候发生单步中断。这就是为什么在中断过程中有TF=0这个步骤,再来看一下中断过程:

CPU为什么要提供这样的功能呢?

CPU提供单步中断功能的原因就是,为单步跟踪程序的执行过程,提供了实现机制。

响应中断的特殊情况

一般情况下,CPU在执行完当前指令后,如果检测到中断信息,就响应中断,引发中断过程。

但是,在有些情况下,CPU在执行完当前执行后,即便是发生中断,也不会响应。比如:

在执行完向ss寄存器传送数据的指令后,即便是发生中断,CPU也不会响应。这样做的主要原因是,ss:sp联合指向栈顶,而对它们的设置应该连续完成。

汇编学习--7.16--中断相关推荐

  1. 汇编学习--7.16--外中断

    以前我们讨论的都是CPU对指令的执行. 我们知道,CPU在计算机系统中,除了能够执行指令,进行运算以外,还应该能够对外部设备进行控制,接收它们的输入,向它们进行输出.也就是说,CPU除了有运算能力外, ...

  2. 8086汇编学习小记-王爽汇编语言实验12

    8086汇编学习小记-王爽汇编语言实验12 0号中断处理程序,开始安装在0000:0200处的程序最后用死循环导致显示不出'divided error',改成直接退出就正常显示了.注意修改ss,sp之 ...

  3. 8086汇编学习之[BX],CX寄存器与loop指令,ES寄存器等

    同类学习笔记总结: (一).8086汇编学习之基础知识.通用寄存器.CS/IP寄存器与Debug的使用 (二).8086汇编学习之DS寄存器.SS/SP寄存器 一.汇编程序的基本格式: 1.基本格式与 ...

  4. 汇编学习笔记——汇编指令

    目录 汇编指令 nop指令 mov.add.sub指令 adc.sbb指令 and.or指令 移位指令 逻辑左/右移指令 循环左/右移指令 算术左/右移指令 带进位循环左/右移指令 inc指令 pus ...

  5. 汇编学习(1)——基础知识

    汇编学习(1)--基础知识 ---谨以此系列文章记录我的汇编学习.  关于汇编 说起汇编语言,那自然不得不想到机器语言,在汇编语言尚未诞生之际,程序猿们只能非常苦逼的敲着0和1,还要记住一大堆复杂难记 ...

  6. 寄存器---汇编学习笔记

    第二章 寄存器 2.0 寄存器的绪论 一个典型的CPU由运算器.控制器.寄存器(CPU工作原理)等器件构成.内部总线实现 CPU 内部各个器件之间的联系,外部总线实现CPU和主板其他器件的联系. 在C ...

  7. c语言里10h代表什么,汇编中的10H中断int 10h详细说明

    汇编中的10H中断是由BIOS对显示器和屏幕所提供的服务程序.使用int 10h服务程序时,必须先指定ah寄存器为以下显示服务编号之一,以指定需要调用的功用. 显示服务 (Video Service: ...

  8. SHU汇编程序设计常见考点、易错点总结与综合实例、汇编学习资源

    文章目录 SHU汇编程序设计常见考点.易错点总结与综合实例.汇编学习资源 一.常见考点 1.寻址方式 2.移位 3.乘除运算 4.加减操作 5.取址操作 6.出入栈 7.中断 8.标志位 9.常见的字 ...

  9. 15 计算机底层——二进制到汇编学习

    计算机底层--二进制到汇编学习 1.概述 语言 机制 进制如何计算 二进制 数据宽度 有符号和无符号数 原码和反码 位运算 位运算计算 汇编 寄存器 内存 汇编指令 内存复制 堆栈的指令 汇编如何写函 ...

  10. 二进制到汇编学习-狂神说-雁迟

    [狂神说]最通俗易懂的计算机底层教学,二进制到汇编学习!视频地址:https://www.bilibili.com/video/BV1ni4y1G7B9 概述 语言 人和人沟通?语言!老外!计算机!学 ...

最新文章

  1. Linux虚拟内存和物理内存精华【美】
  2. C# 利用Jmail接收邮件
  3. 是否要入坑强化学习,看了这篇文章再说
  4. centos 程序 mysql数据库文件位置,CentOS 更改MySQL数据库目录位置
  5. 【bzoj4712】洪水
  6. 【HDU - 5477】A Sweet Journey(思维,水题)
  7. 工具的使用——谷歌浏览器(chrome) (二)
  8. mysql 把一列转多行_Oralce 按分隔符把一列转成多行
  9. java 货币格式 转换_java格式化数值成货币格式示例
  10. LaTeX目录格式控制
  11. 报名 | 智慧数据云平台与新型城镇化智库论坛
  12. 对其他小组作评的评价
  13. 产品 · B端生意的定义和分类
  14. Matlab画天球坐标图,知道方位角和高度角
  15. 【python 生成自己的二维码】推广二维码带log图片
  16. iOS模拟器不能输入中文解决
  17. Altium Designer快捷键
  18. 一部《小猪佩奇》让中国90%的家长感到惭愧
  19. 在Word2010中交叉引用插入参考文献
  20. 女人眼中最美的七种男人……

热门文章

  1. 802.11协议常用语缩写
  2. Android逆向基础笔记—Android中的常用ARM汇编指令
  3. 深度学习自学(一):Loss function 损失函数
  4. 华硕主板开机:loading asus express gate解决办法
  5. .net core sorteddictionary 排序_#键盘排序——为什么我们的键盘字母不是按照ABCD的顺序排列?...
  6. CentOS使用 Crontab定时任务清理程序日志
  7. MySQL数据库查询重复数据办法
  8. 编译原理---代码优化基础(自己看)
  9. cesium 基于在vue框架写功能
  10. 年月日_时间单位年月日是怎么得来的?