• 沒有找到結果。

正如上面的程序所示,将当前显示页和编辑页分开[用 setvisualpage()和 setactivepage()函 数],在编辑页上画好图形后,立即令该页变为显示页显示,然后在上次的显示页上(现在变 为编辑页)进行画图,画好后,又再次交换,如此编辑页和显示页反复地交换,在观察者的视 觉上,就出现了动画的效果。要让页的交替速度快,唯一的办法是缩短在页上的画图时间。

4.4 中断技术

键盘原理:在键盘上按下一个键位时,键盘上的处理器首先向计算机主机发出硬件中断 请求,然后将该键位的扫描码以串行的方式传送给计算机主机,计算机主机在硬件中断的作 用下,调用 INT09H 中断把键盘送来的扫描码读入,并转换为 ASCII 码值存入键盘缓冲区。

按下一个键位,送出一个闭合码,键位被释放时送出一个断开码,键盘处理中断程序从键盘 I/O 端口(端口地址为 60H)读取一个字节的数据,如果读取的数据的第 7 位为 1 时表示按 键已放开(送出断开码),如第 7 位为0表示键按下(送出闭合码),数据的第0~6 位则是 按键的扫描码。在编程中是通过 bioskey()函数使用原有的键盘中断程序。使用 PC 原有的 键盘处理程序可以很方便地处理键盘,但是因为它是调用 BIOS,所以反应比较慢,另外当 要同时处理几个按键时,原有的键盘中断程序就不能满足要求,这时就需要编写一个适合要 求的键盘中断程序。

bioskey()库函数实际是调用了 BIOS 中断 INT16H 的功能,同样也可通过 int86()直接调用 INT16H。总之,还是调用了系统的中断服务程序。在大型程序的实现中往往需要自己编写中 断。在中断向量表中,有些中断向量,用户是可以在自己的程序中使用的,如 60~67H 号中 断。绝对地址为 180~19FH 的一段,是专为用户保留的,它可用作用户的软中断。例如,想 使用中断号为 60H 的软中断,那么用户只要在 4×60H=180H 和 181H 处填入中断服务程序 的偏移地址,在 182H~183H 单元填入段地址即可。这样在程序中执行到 geninterrupt(0x60) 时,便执行这个中断服务程序。对于硬件中断,若要通过 IRQi 线产生硬中断,用户必须制作 接口电路,以能产生由低到高的中断请求信号,并通过插头接到微机的扩充槽相应的 IRQ5 脚 上,用户可用的是 IRQ2、IRQ10、IRQ11、IRQ12 和 IRQ15,它们的中断向量号分别是 10、

72、73、74 和 77,因此可以在中断向量表中填入硬中断服务程序的段和偏移值。上述的硬件 中断向量号若不用作硬件中断时,也可将其当作软件中断,不过产生中断要靠发软件中断命令,

此时相应的 IRQi 脚就无作用了。

这部分将介绍这一更高级的中断技术,即如何用 Turbo C 实现自己的中断服务。

4.4.1 编写中断程序

用 Turbo C 2.0 实现编写中断程序的方法可用 3 部分来实现,即编写中断服务程序、安装 中断服务程序、激活中断服务程序。

我们的任务是,当产生中断后,脱离被中断的程序,使系统执行中断服务的程序,它必 须打断当前执行的程序,急需完成一些特定操作,因此该程序中应包括一些能完成这些操作的 语句和函数。由于产生中断时,必须保留被中断程序中断时的一些现场数据,即保存断点,这 些值都在寄存器中(若不保存,当中断服务程序用到这些寄存器时,将改变它的值),以便恢 复中断时,使这些值复原,以继续执行原来中断了的程序。

Turbo C 2.0 为此提供了一种新的函数类型 interrupt,它将保存由该类型函数参数指出的 各寄存器的值,而在退出该函数,即中断恢复时,再复原这些寄存器的值,因而用户的中断服 务程序必须定义成这种类型的函数。如中断服务程序名定为 myp,则必须将这个函数说明成 这样:

void interrupt myp(unsigned bp,unsiened di,unsigned si,unsigned ds, unsigned es,unsigned dx,unsigned cx,unsigned bx,

unsigned ax,unsigned ip,unsigned cs,un3igned flags);

