程序其实并没有任何问题,问题出在DOS身上,因为它太"懒惰"。大家都知道用户程序的长度是不确定的,长的有几百KB,短的可能只有几个BYTE。按理说DOS再调入一个程序时应该先确定这个程序的长度,按程序实际长度给它安排内存,但是DOS并没有这样做。无论程序有多长,它都是一咕脑地把所有自由内存都分给这个程序。所以虽然系统具有几百KB的自由内存,可当程序"GETMEM.EXE"调入内存运行时这几百KB的自由内存都成了它的"私有财产",所以当它再次申请内存时DOS已经没有内存可分了。
因此我们说,一个应用程序在申请内存之前首先需要按自己的实际长度把DOS给它的内存重新划分一下,自己占了多少内存就保留多少,自己不占用的内存就要"无私"地"捐献"出来,这样才能向DOS申请内存,所谓"索取"之前先要"奉献"。
好在DOS提供了一个专用于重新分配内存的功能:
功能号:4AH
用 途:重新划分内存
参 数:AH=4AH
BX=新申请内存的"节"数
ES=被修改的内存块的段地址
调 用:INT 21H
返 回:如果成功,则进位标志CF清零
如果失败,则进位标志CF置1,AX=错误代码
AX=07H 内存控制块被破坏
AX=08H 没有足够内存
AX=09H ES内的段地址无效
这个功能有两个用途,一是在申请内存之前重新划分内存,二是如果第一次申请的内存块大小需要调整也使用这个功能。将"GETMEM.ASM"的"第一个插入点"与"第三个插入点"分别改写成这两小段程序,然后编译生成的可执行文件就能正常运行了。
;*** 第一个插 入点:****** mov bx,zseg ;BX寄存器指向程序结尾的段地址 mov ax,es ;AX寄存器送入PSP的段地址 sub bx,ax ;计算程序占用的内存"节"数据 mov ah,4ah ;重新划分内存 int 21h jc error ;若出现错误,转ERROR显示错误信息 ;*** 第三个插 入点****** zseg segment ;定义一个"空"段,以便于计算 zseg ends ;程序的长度(以"节"为单位)
内存是很重要的一种系统资源,可以想象如果一个程序对于内存比较"贪心",只借不还,那将会有什么样的结果。所以当一个程序向DOS申请了内存之后,在结束之前还要把"借"来的内存"还"回去,所谓"好借好还,再借不难"。
释放申请到的内存块可以使用DOS提供的49H功能:
功能号:49H
用 途:释放已经申请到的内存块
参 数:AH=49H
ES=待释放的内存块段地址
调 用:INT 21H
返 回:如果成功,则CF标志清零
如果失败,则CF标志置1,AX=错误代码
AX=07H 内存控制块被破坏
AX=09H ES内的段地址无效
将GETMEM.ASM程序中的"第二个插入点"改写成下面这一段程序就可以得到一个完善的内存分配程序示例:
;*** 第二个插 入点****** mov ah,49h ;释放申请到的内存块 int 21h jc error ;如果出现错误,转ERROR显示错误信息
了解了内存的分配技术,我们再回过头来说一说内存驻留技术。前面我们提到过当一个程序调入内存进行时,DOS会把所有可用内存分配给这个程序,而当程序结束时,这些内存就会被DOS收回。注意这种情况只有在用户程序采用"INT 20H"中断及DOS的4CH功能调用结束进行时才会发生。如果采用"INT 27H"中断结束程序,那么DOS在回收内存时就会做一些盘算,它会将内存重新划分一下,用户程序需要多少内存,DOS就会留出多少内存,其余内存它会毫不客气地收回来。
这就有点象我们编写的"GETMEM"程序首先要重新划分内存一样。实际上DOS也是通过4AH功能把可用内存重新分配,为需要驻留的程序保留合适的空间。这就给程序设计者这样一个提示:如果编制一个程序把某个已经驻留在内存中的程序占据的内存空间释放,这样不就可以在需要的时候把已驻留的程序"撤出"内存了吗。
很多鼠标器配用的驱动程序都有这样的能力。下面给出的这个程序例是为前面的"SNAP"程序编制的,运行了SNAP程序之后再运行UNINST就可以收回SNAP占据的内存空间:
code segment assume cs:code,ds:code org 100h main proc near jmp start ;跳过字符串 msg db 'SNAP.COM is already un installed.',07h,0dh,0ah,24h start: mov ax,3505h ;取得05H中断向量(等同于取得SNAP int 21h ;的PSP段地址) mov bx,2ch ;将SNAP程序的环境块段地址 mov ax,word ptr es:[bx] ;送至AX寄存器 push es ;暂存SNAP程序的PSP段地址 mov es,ax ;将SNAP程序的环境块段地址送入ES mov ah,49h ;释放SNAP程序的环境块 int 21h pop es ;取回SNAP程序的PSP段地址 mov ah,49h ;释放SNAP程序占用的内存空间 int 21h lea dx,msg ;显示提示信息 mov ah,9 int 21h mov ah,4ch ;结束进程 int 21h main endp code ends end main
执行SNAP程序之后可以使用MEM程序观察一下内存中是否驻留了SNAP,而后再运行UNINST程序,之后使用MEM程序观察一下SNAP程序是否还在内存中。
把某个驻留程序从内存中撤出,从技术上来说并不很难,但是要注意一些小问题:
① 不仅要释放程序代码与PSP占用的内存,还要释放这个程序对应的环境信息占用的内存,否则就会在内存中产生一些"碎片"。
② 驻留在内存中的程序一般都修改了中断向量表,使某个中断向量指向自己的服务程序。将程序撤出之后要注意把中断向量表复原,否则一旦中断产生CPU就会找不到正确的服务程序,这可以导致"死机"。UNINST程序就没有恢复05H中断向量,所以当运行了UNINST.COM以及MEM程序之后05H中断的服务程序就不复存在了,这时如果按下[PrtSc]就可能有令人不愉快的情况发生。
当然了,UNINST程序已经没有办法改进了,因为SNAP程序并没有保留原05H中断服务程序的入口。看来有些时候即使不再使用某个中断原来的服务程序也应该保存其入口地址,以利于撤出用户程序后不会给系统造成隐患。