学习总结之汇编延时函数

摘要:这篇博客里主要讲的是一些简单的延迟函数,不涉及中断向量的替换。定时功能将会在下一篇博客讲述。

86H号BIOS功能

  • 中断类型码:15H
  • 功能号:86H,延迟功能
  • 入口参数: CX:DX= 延迟时间(单位:微秒),CX为高16位,DX为低16位
  • 出口参数:CF=0 操作成功,AH=00H

但是,如果直接这样用的话,你会发现一些神奇的错误,比如我之前的博客(密码通行字),如果那个延迟功能,直接用下面这段代码替换:

DELAY  MACRO   TIME
       MOV     EAX, TIME
       MOV     DX, AX
       SHR     EAX, 16
       MOV     CX, AX
       MOV     AH, 86H
       INT     15H
       ENDM

你会发现,用户输入的密码是被正确写进数据块了,但是,如果将密码显示到屏幕上,密码竟然都是乱码,这是为什么呢?w(゚Д゚)w 明明写进去是正常的呀!

┗( T﹏T )┛ 然后,当时我就直接换了另一种方式实现延迟。。。

不开心,于是前几天就来调试了。。。密码通行字的程序太长了,为了方便调试,我单独写了个程序,代码就不贴了。下面是调试截图:
调试
看到那个 ??? [BX+SI] 了吗,一阵激动,怀疑是每次调用破坏了内存[BX+SI]单元。于是在数据段单独设置了一个字节交给它破坏,果然正常了。。。

附代码,出于个人习惯,前面一直写的是宏指令,其实比较长点的最好还是写成子程序。

;延时函数
;寄存器传参:EAX=延迟时间(微秒)
;内存数传参:TMP 是数据段中设置的妥协字节单元,专门用来给86H号BIOS功能破坏的
;被改动的寄存器 EAX,CX,DX;已保护的寄存器 BX,SI
DELAY  PROC
       PUSH    BX
       PUSH    SI
       MOV     BX, OFFSET TMP    ;TMP是内存数传参,是数据段中设置的妥协字节单元
       MOV     SI, 0
       MOV     DX, AX            ;EAX是寄存器传参,传递延迟时间,以微秒为单位
       SHR     EAX, 16
       MOV     CX, AX
       MOV     AH, 86H
       INT     15H
       POP     SI
       POP     BX
       RET
       ENDP

老样子,我保护现场只以例程为参考,所以只保护了BX和SI。如果需要保护很多寄存器的话,可以直接使用 PUSHA/POPA 指令,详见我的上一篇博客(汇编堆栈操作指令)。

但是,其实问题不在那句指令Σ( ° △ °|||)︴,因为,我调用其他中断类型为15H的杂项功能也没有出现这种情况,而那句指令是中断服务程序里的。再来看一个09H号DOS功能(中断类型为21H)的调试截图:
调试21H
可以看到,软中断21H也有这句指令 ??? [BX+SI] ,但是没有出现任何问题。所以,我觉得问题不是出在这句指令上,一定还有别的原因(ノへ ̄、)。但是我调试不出来,只知道它把DS:[BX+SI]单元内容破坏了。跪求路过的大佬在底下评论86H号BIOS功能破坏DS:[BX+SI]单元内容的原因o(≧口≦)o

其他方式

我之所以把86H号BIOS功能放在首部,是因为我比较推崇的这种实现方式。当然,还有很多其他的方法,比如利用2CH号DOS功能实现:

  • 中断类型:21H
  • 功能号:2CH,取时间
  • 入口参数:无
  • 出口参数:CH:CL=时:分,DH:DL=秒:1/100秒
;延时函数
;内存数传参,参数TIME=延迟时间(TIME<=32767),单位1/100s
;被改动的寄存器 AX, CX, DX, BP       
WAITS  PROC
       MOV     BP, TIME
       MOV     AH, 2CH             ;DH中存1s为单位,DL中为1/100s
       INT     21H
GTTE:  MOV     AL, 100
       MUL     DH
       MOV     DH, 0
       ADD     AX, DX
       PUSH    AX
       MOV     AH, 2CH
       INT     21H
       POP     CX
       PUSH    DX
       MOV     AL, 100
       MUL     DH
       MOV     DH, 0
       ADD     AX, DX
       POP     DX
       SUB     CX, AX
       CMP     CX, 0
       JLE     NOOU
       SUB     CX, 6000            ;过整分点,减6000纠正
NOOU:  ADD     BP, CX
       CMP     BP, 0
       JG      GTTE
       RET
WAITS  ENDP

上面的代码是计算的前后2次读取的时间差值,你可能认为这样没必要,只要判断前后2次时间是否一样就可以了,我之前也这样认为的。于是我把代码写出来验证一下:

;延时函数
;内存数传参:TIME=延迟时间(TIME<=32767),单位1/100s
;被改动的寄存器 AH, CX, DX, BP 
WAITP  PROC
       MOV     BP, TIME
       MOV     AH, 2CH         ;DH中存1s为单位,DL中为1/100s
       INT     21H