若是在小模式下的程序,只有一个段,在中断服务程序中用户就可以像用无符号整数变 量一样,使用这些寄存器。若中断服务程序中不使用上述的寄存器,也就不会改变这些寄存器 原来的值,因而也就不需保存它们,这样在定义这种中断类型的函数时,可不写这些寄存器参 数,如可写成:

void interrupt myp() {

}

对于硬中断,则在中断服务程序结束前要送中断结束命令字给系统的中断控制寄存器,

其口地址为 0x20,中断结束命令字也为 0x20,即 outportb(0x20,0x20);

在中断服务程序中,若不允许别的优先级较高的中断打断它,则要禁止中断,可用函数 disable()来关闭中断。

若允许中断,则可用开中断函数 enable()来开放中断。

4.4.2 安装中断服务程序

定义了中断服务函数后,还需将这个函数的入口地址填入中断向量表中,以便产生中断 时程序能转入中断服务程序去执行。为了防止正在改写中断向量表时,又产生别的中断而导致 程序混乱,可以关闭中断,当改写完毕后,再开放中断。一般的,常定义一个安装函数来实现 这些操作,如:

void install(void interrupt (*faddr)(),int inum) {

disable();

setvect(intnum,faddr);

enable();

}

其中 faddr 是中断服务程序的入口地址,其函数名就代表了入口地址,而 inum 表示中 断类型号。setvect()函数就是设置中断向量的函数,上述定义的 install()函数,将完成把中断 服务程序入口地址填入中断向量 inum 中去。

setvect(intnum,faddr):把第 intnum 号中断向量指向所指的函数,也即指向 faddr()这个 函数。注意:faddr()必须是一个 interrupt 类型函数。

getvect(intnum):返回第 int num 号中断向量的值,即 intnum 号中断服务程序的进入地 址(4B 的 far 地址)。该地址是以 pointer to function 的形式返回的。

getvect()使用的方式为:ivect =getvect(intnum);其中 ivect 是一个 function 的 pointer,存 放 function 的地址,其类型必须是 interrupt 类型,由于声明时,此 pointer 无固定值,所以 冠以 void。

4.4.3 中断服务程序的激活

当中断服务程序安装完后,如何产生中断,从而执行这个中断服务程序呢?对硬件中断,

就要在相应的中断请求线(IRQi,i=0,l,2,…,7)产生一个由低到高的中断请求电平,这 个过程必须由接口电路来实现,但如何激励它产生这个电平呢?可以用程序来控制实现,如发 命令[outportb(口地址,命令)]。然后主程序等待中断,当中断产生时,便去执行中断。

由于中断类型的函数不同于用户定义的一般函数,因此也不能用调用一般函数的方法来调 用它,这样一般软中断调用可用以下方法:

(1)使用库函数geninterrupt(中断类型号)

在主函数中适当的地方,用 setvect 函数将中断服务程序的地址写入中断向量表中,然后 在需要调用的地方用 geninterrupt()函数调用。

(2)直接调用

如已用 setvect(类型号,myp)设置了中断向量值,则可用 myp();直接调用,或用指向地址 的方法调用:

(* myp)();

(3)也可用在 Turbo C 程序中插入汇编语句的方法来调用,如:

setvect (inum,myp) ...

asm int inum;

...

不过用这种方法的程序生成执行程序稍麻烦点。

通常上述的调用可定义成一个中断激活函数来完成,该函数中可附加一些别的操作,主 程序在适当的地方调用它就可以了。

(4)恢复被修改的中断向量

这一步视情况而定,当用户采用系统已定义过的中断向量,并且将其中断服务程序进行 了改写,或用新的中断服务程序代替了原来的中断服务程序,为了在主程序结束后,恢复原来 的中断向量以指向原中断服务程序,可以在主程序开始时,存下原中断向量的内容,这可以用 取中断向量函数 getvect()来实现,如 j=(char *)getvect(0xlc),这样 j 指针变量中将是 0xlc 中 断服务程序的入口地址,由于 DOS 已定义了 0xlc 中断的服务程序入口地址,但它是一条无 作用的中断服务,因而可以利用 0xlc 中断来完成一些用户想执行的操作,实际上就是用户自 己的中断服务程序代替了原来的。当主程序要结束时,为了保持系统的完整性,可以恢复原来 的中断服务入口地址,如可用 setvect(0xlc,j),也可以调用 install()函数再一次进行安装。一 般情况下可以不加这一步。

