int 指令
中断信息可以来自 CPU 内部和外部,当 CPU 的内部有需要处理的时候发生的时候,将产生需要马上处理的中断信息,引发中断过程。这里,我们讲解另外一种重要的内中断,由 int 指令引发的中断。
13.1 int 指令
int 指令的格式为:int n,n 为中断类型码,它的功能是引发中断过程。
CPU 执行 int n 指令,相当于引发一个 n 号中断的中断过程,执行过程如下。
(1)取中断类型码 n;
(2)标志寄存器入栈,IF=0,TF=0;
(3)CS、IP 入栈;
(4)(IP)=(n*4),(CS)=(n*4+2)。
可以在程序中使用 int 指令调用任何一个中断的中断处理程序。从此转去执行 n 号中断处理程序。例如,下面的程序。
assume cs:code code segment start: mov ax,0b800h mov es,ax mov byte ptr es:[12*160+40*2],'!' int 0 code ends end start
这个程序在 windows 2000 中的 DOS 方式下执行时,将在屏幕中间显示一个 "!",然后显示 "Divide overflow" 后返回系统中。我们并没有做除法运算,产生 "Divide overflow" 的原因是我们调用了 0 号中断程序。
可见,int 指令的最终功能和 call 指令相似,都是调用一段程序。
一般情况下,系统将一些具有一定功能的子程序,以中断处理程序的方式提供给应用程序调用。我们在编程的时候,可以用 int 指令调用这些子程序。当然,也可以自己编写一些中断处理程序供别人使用。
13.2 编写供应用程序调用的中断例程
问题一:编写、安装中断 7ch 的中断例程
功能:求一 word 型数据的平方
参数:(ax)=要计算的数据
返回值:dx、ax 中存放结果的高 16 位和低 16 位
应用举例:求 2*3456^2。
assume cs:code code segment start: mov ax,3456 ;(ax)=3456 int 7ch ;调用中断 7ch 的中断例程,计算 ax 中的数据的平方 add ax,ax adc dx,dx ;dx:ax 存放结果,将结果乘以 2 mov ax,4c00h int 21h code ends end start
分析一下,我们要做以下 3 部分工作。
(1)编写实现求平方功能的程序;
(2)安装程序,将其安装在 0:200 处;
(3)设置中断向量表,将程序的入口地址保存在 7ch 表项中,使其成为中断 7ch 的中断例程。
安装程序如下。
assume cs:code code segment start: mov ax,cs mov ds,ax mov si,offset sqr ;设置 ds:si 指向源地址 mov ax,0 mov es,ax mov di,200h ;设置 es:di 指向目的地址 mov cx,offset sqrend-offset sqr ;设置 cx 为传输长度 cld ;设置传输方向为正 rep movsb mov ax,0 mov es,ax mov word ptr es:[7ch*4],200h mov word ptr es:[7ch*4+2],0 mov ax,4c00h int 21h sqr: mul ax iret sqrend: nop code ends end start
注意,在中断例程 sqr 的最后,要使用 iret 指令。用汇编语法描述,iret 指令的功能为:
pop IP
pop CS
popf
CPU 执行 int 7ch 指令进入中断例程之前,标志寄存器、当前的 CS 和 IP 被压入栈中,在执行完中断例程后,应该用 iret 指令恢复 int 7ch 执行前的标志寄存器和 CS、IP 的值,从而接着执行应用程序。
int 指令和 iret 指令的配合使用与 call 指令和 ret 指令的配合使用具有相似的思路。
问题二:编写、安装中断 7ch 的中断例程。
功能:将一个全是字母,以 0 结尾的字符串,转化为大写。
参数:ds:si 指向字符串的首地址。
应用举例:将 data 段中的字符串转化为大写。
assume cs:code data segment db 'conversation',0 data ends code segment start: mov ax,data mov ds,ax mov si,0 int 7ch mov ax,4c00h int 21h code ends end start
安装程序如下:
assume cs:code code segment start: mov ax,cs mov ds,ax mov si,offset capital mov ax,0 mov es,ax mov di,200h mov cx,offset capitalend - offset capital cld rep movsb mov ax,0 mov es,ax mov word ptr es:[7ch*4],200h mov word ptr es:[7ch*4+2],0 mov ax,4c00h int 21h capital: push cx push si change: mov cl,[si] mov ch,0 jcxz ok and byte ptr [si],11011111b inc si jmp short change ok: pop si pop cx iret capitalend: nop code ends end start
在中断例程 capital 中用到了寄存器 si 和 cx,编写中断例程和编写子程序的时候具有同样的问题,就是要避免寄存器的冲突。应该注意例程中用到的寄存器的值的保存和恢复。
13.3 对 int、iret 和栈的深入理解
问题:用 7ch 中断例程完成 loop 指令的功能。
loop s 的执行需要两个信息,循环次数和到 s 的位移,所以,7ch 中断例程要完成 loop 指令的功能,也需要这两个信息作为参数。我们用 cx 存放循环次数,用 bx 存放位移。
应用举例:在屏幕中间显示 80 个 '!'。
assume cs:code code segment start: mov ax,0b800h mov es,ax mov di,160*12 mov bx,offset s - offset se ;设置从标号 se 到标号 s 的转移位移 mov cx,80 s: mov byte ptr es:[di],'!' add di,2 int 7ch ;如果(cx)≠0,转移到标号 s 处(偏移地址bx) se: nop mov ax,4c00h int 21h code ends end start
在上面的程序中,用 int 7ch 调用 7ch 中断例程进行转移,用 bx 传递转移的位移。
分析:为了模拟 loop 指令,7ch 中断例程应该具备以下的功能。
(1)dec cx;
(2)如果 (cx)≠0,转到标号 s 处执行,否则向下执行。
int 7ch 引发中断过程后,进入 7ch 中断例程,在中断过程中,当前的标志寄存器、CS 和 IP 都要入栈,此时压入的 CS 和 IP 中的内容,分别是调用程序的段地址(可以认为是标号 s 的段地址)和 int 7ch 后一条指令的偏移地址(即标号 se 的偏移地址)。
可见,在中断例程中,可以从栈里取得标号 s 的段地址和标号 se 的偏移地址,而用标号 se 的偏移地址加上 bx 中存放的转移位移就可以得到标号 s 的偏移地址。
现在知道,可以从栈中直接和间接地取得标号 s 的段地址和偏移地址,那么如何用它们设置 CS:IP 呢?
可以利用 iret 指令,我们将栈中的 se 的偏移地址加上 bx 的转移位移,则栈中的 se 的偏移地址就变为了 s 的偏移地址。我们再使用 iret 指令,用栈中的内容设置 CS、IP,从而实现转移到标号 s 处。
7ch 中断例程如下。
lp: push bp mov bp,sp dec cx jcxz lpret add [bp+2],bx lpret: pop bp iret
因为要访问栈,使用了 bp,在程序开始处将 bp 入栈保存,结束时出栈恢复。当要修改栈中 se 的偏移地址的时候,栈中的情况为:栈顶处是 bp 原来的数值,下面是 se 的偏移地址,再下面是 s 的段地址,再下面是标志寄存器的值。而此时,bp 中为栈顶的偏移地址,所以((ss)*16+(bp)+2) 处为 se 的偏移地址,将它加上 bx 中的转移位移就变为 s 的偏移地址。最后用 iret 出栈返回,CS:IP 即从标号 s 处开始执行指令。
如果 (cx)≠0,则不需要修改栈中 se 的偏移地址,直接返回即可。CPU 从标号se 处向下执行指令。
13.4 BIOS 和 DOS 所提供的中断例程
在系统板的 ROM 中存在着一套程序,称为 BIOS(基本输入输出系统),BIOS 中主要包含以下几部分内容。
(1)硬件系统的检测和初始化程序;
(2)外部中断和内部中断的中断例程;
(3)用于对硬件设备进行 I/O 操作的中断例程;
(4)其他和硬件系统相关的中断例程。
操作系统 DOS 也提供了中断例程,从操作系统的角度来看,DOS 的中断例程就是操作系统向程序员提供的编程资源。
BIOS 和 DOS 在所提供的中断例程中包含了许多子程序,这些子程序实现了程序员在编程的时候经常需要用到的功能。程序员在编程的时候,可以用 int 指令直接调用 BIOS 和 DOS 提供的中断例程,来完成某些工作。
和硬件设备相关的 DOS 中断例程中,一般都调用了 BIOS 的中断例程。
13.5 BIOS 和 DOS 中断例程的安装过程
BIOS 和 DOS 所提供的中断例程是如何安装到内存中的呢?
(1)开机后,CPU 一加电,初始化 (CS)=0FFFFH,(IP)=0,自动从 FFFF:0 单元开始执行程序。FFFF:0 处有一条跳转指令,CPU 执行该指令后,转去执行 BIOS 中的硬件系统检测和初始化程序。
(2)初始化程序将建立 BIOS 所支持的中断向量,即将 BIOS 提供的中断例程的入口地址登记在中断向量表中。注意,对于 BIOS 所提供的中断例程,只需将入口地址登记在中断向量表中即可,因为它们是固化到 ROM 中的程序,一直在内存中存在。
(3)硬件系统检测和初始化完成后,调用 int 19h 进行操作系统的引导。从此将计算机交由操作系统控制。
(4)DOS 启动后,除完成其他工作外,还将它所提供的中断例程装入内存,并建立相应的中断向量。
13.6 BIOS 中断例程应用
下面我们举几个例子,来看一下 BIOS 中断例程的应用。
int 10h 中断例程是 BIOS 提供的中断例程,其中包含了多个和屏幕输出相关的子程序。
一般来说,一个供程序员调用的中断例程中往往包含多个子程序,中断例程内部用传递进来的参数来决定执行哪一个子程序。BIOS 和 DOS 提供的中断例程,都用 ah 来传递内部子程序的编号。
下面看一下 int 10h 中断例程的设置光标位置功能。
mov ah,2 ;置光标
mov bh,0 ;第 0 页
mov dh,5 ;dh 中放行号
mov dl,12 ;dl 中放列号
int 10h
(ah)=2 表示调用第 10h 号中断例程的 2 号子程序,功能为设置光标位置,可以提供光标所在的行号(80*25 字符模式下: 0~24)、列号(80*25 字符模式下: 0~79),和页号作为参数。
(bh)=0,(dh)=5,(dl)=12,设置光标到第 0 页,第 5 行,第 12 列。
bh 中页号的含义:内存地址空间中,B800H~BFFFFH 共 32KB 空间,为 80*25 彩色字符模式的显示缓冲区。一屏的内容在显示缓冲区中共占 4000 个字节。
显示缓冲区分为 8 页,每页 4KB(≈4000B),显示器可以显示任意一页的内容。一般情况下,显示第 0 页的内容。也就是说,通常情况下,B800H~B8F9H 中的 4000 个字节的内容将出现在显示器上。
再看一下 int 10h 中断例程的在光标位置显示字符功能。
mov ah,9 ;在光标位置显示字符
mov al,'a' ;字符
mov bl,7 ;颜色属性
mov bh,0 ;第 0 页
mov cx,3 ;字符重复个数
int 10h
(ah)=9 表示调用第 10h 号中断例程的 9 号子程序,功能为在光标位置显示字符,可以提供要显示的字符、颜色属性、页号、字符重复个数作为参数。
13.7 DOS 中断例程应用
int 21h 中断例程是 DOS 提供的中断例程,其中包含了 DOS 提供给程序员在编程时调用的子程序。
我们前面一直使用的是 int 21h 中断例程的 4ch 号功能,即程序返回功能,如下:
mov ah,4ch ;程序返回
mov al,0 ;返回值
int 21h
(ah)=4ch 表示调用第 21 号中断例程的 4ch 号子程序,功能为程序返回,可以提供返回值作为参数。
我们看一下 int 21h 中断例程在光标位置显示字符串的功能:
ds:dx 指向字符串 ;要显示的字符串需要用 "$" 作为结束符
mov ah,9 ; 功能号 9,表示在光标位置显示字符串
int 21h
(ah)=9 表示调用第 21h 号中断例程的 9 号子程序,功能为在光标位置显示字符串,可以提供要显示的字符串地址作为参数。