内中断
关于中断信息:
如果cpu在执行完当前正执行的指令后,检测到了中断信息,将不会执行接下来的指令,而是立即处理中断信息;
中断信息可以来自cpu内部和外部;
来自cup内部的中断信息称为内中断;
1.内中断的产生
8086cpu产生内中断信息的情况:
1】除法错误;比如执行div指令产生的除法溢出
2】单步执行
3】执行into指令
4】执行int指令
中断源:
cpu需要知道中断信息的来源,以此来区别不同的中断信息;
中断信息中包含有中断类型码;
中断类型码为一个字节型数据,可表示256种中断信息来源;
例如:四种内中断对应的中断类型码
除法错误 ->0
单步执行 ->1
执行into指令 ->4
int n ->n
2.处理中断信息
1)中断处理程序
cpu收到中断信息后,需要对中断信息进行处理;
用来处理中断信息的程序称为中断处理程序;
一般来说,要对不同的中断信息编写不同的中断处理程序;
2)中断向量表
中断信息中包含8位的中断类型码;
需要通过中断类型码来找到对应的中断处理程序;
cpu用8位中断类型码通过中断向量表找到相应的中断处理程序入口地址;
也就是说:中断向量表就是中断处理程序的入口地址列表;
cpu在处理中断时,必需找到中断向量表;
在8086cpu机器中,中断向量表存放在内存0处;
内存地址:0000:0000~0000:03FF,之间1024个内存单元中用来存放中断向量表;
一个表向存放一个中断向量,即中断处理程序的入口地址;
一个表项占两个字;
高地址字存放段地址,低地址字存放偏移地址;
3)中断过程
在处理中断时,cup硬件会自动完成下面的过程:
1】通过中断类型码查看中断向量表;
2】找到对应的表项,从而确定中断处理程序的入口地址;
3】将cs:ip设为表项中对应的值,以此来让cpu执行中断处理程序;
cpu完成该过程的工作称为中断过程;
4)处理中断信息的流程
8086cpu在收到中断信息后,将执行下面的流程:
1】从中断信息中获取中断类型码n;
2】标志寄存器的值入栈;
3】标志寄存器第8位TF、第9位IF置0;
5】CS的值入栈;
6】IP的值入栈;
7】cs的值设为4*n,ip的值设为4*n+2;
完成该流程后,cpu开始执行中断处理程序;
3.关于中断处理程序
由于cpu随时可能会检测到中断信息;
也就是说中断处理程序随时可能执行;
因此,中断处理程序必须一直储存在一段内存空间中;
中断处理程序的入口地址必须储存在中断向量表中;
中断处理程序的编写:
1】保存用到的寄存器
2】处理中断
3】恢复用到的寄存器
4】用iret指令返回
iret指令
iret
作用:
调用中断处理程序前,需要将标志寄存器、cs、ip的值入栈;
当执行完后,需要将这些值出栈还原;
iret指令就用来做这件事;
相当于:
pop ip
pop cs
popf
iret指令执行完后,cpu回到中断处理程序执行前的执行点,继续向下执行;
4.编写0号中断的处理程序
1)分析
0号中断对应的是除法溢出;
当发生除法溢出时,cpu做如下工作:
1】取得中断类型码,也就是0;
2】标志寄存器入栈、TF和IF置0;
3】cs和ip入栈;
4】cs和ip的值设为中断向量表中的值,即:cs=4*0,ip=4*0+2;
cs:ip会指向中断处理程序,也就是cpu会开始执行中断处理程序;
编写中断处理程序do0;
do0做如下操作:
相关处理;打印错误信息;返回dos;
因为除法溢出随时可能发生,因此do0随时可能被调用;
为了让do0一直存在于内存中,do0需要放在其他程序用不到的内存区域;
为了方便,可以将do0放在0000:0200处;
理由如下:
0000:0000~0000:03ff是系统存放中断向量表的区域,因此一般不会被其它程序修改;
8086支持256个中断但实际上,系统要处理的中断数远小于该值,也就是说,该段内存很多都是没被使用的;
do0程序不会占用太大的内存空间;
通过分析,我们需要做如下工作:
1】编写中断处理程序do0;
2】将do0送入内存0000:0200处;
3】将do0的入口地址,也就是0000:0200存放在中断向量表的0号表项中;
总的来说,要编写一个汇编程序,实现如下功能:
1】安装do0 ->主要是将do0代码复制到0000:0200处,可以用rep movsb指令来实现;
2】实现do0 ->主要是打印错误提示信息,也就是将字符串送入到内存地址b8000处;
2)实现
中断处理程序:
assume cs:code
code segment
start: mov ax,code
mov ds,ax
mov si,offset do0 ;ds:si指向do0用来做串传送
mov ax,0
mov es,ax
mov di,200h ;es:di指向0000:0200h
cld ;标志位df置0,正向传送,也就是传送一个字节后si、di加1
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],0
mov ax,4c00h ;返回dos
int 21h
do0: jmp short do0Start ;为了字符串不被其它程序覆盖,也需要放在db0中
db "hello world!",0
do0start:mov ax,cs
mov ds,ax
mov si,202h ;ds:si指向字符串,也就是do0执行后要打印的字符串
mov ax,0b800h
mov es,ax
mov di,12*160+36*2 ;在12行的中间输出,由编译器来做数学运算
s: mov ch,0
mov cl,byte ptr[si]
jcxz ed ;如果遇到0就跳出循环
mov al,[si]
mov ah,02h ;黑底绿字
mov es:[di],ax
inc si
add di,2
loop s
ed: nop
iret
mov ax,4c00h ;do0结束后返回dos
int 21h
do0end: nop ;标记do0结束,用来让编译器计算do0的字节数
code ends
end start
测试用的除法溢出程序:
assume cs:code
code segment
start: mov ax,1000h
mov bh,01h
div bh
mov ax,4c00h
int 21h
code ends
end start
测试结果:
可以看到发生除法溢出后,输出了“hello world!”,表示执行了自定义的中断处理程序;
5.单步中断
单步中断类型码为1;
单步中断产生:
在cpu执行完一条指令后,如果检测到标志寄存器的TF标志位的值为1,则产生单步中断;
1)debug与单步中断
debug 的 t 命令用来单步调试程序;
当t命令执行时,debug将TF标志位置1;
cpu检测到单步中断;
执行中断处理程序1,作用是显示所有寄存器的内容、并且等待命令输入;
2)关于进入中断处理程序前的TF置0
当tf=1时,cpu执行完当前指令后进入中断;
单中断处理程序也是由一条条指令组成;
如果在执行中断处理前tf=1,则在执行一条中断处理指令后将再次引发单步中断;
以此类推,会造成死循环;
为了避免这种情况,在进入中断处理程序之前,中断过程会将tf置0;
6.响应中断的特殊情况
一般情况下,cpu在执行当前指令后,如果检测到中断信息就会立即响应中断;
但有些情况下例外;
例如:执行完向ss寄存传送数据的指令后,即使发生中断cpu也不会响应;
原因:
ss:sp指向栈顶;
在中断过程中,需要将cs:ip压入栈;
此时ss改变了,但sp还没变,ss:sp指向的不是正确的栈顶,将引起错误;
因此,在设置ss:sp时应该将设置sp的指令紧接设置ss的指令;
mov ax,1000h
mov ss,ax
mov sp,0
而不应该
mov ax,1000h
mov ss,ax
mov ax,200
mov sp,0