PC286、386、486 上使用的硬中断请求信号和对应的中断向量号与外设的关系如表 4-14 所示。

表 4-14 C X86 硬中断搞求信号与中断向量号及外设的关系

中断请求信号 中断向量号 使用的设备

IRQ0 8 系统定时器

IRQ1 9 键盘

IRQ2 10 对 XT 机保留,AT 总线扩充为 IRQ8~15

IRQ3 11 RS-232C(COM2)

IRQ4 12 RS-232C(COMl)

IRQ5 13 硬盘中断

IRQ6 14 软盘中断

IRQ7 15 打印机中断

IRQ8 70 实时时钟中断

IRQ9 71 软中断方式重新指向 IRQ2

IRQ10 72 保留

IRQ11 73 保留

IRQ12 74 保留

IRQ13 75 协处理器中断

IRQ14 76 硬盘控制器

IRQ15 77 保留

中断优先级从高到低排列顺序为 IRQ0、IRQl、IBQ 2、IRQ8、IRQ9、IRQ10、IRQ1l、IRQ12、

IRQ13、IRQ14、IRQ15、IRQ3、IRQ4、IRQ5、IRQ6 和 IRQ7。

4.4.4 应用——硬中断演示秒表程序

例 4-21 是一个硬中断程序,我们知道 PC 系统以每秒 18.2 次的频率进行时钟硬中断(使 用中断请求 IRQ8),即执行 8 号中断。这个中断周而复始地在进行,在它的中断服务程序中除 了进行日时钟计数和磁盘驱动器超时检测控制外,接着又进行 0xlc 的软中断调用,0xlc 软中 断只有一条返回指令,它不做什么事情,因而可以改写它的内容,使其变为一个有用的软中断 服务程序。在这个例子中,利用每秒 18.2 次的定时硬中断。每秒要调用 18.2 次的软中断 0xlc,

将中断 0xlc 中断服务程序改写为对进入该中断的次数进行计数的程序,每到 18 次时,在屏幕 的右上角开一个窗口(window(50,1,54,3)),在窗口的中间位置显示 0~9 十个数字中的 一个,频率接近于秒表数(不过只显示 10 个数)。由于这是一个硬中断演示程序,计时并不准 确,若要精确计时,则应 91 次 0xlc 中断为 5 秒。

该程序中用 programsize 表示程序长度,设置为 400 节,由于在小模式下,Turbo C 使用 64K 的一个段。

例 4-21 一个硬中断程序。

#include <dos.h>

#include <conio.h>

#define programsize 400

#define TRUE 1

void interrupt(*oldtimer)();

void interrupt newtimer();

void install();

static struct SREGS seg;

int b=0;int b1=0;

unsigned intsp,intss;

unsigned myss,stack,x0,y0;

int busy=0;

void on_timer();

void goxy();

void rexy();

void prt();

main() { char ch;

char *p;

p=(char *)newtimer;

on_timer(p);

while(TRUE) { ch=getch();

switch(ch)

{ /*键盘输入 0、1 或 q 进行功能选择*/

case '0': install(oldtimer); break;

case '1': b1=47; install(newtimer); break;

case 'q': install(oldtimer); exit(1);

default: printf("%c",ch); /*若是其他字符就打印出来*/

}

}

void interrupt newtimer() {

rg.h.ah=2;

rg.h.dl=y;

rg.h.dh=x;

rg.h.bh=0;

int86(0x10,&rg,&rg);

}

void rexy() /*得到光标坐标*/

{ union REGS rg;

rg.h.ah=3;

rg.h.bh=0;

int86(0x10,&rg,&rg);

y0=rg.h.dl;

x0=rg.h.dh;

}

void prt(p) /*显示字符*/

{ union REGS rg;

rg.h.al=p;

rg.h.ah=14;

int86(0x10,&rg,&rg);

}

相關文件