引言
本书主要讲解硬件中断。
12.1 内中断的产生
12.2 中断处理程序
12.3 中断向量表
- 中断向量表在内存中存放,对于8086PC机,中断向量表指定存放在内存地址0处。
- 从内存0000:0000到0000:03FF的1024个单元中存放着中断向量表。为什么是1024个字节呢?我们回忆,段地址和偏移地址分别都是16位的,它俩经过组合构成一个20位的物理地址。16位相当于2个字节,我们要得到物理地址就要分别存放段地址和偏移地址,所以一个物理地址需要4个字节(也就是4个内存单元)来存放,因为中断向量表中有256个中断,一个中断对应一个物理地址,所以需要256*4=1024个字节,即1024个内存单元。
- 我们在第五章有讲,内存0000:00000000:03FF,大小为1KB的空间是系统存放中断处理程序入口地址的中断向量表。一般情况下,从0000:02000000:02FF(这是256个字节,即256个内存单元)的256个字节的空间所对应的中断向量表项都是空的,操作系统的其它应用程序都不占用。
12.4 中断过程
8086CPU的中断过程:
- (从中断信息中)取得中断类型码;
- 标志寄存器的值入栈(保护标志位);
- 设置标志寄存器的第8位 TF 和第9位 IF 的值为 0;
- CS寄存器的内容入栈;
- IP寄存器的内容入栈;
- 从内存地址为中断类型码*4和中断类型码*4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS。
更简洁的描述中断过程,如下:
- 取得中断类型码N;
- pushf
- TF = 0,IF = 0
- push CS
- push IP
- (IP) = (N*4),(CS) = (N*4+2)
在最后一步完成后,CPU开始执行由程序员编写的中断处理程序。
动画演示中断过程(13:45处)
12.5 中断处理程序和iret指令
中断处理程序,常规的步骤:
- 保存用到的寄存器;
- 处理中断;
- 恢复用到的寄存器;
- 用 iret 指令返回。
iret 指令的功能用汇编语法描述为:
pop IP
pop CS
popf
iret 通常和硬件自动完成的中断过程配合使用。可以看到,在中断过程中,寄存器入栈的顺序是标志寄存器、CS、IP,而 iret 的出栈顺序是IP、CS、标志寄存器,刚好和入栈对应,实现了用执行中断处理程序前的CPU现场恢复标志寄存器和CS、IP的工作。iret 指令执行后,CPU回到执行中断处理程序前的执行点继续执行程序。
12.6 除法错误中断的处理
除法溢出对应0号中断。
我们编写程序 test.asm
测试除法溢出:
assume cs:codesg
codesg segment
start:
mov ax,1000h ;被除数1000
mov bh,1 ;除数1
div bh ;bh决定了该除法为8位除法
;商寄存器 al 为 8 位,存储无符号数除法所得商范围 0~255
;1000/1=1000,很明显在 al 中无法存放,引发除法溢出
codesg ends
end start
div 指令可以做除法。当进行 8 位除法的时候,用 al 存储结果的商,ah 存储结果的余数;进行 16 位除法的时候,用 ax 存储结果的商,dx 存储结果的余数。
test.asm
编译,链接和Debug截图:
可以看到引发除法溢出 Divide overflow。
12.7 编程处理0号中断
12.8 安装
更详细的程序框架:
assume cs:code
code segment
start:
;设置ds:si指向源地址
;设置es:di指向目的地址
;设置cx为传输长度
;设置传输方向为正
rep movsb
;设置中断向量表
mov ax,4c00h
int 21h
do0:
;显示字符串"Welcome to Fishc.com!"
mov ax,4c00h
int 21h
code ends
end start
更明确的的程序:
assume cs:code
code segment
start:
;设置ds:si指向源地址
mov ax,cs
mov ds,ax
mov si,offset do0
;设置es:di指向目的地址
mov ax,0
mov es,ax
mov di,200h
;设置cx为传输长度
mov cx,do0部分代码的长度
;设置传输方向为正
cld
rep movsb
;设置中断向量表
mov ax,4c00h
int 21h
do0:
;显示字符串"Welcome to Fishc.com!"
mov ax,4c00h
int 21h
code ends
end start
接着就是计算do0部分代码的长度:
assume cs:code
code segment
start:
;设置ds:si指向源地址
mov ax,cs
mov ds,ax
mov si,offset do0
;设置es:di指向目的地址
mov ax,0
mov es,ax
mov di,200h
;设置cx为传输长度
mov cx,offset do0end - offset do0
;设置传输方向为正
cld
rep movsb
;设置中断向量表
mov ax,4c00h
int 21h
do0:
;显示字符串"Welcome to Fishc.com!"
mov ax,4c00h
int 21h
do0end: nop
code ends
end start
接下来是do0程序,do0程序的主要任务是显示字符串,程序如下:
do0:;显示字符串"Welcome to Fishc.com!"
mov ax,0b800h
mov es,ax
mov di,12*160+36*2 ;设置es:di指向显存空间的中间位置
mov cx,21 ;设置cx为字符串长度
s: ;把字符串中的字符一个一个的拷贝过去
mov al,[si]
mov es:[di],al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
do0end: nop
现在,我们得到了相对完整的汇编程序版本 program1.asm
:
assume cs:code
data segment
db "Welcome to Fishc.com!"
data ends
code segment
start:
;设置ds:si指向源地址
mov ax,cs
mov ds,ax
mov si,offset do0
;设置es:di指向目的地址
mov ax,0
mov es,ax
mov di,200h
;设置cx为传输长度
mov cx,offset do0end - offset do0
;设置传输方向为正
cld
rep movsb
;设置中断向量表
mov ax,4c00h
int 21h
do0:;显示字符串"Welcome to Fishc.com!"
mov ax,0b800h
mov es,ax
mov di,12*160+36*2 ;设置es:di指向显存空间的中间位置
mov cx,21 ;设置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
程序 program1.asm
看似合理,可实际上却大错特错。
注意,“Welcom to Fishc.com!”在程序 program1
的data段中。程序 program1
执行完成后返回,它所占用的内存空间被系统释放,而在其中存放的“Welcom to Fishc.com!”也将很可能被别的信息覆盖。而do0程序被放到了0000:0200处,随时都会因发生了除法溢出而被CPU执行,很难保证do0程序从原来程序 program1
所处的空间中取得的是要显示的字符串“Welcom to Fishc.com!”,因为字符串“Welcom to Fishc.com!”是存放在数据段的,随时可能被覆盖,不是一段安全的内存空间。因为 program1
执行完,它的数据段就被释放了。
故,由于do0程序随时可能被执行,而它要用到字符串“Welcom to Fishc.com!”,所以该字符串也应该存放在一段不会被覆盖的空间中。解决思路是把字符串存放到0000:0200处,也就是do0程序在内存中的地址0000:0200处。
do0: ;显示字符串"Welcome to Fishc.com!"
jmp short do0start ;执行到此处,CPU直接跳到do0start执行
db "Welcome to Fishc.com!" ;在代码段里存放数据。这算是“歪门邪道”
do0start:
mov ax,0b800h
mov es,ax
mov di,12*160+36*2 ;设置es:di指向显存空间的中间位置
这样修改后,我们的字符串就能跟随do0保存在安全空间中,但是问题又来了。do0程序执行过程中必须找到“Welcom to Fishc.com!”,那么它在哪里呢?
首先来看段地址,“Welcom to Fishc.com!”和do0的代码处于同一个段中,而除法溢出发生时,CS中必然存放do0的段地址,也就是“Welcom to Fishc.com!”的段地址;再来看偏移地址,0000:0200处的指令为 jmp short do0start
,这条指令占两个字节,所以“Welcom to Fishc.com!”的偏移地址为0202h。
最后,我们将do0的入口地址0000:0200写入中断向量表的0号表项中,使do0成为0号中断的中断处理程序。0号表项的地址为0000:0000,其中0000:0000字单元存放偏移地址;0000:0002字单元存放段地址。
完整程序实现 program2.asm
:
assume cs:code
code segment
start:
;设置ds:si指向源地址
mov ax,cs
mov ds,ax
mov si,offset do0
;设置es:di指向目的地址
mov ax,0
mov es,ax
mov di,200h
;设置cx为传输长度
mov cx,offset do0end - offset do0
;设置传输方向为正
cld
rep movsb
;设置中断向量表
mov ax,0
mov es,ax
mov word ptr es:[0*4],200h
mov word ptr es:[0*4+2],0
mov ax,4c00h
int 21h
do0: ;显示字符串"Welcome to Fishc.com!"
jmp short do0start ;执行到此处,CPU直接跳到do0start执行
db "Welcome to Fishc.com!" ;在代码段里存放数据。这算是“歪门邪道”
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,21 ;设置cx为字符串长度
s: ;把字符串中的字符一个一个的拷贝过去
mov al,[si]
mov es:[di],al
inc si
add di,1
mov al,02h ;设置颜色
mov es:[di],al
add di,1
loop s
mov ax,4c00h
int 21h
do0end: nop
code ends
end start
当我们的程序 program2.asm
执行过后,另外一个程序触发了0号中断,就会显示出字符串“Welcom to Fishc.com!”,而不是除法溢出提示。
-
实际演示截图
-
在没有运行
program2.exe
前,debug除法溢出程序test.exe
单步跟踪提示除法溢出 Divide overflow。编译、链接program2.asm
。 -
运行
program2.exe
之后,再 debugtest.exe
。
-
12.9 do0
12.10 设置中断向量
12.11 单步中断
标志寄存器的 TF 位为 1,则CPU产生单步中断。
CPU 提供单步中断功能的原因就是,为单步跟踪的执行过程,提供了实现机制。