0%

10:8086CPU_Learning(8)

今天继续学习汇编语言,争取这个星期之内学完8086CPU

内中断

CPU 具有一种能力,可以在执行当前的命令之后,检测到从CPU外部发送过来的或者内部产生的一种特殊信息,并且可以立即对接受到的信息进行某种处理。这种特殊的信息,我们称之为”中断信息”。中断的意思是要求CPU马上进行某种处理,并向需要进行处理的提供了必备的参数的通知信息

内中断的产生

对于8086CPU,我们有四种情况会产生中断信息:

  • 除法错误,比如除法溢出
  • 单步执行
  • 执行into 指令
  • 执行int 指令

对于不同的信息,我们需要进行不同的处理,那么我们必须知道中断信息的来源,所以中断信息中需要包含识别来源的编码。在8086中我们用称为中断类型码的数据来标识中断信息的来源。中断类型码为一个字节型数据,可以标识256种中断信息的来源。以后我们将中断信息的来源称之为中断源。上述的4种中断源,在8086中的中断类型码如下:

  • 除法错误 –> 0
  • 单步执行 –> 1
  • 执行into –> 4
  • 执行int指令,指令格式为 int n指令中n为字节型立即数,是提供给CPU 的中断类型码

中断处理

中断处理程序

CPU在收到中断信息之后,需要对中断信息进行处理。而我们编写的,用来处理中断信息的程序被称之为中断处理程序。一般对不同的中断信息我们会编写不同的处理程序

当我们收到中断信息后,应该前往中断处理程序进行处理。所以我们需要根据中断信息确定处理程序的入口。我们用下面的方式来进行对中断处理程序的段地址和偏移地址的查找

中断向量表

根据CPU的设计,中断类型码的作用就是用来定位中断处理程序的

我们通过中断向量表完成对中断类型的查找,中断向量表在内存中保存,其中放了256个中段源的处理程序、

image.png

所以只要知道了中断类型码,就可以通过查找中断向量表,找到处理程序的入口

所以现在,找到中断向量表成了首要条件。

中断向量表在内存中存放,对于8086PC机,中断向量表指定存放在内存地址0处。从内存0000:00000000:03FF的1024个单元中存放着中断向量表

中断过程

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

不过在执行完中断处理程序后,我们还需要返回原来的执行点继续执行下面的指令。

为了解决这个问题,CPU在收到中断类型之后,所引发的中断过程是这样的:

  • 获取中断类型码
  • 将标志寄存器中的值入栈(因为在中断过程中要改变标志寄存器的值,所以先要保存)
  • 设置标志寄存器的第8位TF和第9位IF的值为0
  • CS的内容入栈
  • IP的内容入栈
  • 从内存地址为中断类型码4和中断类型码4+2的两个字单元读取中断处理程序的入口地址,并设置

我们用以下方式更加简洁的描述这个中断的过程:

  • 取得中断类型码N
  • pushf
  • TF = 0,IF = 0
  • push CS
  • push IP
  • (IP) = (N*4)(CS) = (N*4+2)

最后开始执行由程序员编写的中断处理程序

中断处理程序和iret指令

由于CPU随时都可能检测到中断信息,所以CPU随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存中的某段空间中。而中断处理程序的入口地址,即中断向量,必须存储在中断向量表项中

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

  • 保存用到的寄存器
  • 处理中断
  • 恢复用到的寄存器
  • 用iret指令返回

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

  • pop IP
  • pop CS
  • popf

iret的出栈顺序和执行中断过程中断的入栈顺序正好相反

除法错误中断的处理

触发触发错误

编程处理0号中断

我们改变一下0号中断处理程序的功能,即重新编写一个0号中断处理程序,他的功能是在屏幕中显示”overflow”,然后返回操作系统

我们分析一下需求:

  1. 引发中断过程
  • 取得中断类型码0
  • 标志寄存器入栈,TF,IF设置为0
  • CSIP入栈
  • (IP) = (0*4) (IP) = (0*4+2)
  1. 中断处理过程(我们将此程序称为do0)
  • 相关处理
  • 向显示缓冲区送字符串”overflow!”
  • 返回DOS
  1. 存放do0到电脑内存空间中

​ 如果存储在其他内存空间中,可能会导致内存内容被覆盖。所以我们将其放在中断向量表中的后面的空余部分,这是因为中断向量表支持256个中断,但是在实际的操作过程中,后面的数据基本不会被使用。所以在中断向量表中,许多单元是空的。我们使用这些程序对我们的中断处理程序进行存放。

  1. 中断处理程序do0的存放

​ 我们将中断处理程序放到0000:0200后,此时0000:0200是我们中断处理程序的入口,我们需要把0号中断向量表的地址设置为该入口的地址

综上我们需要,进行以下的任务:

  • 编写可以显示”overflow!“的中断处理程序
  • 将do0送入内存0000:0200
  • 将do0的入口地址0000:0200存储在中断向量表0项中

程序实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
assume cs:code
code segment
start:
;设置es:di指向目标地址
mov ax,0
mov es,ax
mov di,200H
;设置ds:si指向源地址
mov ax,cs
mov ds,ax
mov si,offset do0
;设置cx为源程序长度
mov cx,offset do0end-offset do0;利用编译器计算do0代码字节长度
;设置方向为正
rep movsb

;设置中断向量表
mov ax,0
mov es,ax
mov word ptr es:[0*4],200H
mov word ptr es:[0*4+2],0d

mov ax,4c00H
int 21H

do0:
jmp short do0start
db "overflow!"

do0start:
;显示字符串"overflow!"
;设置ds:si指向字符串
mov ax,cx
mov ds,ax
mov si,142H
;设置es:di指向显存空间的中间位置
mov ax,0b800H
mov es,ax
mov di,12*160+36*2

mov cx,9
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

首先我们将这段程序进行编译之后成可执行程序,我们运行程序对0号中断处理程序进行修改

然后编写一个有除法溢出错误的程序即可

由于这里的显存内容不断的被刷新,所以会出现看不到警告的问题,但是没关系

单步中断

分析以下单步中断的中断过程

我们知道当CPU检测到TF的值为1时,进行1号中断处理程序,如果此时TF仍然为1,那么在执行中断程序时,会重新进入1号中断处理程序,这样如此往复,会出现各种问题。为了解决这个问题,我们采取以下方法:

  • 取得中断类型码N
  • 标志寄存器入栈,TF=0,IF=0
  • CS,IP入栈
  • (IP)=(N*4),(CS) = (N*4+2)

通过这种方法,我们就可以实现CPU的单步中断功能

响应中断的特殊情况

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

但是也有特殊情况,在执行完向ss寄存器中传送数据的指令后,即使发生中断,也不会响应。这是因为 ss:sp联合指向栈顶,所以对他们的设置应该联合完成,如果只设置了SS,而没有更新SP,那么此时指向的是一个错误的栈顶。所以CPU在执行设置ss的指令之后不会响应中断,而是向后继续执行一条指令,这样的话为连续设置栈顶提供了一个机会(当然你也可以执行其他指令)。

所以这样就可以解释为什么之前提到的,设置SS之后会继续向后执行一条命令。