1. 介绍
任何一种通用的 CPU 都有一种能力, 就是在执行完当前正在执行的任务后, 可以检测到 CPU 内部或外部的某种特殊信息,
并且立刻对所收到的信号进行处理。种特殊信息我们称为中断信息。
中断的意思是, 在执行完当前任务后, 不是继续去执行下一个任务, 而是转而去处理那个特殊信息。
2. 内中断的产生
中断信息的来源称为中断源。不同的中断信息对应不同的中断源, 所以在中断信息中要包含识别来源的编码, 这种编码称为中断类型码, 中断类型码为一个字节, 所以可以表示 256 种中信息来源, 也就是中断源。我们可以先看一下基本的 4 种中断源:
四种中断源, 在 8086CPU 中的中断类型码如下:
(1)除法错误:0
(2)单步执行:1
(3)执行 into 指令:4
(4)执行 int 指令, 该指令格式为 int n, 指令中的 n 为字节型立即数, 是提供给 CPU 的中断类型码。
3. 中断向量表
CPU在收到中断信息后就要转而去对中断信息进行处理, 而如何处理这个中断信息呢?
我们有一种程序专门用来处理中断信息, 叫做中断处理程序。
而对每一种不同的中断信息或者说中断源, 我们都有对应的不同的中断处理程序。
然而, 我们知道, 在CPU内要执行某个程序, 就要将CS:IP指向程序的入口, 那么我们的CPU是如何知道中断处理程序的入口的呢?
其实秘密就在中断类型码中, 因为不同的中断源对应不同的中断处理程序, 中断类型码其实就是用来定位中断处理程序的。
总的来说就是:中断源产生中断信息, 中断信息是用来定位处理这个中断源的中断处理程序的,
然后CPU根据中断信息中的信息找到中断处理程序, 执行程序。
但是这里还有一个问题, CPU是如何根据8位中断信息找到中断程序入口的呢?
答案就是中断向量表。中断向量表有点像路由器的转发表, 中断向量表中包含了每一个中断类型码以及它们对应的程序入口,
当CPU接收到中断信息后, 就到中断向量表中根据中断类型码查找中断处理程序的入口, 然而这里又有一个问题,
就是CPU需要找到中断向量表的位置才行。中断向量表在内存中存放, 是有固定位置的, 8086CPU的中断向量表在内存地址0处,
中断向量表的一个表项占两个字, 高位字存放段地址, 低位字存放偏移地址。中断向量表中存放地址是按照从小到大的,
就是说:0号中断类型码对应的中断处理程序的地址入口就放在从地址0开始的头两个字,
然后1号就放在后面两个字, 2号3号继续, 依此类推.....
0号中断源
中断处理程序的入口地址
1号中断源
中断处理程序的入口地址
2号中断源
中断处理程序的入口地址
...
中断向量表的范围: 0000:0000 => 0000:03FF
调用时: call dword ptr ds:[]
CS:IP 需要4个字节, 比如:
0号中断的地址
0:0*4 = IP
0:0*4 + 2 = CS
1号中断源的地址
0:1*4 = IP
0:1*4 + 2 = CS
4. 中断过程
在中断的处理中, 用中断类型码找到中断向量, 将CS:IP设置为中断处理程序入口, 这个过程称为中断过程,
这个过程是由CPU硬件自动处理的。这两个过程中间其实还有几步需要做, 然后这里还有一个问题需要考虑,
就是, CPU在执行完中断处理程序后需要返回执行原来被中断的程序, 所以这里肯定就要先将CS, IP还有一些其他相关的东西压入栈,
执行完中断处理程序后再从栈中弹出来恢复原来的CPU现场。所以这个中断过程我们可以分成这几步:
1. 从中断信息中取得中断类型码。
2. 将标志寄存器的值入栈。(因为后面要恢复CPU现场, 所以要先将相关的东西都储存起来)
3. 设置标志寄存器的第8位TF以及第9位IF为0。
4. 先将CS压入栈, 再把IP压入栈。
5. 将中断类型码在中断向量表中对应的程序入口地址设置为CS:IP的值。
下面是8086CPU在收到中断信息后, 所引发的中断过程:
(1)(从中断信息中)取得中断类型码;
(2)标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值, 所以先将其保存在栈中);
(3)设置标志寄存器的第8位TF和第9位IF的值为0;
(4)CS的内容入栈;
(5)IP的内容入栈;
(6)从内存地址为中断类型码*4和中断类型码*4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS。
更简洁地描述中断过程:
(1)取得中断类型码N;
(2)pushf
(3)TF=0, IF=0
(4)push CS
(5)push IP
(6)(IP)=(N*4), (CS)=(N*4+2)
中断过程完成后CPU就开始执行中断处理程序了。
5. 中断处理程序
中断处理程序也储存在某段内存中。中断处理程序一般为以下几个步骤:
1. 把用到的寄存器中的值压入栈(因为在中断过程中只将标志寄存器中的值压入了栈, 而因为中断程序执行完后要恢复CPU现场,
所以这里又要将中断程序要用到的寄存器中的值先保存起来, 程序执行完后再弹出来)
2. 处理中断
3. 出栈用到的寄存器(处理中断执行完了, 恢复寄存器的值)
4. iret iret的功能就是
pop ip
pop cs
popf
iret的功能正好和前面中断过程把标志寄存器以及CS,IP压入栈的顺序是对应的, 中断过程是先把标志寄存器压入栈,
然后把CS压入栈, 然后再把IP压入栈, 而iret的出栈顺序正好能把各个寄存器恢复原样。
6. 单步中断
单步中断也是一种中断, 单步中断的类型码为 1 。CPU提供这个功能就是给我们追踪程序执行的每一步提供了机制,
单步中断也是我们每次执行中断程序前在中断过程中要把TF设置为0的原因。原因如下:我们每次在debug中,
使用T 指令时它除了能执行指令, 还会显示此时给寄存器的值。这个显示各寄存器的值其实就是单步中断程序的执行结果,
单步中断程序的的中断源就是TF=1, 每次debug执行 t 命令时都会将TF设置为 1 ,
所以CPU在执行完 T 命令后就会转而去执行单步中断命令。但是也要注意CPU是一条一条指令处理的,
每执行完一条指令都会看一下要不要执行中断程序, 而单步中断程序也是由一条条指令组成的, 如果TF中的值一直为1,
那么执行完这条指令后又会转而去执行单步中断程序, 这样就会一直在执行单步中断程序的第一条指令。
所以我们所有中断程序执行前有个中断过程, 其中有一条就是把TF设置为0。
7. 响应中断的特殊情况
CPU在处理某些指令时如果发出中断信息, 那么就会响应中断, 然后引发中断过程。但是有些时候, 即使发生中断, 也不会有响应,
比如我们之前接触的 :当改变寄存器SS中的值的指令执行时, 它的下一条指令也将接着执行,
我们在debug中用 t 指令无法看到这一条指令的执行过程, 因为在改变SS的值后虽然发生了中断, 但是这个中断并没有响应。
但是为什么要这样做呢?
因为如果我们在执行完对SS的设置的指令后响应中断, 注意:这个时候我们只设置了SS就转而去执行处理中断程序了,
而我们还没有设置SP的值, 而我们的中断处理过程以及程序中, 都要将一些东西比如标志寄存器, CS,IP什么的压入栈,
而这个时候我我们的SS是改变了的, 而SP还没有改变,
所以这个时候我们的SS:SP指向的是一个错误的栈, 可能会引发一些不好的后果。
8. 0 号中断
assume cs:code, ss:stack, ds:data
data segment
db 128 dup (0)
data ends
stack segment stack
db 128 dup (0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,128
int 0
; -d 0:0 F
; 0000:0000 60 CA 00 F0 BB 13 21 08-F4 00 70 00 B1 13 21 08 `.....!...p...!.
; -u 0:0 F
; 0000:0000 60 DB 60
; 0000:0001 CA00F0 RETF F000
; 0000:0004 BB1321 MOV BX,2113
; 0000:0007 08F4 OR AH,DH
; 0000:0009 007000 ADD [BX+SI+00],DH
; 0000:000C B113 MOV CL,13
; 0000:000E 2108 AND [BX+SI],CX
; -r
; AX=0E2C BX=0000 CX=010F DX=0000 SP=0080 BP=0000 SI=0000 DI=0000
; DS=0E14 ES=0E14 SS=0E2C CS=0E34 IP=0008 NV UP EI PL NZ NA PO NC
; 0E34:0008 CD00 INT 00
; -t
; AX=0E2C BX=0000 CX=010F DX=0000 SP=007A BP=0000 SI=0000 DI=0000
; DS=0E14 ES=0E14 SS=0E2C CS=F000 IP=CA60 NV UP DI PL NZ NA PO NC
; F000:CA60 FE38 ??? [BX+SI] DS:0000=CD
; -t
; AX=0E2C BX=0000 CX=010F DX=0000 SP=007A BP=0000 SI=0000 DI=0000
; DS=0E14 ES=0E14 SS=0E2C CS=F000 IP=CA64 NV UP DI PL NZ NA PO NC
; F000:CA64 CF IRET
; -t
; AX=0E2C BX=0000 CX=010F DX=0000 SP=0080 BP=0000 SI=0000 DI=0000
; DS=0E14 ES=0E14 SS=0E2C CS=0E34 IP=000A NV UP EI PL NZ NA PO NC
; 0E34:000A B8004C MOV AX,4C00
mov ax,4c00h
int 21h
code ends
end start