第十二章 内中断

引言

本书主要讲解硬件中断。

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的中断过程:

  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开始执行由程序员编写的中断处理程序。

动画演示中断过程(13:45处)

12.5 中断处理程序和iret指令

中断处理程序,常规的步骤:

  1. 保存用到的寄存器;
  2. 处理中断;
  3. 恢复用到的寄存器;
  4. 用 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截图:

屏幕快照 2018-10-15 16.12.28

屏幕快照 2018-10-15 16.13.08

可以看到引发除法溢出 Divide overflow。

8086除法指令DIV,IDIV

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

rep movs汇编指令的问题

当我们的程序 program2.asm 执行过后,另外一个程序触发了0号中断,就会显示出字符串“Welcom to Fishc.com!”,而不是除法溢出提示。

  • 实际演示截图

    1. 在没有运行 program2.exe 前,debug除法溢出程序 test.exe 单步跟踪提示除法溢出 Divide overflow。编译、链接 program2.asm

      屏幕快照 2018-10-15 16.47.16

    2. 运行 program2.exe 之后,再 debug test.exe

      屏幕快照 2018-10-15 16.49.26

12.9 do0

12.10 设置中断向量

12.11 单步中断

标志寄存器的 TF 位为 1,则CPU产生单步中断。

CPU 提供单步中断功能的原因就是,为单步跟踪的执行过程,提供了实现机制。

12.12 相应中断的特殊情况

原文地址:https://www.cnblogs.com/hacker-x/p/9792037.html