GTNT:  PUSH    DX
       INT     21H
       POP     CX
       CMP     CX, DX
       JZ      GTNT
       DEC     BP              ;前后读取到的时间不同,减1
       CMP     BP, 0
       JG      GTNT
       RET
WAITP  ENDP

但是,事与愿违,我期望的是延迟200ms,结果延迟了1s还多。。。那我就不懂了,怎么会这样?于是,我又单独把这个函数拿出来调试了:
调试1
调试2
可以看到虽然我们是连续取时间的,但是我们取到的时间却不是连续的。有的时候是间隔50ms,有的时候是间隔60ms,而我们每次只减了1,所以它实际延迟了1000多毫秒。。。

好了,到这里,原因就很明确了~系统的时间是日时钟计数器处理的,而系统日时钟的中断源是系统8254 0#计数器,它能根据设置的初值产生方波,55ms就是这些方波的周期上限。再联系我们调试的结果,你可能有了一些猜测。。。o( ̄▽ ̄)d

事实上,0#计数器作为系统日时钟的中断源,每55ms会提出一次中断请求,该中断请求的类型是08H。而在BIOS设计的08H型中断服务程序中,会对“日时钟计数器”进行一次加1计数。系统RAM 40:6CH~40:6FH这4个单元就是“日时钟计数器”,可以通过 INT 1AH 的 0 号功能调用读取,我们所使用的2CH号DOS功能就是调用它实现的。所以,我们读取到的时间数值不是连续变化的,因为它本身计数就是以55ms为间隔的。

而对于上面那段代码(WAITP函数),要想让它起作用,我们需要作出一定修改,比如奇数次变化减5,偶数次变化减6,或者反过来也一样。修改后的代码如下:

;延时函数
;内存数传参:TIME=延迟时间(TIME<=32767),单位1/100s
;被改动的寄存器 AH, CX, DX, BP 
WAITP  PROC
       MOV     BP, TIME
       MOV     AX, 2C05H         ;同时初始化(AL)=5
       INT     21H
GTNT:  PUSH    DX
       MOV     AH, 2CH
       INT     21H
       POP     CX
       CMP     CX, DX
       JZ      GTNT
       MOV     AH, 0
       SUB     BP, AX
       XOR     AL, 03H           ;计数次减5,偶数次减6
       CMP     BP, 0
       JG      GTNT
       RET
WAITP  ENDP

从代码中,我们可以看出,AL寄存器中的值是变化的,读取到奇数次时间变化,(BP)减5 ,同时(AL)变成6,读取到偶数次时间变化,(BP)减6,同时(AL)变成5。05H和06H的二进制码只有最低2位不同,而且恰好相反,所以,我们只要每次检测到读取时间的变化,就对(AL)最低2位取反,其余位保持不变,即可实现上述变化。

另外,延时功能还可以结合2DH号DOS功能实现:

  • 中断类型:21H
  • 功能号:2DH,设置时间
  • 入口参数:CH:CL=时:分,DH:DL=秒:1/100秒
  • 出口参数:AL=00,成功;AL=FF,失败
DELAYP PROC
       MOV      AH, 2DH
       MOV      CX, 0
       MOV      DX, 0
       INT      21H
READ:  MOV      AH, 2CH
       INT      21H
       MOV      AL, 100
       MUL      DH
       MOV      DH, 0
       ADD      AX, DX
       CMP      AX, 200
       JC       READ
       RET
DELAYP ENDP

上面这段代码是用于实验室上机实验的,其实和上一个子程序一个意思。但是这段代码在自己电脑上很多时候是用不了的,2DH号DOS功能被限制了,用不了,测试AL=FF,设置失败。而且,真要在自己电脑上运行,把时间改成00:00也不合适,所以适合在实验室里的实验箱(单片机)上用。

替换中断向量

上面提到的延时函数都是比较浪费系统资源的方式,在等待的时候,CPU被占用,做不了其他的方式。如果短时间延迟,看不出来区别,但是如果需要延迟较长时间,比如2s以上,那就十分明显了。

为了节约宝贵的CPU资源,在需要长时间等待且等待之外还有其他一些耗时操作的时候,可以选择中断实现延时/定时功能。这样,我们在等待的时候可以去处理其他事情,时间到了,系统会提出中断请求,响应后转去相应的中断服务程序,处理结束回到主程序的断点处继续向下执行。

我们可以利用系统的日时钟中断来实现定时功能,系统日时钟的中断源是8254芯片的 0#计数器,每55ms有一次中断请求。我们可以将每次中断请求的中断服务程序换成我们自己写的子程序,可以很轻松的实现55ms整数倍时间间隔的定时操作。此外,还有键盘中断、用户中断等可以用于其他功能的开发,不想写了,下一篇博客再写(´Д`)

  • 8
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值