AutoCAD 3DMAX C语言 Pro/E UG JAVA编程 PHP编程 Maya动画 Matlab应用 Android
Photoshop Word Excel flash VB编程 VC编程 Coreldraw SolidWorks A Designer Unity3D
 首页 > 汇编语言

用汇编编写DOS下的内存驻留程序(4)

51自学网 2015-09-08 http://www.51zixue.net

四 基本的驻留程序
4.1 一个基本的COM程序
 DOS之下有两种形式的可执行文件,这两种文件分别是COM文件和EXE文件.其中,COM文件可以迅速地加载和执行,但是其大小不能超过64K字节,只能有一个段,代码段.而且起始地址为100H指令必须为程序的启动指令.EXE文件可以加载到许多个段中,因此程序的大小没有限制,但是程序加载的过程就比较慢,而且对于内存驻留程序来说还会造成更大的麻烦.
 以下是一个可以正确执行的COM文件,但其内容是空的;只是一个COM文件的框架,可以把你写的任何应用部分加在这个文件中,形成一个COM格式的内存驻留程序:
  ;Section 1
  cseg segment
   assume cs:cseg,ds:cseg
    org 100h
  ;Section 2
  start:
   ret
  ;Section  3
  cseg ends
   end start
 上面的程序可以分成三部分,第一部分定义了代码段和数据段分别放在程序中的位置,以及执行代码的起始地址.第二部分是可执行的程序,在这个例子只一个RET指令而已.第三部分是程序包段的终结,其中END叙述包含了程序开始执行地址.
 若是把上面的程序经过汇编连接,你会发现所产生的COM文件只有一个字节长.这是因为所产生的COM文件没有程序段前缀(Programsegmetn  profix),因为在DOS下所有和COM文件都有相同的程序段前缀.当DOS加载一个COM文件到内存中时,就会自动地产生一份正确的程序段前缀.一个程序在执行的过程中,可以根据需要修改其程序段前缀,但是在一开始,所有COM文件的程序前缀都是相同的.下面是程序前缀的格式.
    偏移位置    含义
    0000H     程序终止处理子程序地址(INT 20H)
    0002H     分配段的结束地址,段值
    0004H     保留
    0005H     调用DOS的服务
    000AH     前一个父程序的IP和CS
    000EH     前一个父程序的CONTROL_C处理子程序地址
    0012H     前一个父程序包的硬件错误处理子程序地址
    0016H     保留
    002CH     环境段的地址值
    005EH     保留
    005CH     FCB1
    006CH `    FCB2
    0080H     命令行的参数和磁盘转移区域
4.2 一个最小的内存驻留程序
 上面的程序只是一个一般的DOS程序而已.并不是内存驻留的.以下是一个基本的内存驻留程序结构:
         ;Section 1
     cseg segment
      assume cs:cseg;ds:cseg
      org 100h
     start:    ;Section 2
      nop  
     done:    ;Section 3
      mov dx,offset done
      int 27h
         ;Section 4
     cseg ends
      end start
 和前一个程序相比,这个程序只是增加了一个DONE部分.这个部分使用了INT 27H这个中断调用,来终止并驻留在内存(Terminate and Stay Resident)中.使用INT 27H这个中断调用时,必须设定好一个指针,让这个指针指向内存中可以使用的部分,事实上,这就相当于设置一个COM文件可加载的位置.另外DOS还提供了INT 21H,AH=31H(驻留程序,Keep process),但是使用这个中断调用时,我们必须设定所保留的内存大小,而不是设定一个指针;另外这个中断调用会送出退出码.
 使用INT 27H时,必须设定一个指针指向可用存储位置的开头,以便让DOS用来加载稍后执行的程序.DOS本身有一个指针,这个指针是加载COM文件或EXE文件时的基准地址值.INT尿27H 会改变这个指针或为新的数值.同时造成新指针和旧指针之间的存储空间无法让DOS使用因此这样做会造成可用存储位置愈来愈少.
 调用INT 27H时所使用的指针是个FAR指针,其中DX存放的是位移指针(Offset pointer),它可以指到64K字节之内的范围.而DOS是段指针(Segment pointer),它可以指到IBM PC中640K字节的任何一个段.在上面的例子中,DS的内容不必另外设定,因为当COM文件加载时,DS的内容就CS的内容相同了.
 经常在编写汇编程序时,常犯的一个错误就是:把assume ds:cseg这个叙述误认为是,存放某一预设值到DS中,事实上,汇编语言程序中的Assume叙述不会产生任何的程序代码,这个功能是告诉汇编器做某些必要的假设,以便正确地汇编程序.譬如以下的程序:
     cseg segment
      .............
      assume ds:cseg
      mov ah,radix
      .............
     radix db 16
      .............
     cseg ends
 上面的程序汇编时,当汇编器看到mov ah,radix这个指令时,它就根据assume ds:cseg来产生一定形式的赋值指令.在面的Assume ds:cseg叙述是告诉汇编器,数据段就位于目前的代码段中.这是内存驻留程序的一项重要关键.如果DS的内容和CS不相同时,无论是否有assume 叙述,程序执行时都会失败.
4.3 改良的内存驻留程序
 上面所介绍的内存驻留程序实际上没有做任何事,只是驻留在内存中而已.事实上,在START和END之间放入任何程序代码,都只会执行一次而已然后就永远驻留在内存中,除非是使用转移指令转到START的地址去,否则将永远无法被使用.还要注意一点,START的地址值并非固定不变,它会根据程序执行时计算机的状态而改变.
 下面的这个程序只是把需要驻留的程序代码装载好,但是并不会执行.
         ;Section 1
     cseg segment
      assume cs:cseg,ds:cseg
      org  100h
         ;Section 2
     start:
      jmp initialize
         ;Section 3
     app_start:
      nop
     initialize:
         ;Section 4
      mov dx,offset initialize
      int  27h
         ;Section 5
     cseg ends
      end start
 上面的程序一开始执行时就传到initialize标志的地方,装置好驻留在内存的应用部分.原先的DONE已经改成initialize,而驻留在内存的程序代码则放在App_Start 和Initialize之间.
 另外,你也许注意到了,程序的起始地址并不是Initialize而是Start.这是因为所有COM程序的起始地址都是100H;而上面的程序中Start是放在100H的地方.如果把Initialize放在End之后,Initialize就变成起始地址,但是这样的程序无法透过EXE2BIN转换成COM文件了.如果无法产生COM文件时,那么就必须直接处理段的内容.
4.4 减少内存的额外负担
 到目前为止,都没有接触到程序前缀,当使用INT 27H时,事实上是把指针以前的东西都保留在内存中,这也包括了COM的程序段前缀.因为COM文件执行完毕后,才可以把程序段前缀移掉.
 从上面的事实可以看出:如果程序段前缀只能在COM装置程序结束后才可以移去,那么就可以由驻留在内存中的程序代码完成.要做到这一点,可以把整个程序往下移动256个字节.但又如何做到这一点呢?我们可以设定一个标志(Flag),用来指示这个程序是否执行过.如果这个驻留程序或是第一次执行时,就把整个程序往下移动256个字节,以便把程序段前缀移去.但是如果驻留程序在装置好之后,经过一段长时间仍然没有被执行时,怎么办呢?如果同时载入了好几个驻留程序时,双该如何呢?这些重要的事情都需要使用不同的程序代码来解决.如果说这些程序代码超出了256字节时,那么所占用的存储位置就超出程序段前缀所浪费的空间.有些人用一些比较简短的代码来解决这个问题,但是还是比较麻烦.因此对于大部分的内存驻留程序而言,除非存储空间太少,以至于256字节变得很重要,否则最好不要去处理程序段前缀,这样子会让你的程序简洁而且容易阅读.
4.5 使用驻留程序
 上面介绍了如何把程序加载到内存,并且让它永远留在内存中,接下来,介绍如何来使用驻留在内存中的程序.
 内存驻留程序的使用方法和它原先的设计有密切的关系.譬如,截获键盘输入的程序就必须通过键盘输入的软件中断,或是敲键盘所产生的硬件中断来使用.其它的驻留程序可能就必须靠:系统时钟,系统调用,或是其它的中断才有办法使用.这些驻留程序必须要和以上的使用方法连结;而且在驻留程序安装好之后,至少必须建立一种使用的管道,否则驻留程序将无法使用.
 IBM PC必须经由事件来驱动,譬如:键盘,系统时钟,或是软件中断.这些事件可以被截获,然后根据所发生的事件来执行一定的动作.因此必须让中断事件发生时,先执行我们的程序,而非系统的程序.
 譬如,当我们设计一个截获键盘输入的驻留程序时,就必须把驻留程序和执行键盘输入的系统调用连结起来.当DOS或是应用程序希望从键盘读取一个字符时,它就必须执行INT 16H调用.因此如果我们能够在调用INT 16H时,先执行我们的驻留程序,那么驻留程序就可能变成应用程序和操作系统间的桥梁.
 可以使用INT 21H中断调用中AH=25H来完成以上的要求.设置中断矢量可以更改INT 16H原先的中断矢量内容,让它改为指向我们的程序.譬如以下的例子所示:
     cseg segment
      assume cs:cseg,ds:cseg
      org  100h
     start:
      jmp Initialize
           ;Section 1
     new_keyboard_io proc far
      sti
      nop
      iret
     new_keyboard_io endp
           ;Section 2
     Initialize:
      mov dx,offset new_keyboard_io
      mov al,16h
      mov ah,25h
      int 21h
           ;Section 3
      mov dx,offset Initialize
      int 27h
     cseg ends
      end start
 上面的程序和4.3的程序结构是一样的,但是仍然有一些重要的改变.在Section 1和Section 2.在Section 1把驻留部分修改成子程序形式(Procedure),这样做是为了增加程序的可读性.另外,驻留部分多加了两个指令,STI和IRET.其中STI是设置中断标志(Set Interrupt Flag)和起始中断(Enable interrupts).
 当CPU发生中断时,它就关闭中断标志,因此CPU就不再接受中断.事实上,CPU会专心地为目前发生的中断服务.当CPU停止接受中断时,任何硬件中断的信号都会被忽略,譬如:键盘,时钟脉冲,磁盘机信号,调制解调器的中断.如果CPU一直不接受中断,那么就会漏掉一些重要的信息,计算机系统也可能因此而死机.因此虽然CPU可以停止接受中断一段时间,但是却不能够久.

<

 

 

 
上一篇:用汇编编写DOS下的内存驻留程序(5)  下一篇:用汇编编写DOS下的内存驻留程序(3)