第十五章:外中断02
让编程改变世界
Change the world by program
小甲鱼和大家谈谈心
一个帖子引发小甲鱼的反省! 猫姐曾经说过,步子别迈太大,容易扯着蛋! 结果还真蛋疼了…… 因此,小甲鱼要学会淡定面对,不能忘记当初的宗旨!做视频也好、做网站也好,对得住大家才对得住自己的良心! 最后:希望大家继续支持鱼C、支持小甲鱼,看到大家都能坦诚相待,很开心,很幸福!编写int 9 中断例程
复习一下前边的内容中,我们可以总结出键盘输入的处理过程:
(1)键盘产生扫描码; (2)扫描码送入60h 端口; (3)一旦侦测到60h端口有动静,引发9 号中断; (4)CPU执行int 9 中断例程处理键盘输入。 以上的过程,前三步都由硬件系统自动完成。我们能够改变的只有第四步,修改int 9 终端程序。 但是,在这门课程中,我们不准备完整地编写一个键盘中断的处理程序,因为要涉及到一些硬件细节,而这些内容脱离了我们的内容主线。 插入语:如果有兴趣想更为深入的学习汇编语言,探究汇编语言的奥妙,可以关注小甲鱼今后推出的《The Art of Assembly Language》。但是,我们却还要编写新的键盘中断处理程序,来进行一些特殊的工作,那么这些硬件细节如何处理呢?
如果单纯要完成这点还是相对比较简单的,因为BIOS 提供的int 9中断例程已经对这些硬件细节进行了处理。 我们只要在自己编写的中断例程中调用BIOS 的int 9中断例程就可以了。任务演示:在屏幕中间依次显示 “a”~“z” ,并可以让人看清。在显示的过程中,按下Esc键后,改变显示的颜色。
我们先来看一下如何依次显示“a”~“z”:
[codesyntax lang="asm"]assume cs:code code segment start: mov ax,0b800h mov es,ax mov ah,'a' s: mov es:[160*12+40*2],ah inc ah cmp ah,'z' jna s mov ax,4c00h int 21h code ends end start[/codesyntax] 我们发觉,因为一个字母刚显示到屏幕上,CPU执行几条指令后,就又变成了另一个字母,字母之间切换得太快,因此我们无法看清。 理想状况是:我们应该在每显示一个字母后,延时一段时间,让人看清后,再显示下一个字母。
那么如何延时呢?
不如……我们让CPU 执行一段时间的空循环。有时候让它做点无用功哈~ 请看源代码并试图分析作者的做法:相关代码下载 现在显示“a”~“z”的任务我们基本完成了,并做到可以让人看清,虽然做法有些无耻…… 那么接下来将进一步来实现:按下 Esc 键后,改变显示的颜色!怎么办呢? 键盘输入到达60h 端口后,就会引发 9号中断,CPU 则转去执行int 9中断例程。 我们可以编写int 9中断例程,功能如下: (1)从60h端口读出键盘的输入; (2)调用BIOS 的int 9 中断例程,处理其他硬件细节; (3)判断是否为Esc的扫描码,如果是,改 变显示的颜色后返回;如果不是则直接返回。接下来,我们对这些功能的实现一一进行分析!
第一步:从端口60h读出键盘的输入
in al,60h第二步:调用BIOS的int 9中断例程
注:有一点要注意的是,我们写的中断处理程序要成为新的int 9中断例程,主程序必须要将中断向量表中的int 9中断例程的入口地址改为我们写的中断处理程序的入口地址。 那么在新的中断处理程序中调用原来的int 9中断例程时,中断向量表中的int 9中断例程的入口地址却不是原来的int 9 中断例程的地址。所以我们不能使用int 指令直接调用。 这里有必要解释一下:。。。。。。 对于我们现在的问题,假设我们将原来int 9中断例程的偏移地址和段地址保存在ds:[0]和ds:[2]单元中。 那么我们在需要调用原来的int 9中断例程时候,就可以在 ds:[0]、ds:[2] 单元中找到它的入口地址。那么,有了入口地址后,我们如何进行调用呢?
当然不能使用指令int 9来调用。我们可以用别的指令来对int指令进行一些模拟,从而实现对中断例程的调用。 我们来看,int 指令在执行的时候,CPU 进行下面的工作: (1)取中断类型码n; (2)标志寄存器入栈; (3) IF=0,TF=0; (4) CS 、IP 入栈; (5)(IP) = (n*4),(CS) = (n*4+2)。 取中断类型码是为了定位中断例程的入口地址,在我们的问题中,中断例程的入口地址已经知道。 所以,我们用别的指令模拟int 指令时候,不需要做第(1)步。 在假设要调用的中断例程的入口地址在ds:0和ds:2单元中的前提下,我们将int 过程用下面几步模拟: (1)标志寄存器入栈; (2)IF=0,TF=0; (3)CS、IP入栈; (4)(IP)=((ds)*16+0),(CS)=((ds)*16+2)。 可以注意到第(3)、(4)步和call dword ptr ds:[0]的功能一样。 call dword ptr ds:[0] 的功能也是: (1)CS 、IP 入栈; (2)(IP)=((ds)*16+0),(CS)=((ds)*16+2)。 如果这点上有疑问的童鞋,不妨可以复习下10.6节的内容。所以经过我们总结后,int 过程的模拟最终变为:
(1)标志寄存器入栈; (2)IF=0,TF=0; (3)call dword ptr ds:[0] 对于(1),可用pushf实现。 对于(2),我们又得动点歪脑筋,没办法,资源条件极其卑劣的8086 要么使人放弃,要么逼出天才!我们可用以下程序间接实现: 实现IF=0,TF=0步骤:pushf
pop ax
and ah,11111100b ; IF和OF为标志寄存器的
; 第9位和第8位
push ax
popf
这样,模拟int指令的调用功能,调用入口地址在ds:0、ds:2中的中断例程的程序如下:pushf ;标志寄存器入栈
pushf
pop ax
and ah,11111100b ; IF和OF为标志寄存器的第9
; 位和第8位
push ax
popf ;IF=0、TF=0
call dword ptr ds:[0]