内中断

Tutorial: 汇编基础 Category: C语言 Published: 2026-04-07 13:58:26 Views: 20 Likes: 0 Comments: 0
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号就放在后面两个字, 23号继续, 依此类推.....

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为04. 先将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