安全释放基本内存和扩展内存的方法
董占山
由于DOS操作系统640KB常规内存的限制,如果不能有效地控制和释放内存中的各种TSR程序,在运行一些大型软件时就会发现内存不够的现象,&127;往往不得不重新启动系统,不仅浪费了时间,而且也容易损坏机器。
最近,报刊杂志上刊登了大量有关清除内存的文章,这些文章提供的程序多是TSR程序。当这些程序驻留内存后,&127;通过热键或参数操作来将其后驻留内存的TSR程序清掉。在实际应用过程中,&127;不难发现这些程序都不同程度地存在着一些缺点:①程序自身必须驻留内存,从而占掉了宝贵的常规内存,增加了系统的开销;②大多程序自身无法撤离内存,或者没有检测自身是否安装的功能,造成重复安装;③它要占用或借用了DOS的中断号,&127;如有的程序借用了中断5H,使原来的屏幕打印功能失效,这有点治聋子又治成了哑巴的味道;④均不能将其它程序开辟的但没有释放掉的扩展内存(XMS)释放出来,&127;当运行其它使用扩展内存(XMS)的程序时,造成XMS内存不够。
本文从DOS对基本内存和扩展内存的分配方法入手,&127;提出了安全释放常规内存和扩展内存(XMS)资源的方法,并给出了汇编语言程序。
一、内存分配的方法
1.常规内存
当DOS装入一个程序执行时,&127;一般要建立一个环境内存块和一个程序内存块。每个内存块有两部分组成:一个16字节长的内存控制块(MCB)&127;和以节为单位的内存块。如果多个程序建立了多个内存分配块,这些内存分配块在内存中就形成一条内存控制链,调用DOS功能52H号,在中断返回时,ES:[BX-2]&127;中即是第一个内存控制块的地址。
内存控制块(MCB)的结构见表1。用标志字节可以判断内存控制链是否结束;用第2个域可以判断此内存块归谁所有;用第3个域可以求得下一个内存控制块的地址,方法是用当前内存控制块的地址与此域值相加,再加1&127;就得到下一个内存控制块的地址;用第4个域可以知道拥有者的名字。这4个域在释放常规内存时都要用到。
表1 内存控制块(MCB)结构
| 偏移 |
内容 |
| 0 |
标志字节,4DH表示内存控制链未完,5AH表示内存控制链结束 |
| 1-2 |
拥有者段地址,即PSP地址 |
| 3-4 |
内存块长度,以节表示(1节=16个字节) |
| 8-F |
程序名,为ASCIIZ字符串 |
2.扩展内存
MS DOS 4.0及以后的版本提供了HIMEM.SYS的扩展内存(XMS)管理程序,连接在多路中断2FH上。用如下程序段可以获得扩展内存控制程序入口:
MOV AX,4300H ; 判断是否安装XMS驱动程序
INT 2FH
CMP AL,80H ; AL=80H,则安装XMS驱动程序,否则,没有安装
JNE NO_XMS
MOV AX,4310H ; 获取XMS控制程序入口地址
INT 2FH
LEA DI,XMS_CONTROL
MOV DS:[DI],BX ; 存XMS控制程序入口的偏移地址
MOV DS:[DI+2],ES ; 存XMS控制程序入口的段地址
获得扩展内存控制程序的入口地址后,可以采用长调用的方式来使用它提供的功能。一般可用9号功能来分配一块扩展内存,调用返回时,&127;如果分配成功,在DX寄存器中返回一个句柄。扩展内存控制程序就是通过这个唯一的句柄来存取和释放这块扩展内存的。一个扩展内存句柄加10或减10,即可得到下一个或上一个扩展内存句柄,这为释放扩展内存提供了便利。扩展内存控制功能的具体使用方法参见有关文献。
二、保存当前的内存状态
根据内存分配的方法可知,欲释放TSR软件占用的内存资源,在运行TSR程序之前,只要将系统的中断向量表、常规内存的最后一个MCB&127;的地址和最后一个可用的扩展内存句柄保存起来,在需要撤除TSR程序时,&127;将原来的中断向量表恢复,把内存控制链中保存的MCB值之后的内存控制块全部释放,&127;将保存的扩展内存句柄及其后的有效句柄所标识的扩展内存全部释放,就完全将内存恢复到TSR程序运行前的状态了。这个过程没有必要用常驻内存的TSR程序来实现,只需要事先保存欲恢复的内存状态,需要时用程序恢复一下即可。
保存当前的内存状态的方法如下:
1.保存中断向量表:将绝对地址00000-00400H共1024个字节的内容保存到磁盘上即可。
2.保存当前最后一个内存控制块(MCB)的段地址(即自由内存控制块的段地址):首先用DOS功能调用52H获得第一个MCB的地址,然后从此地址开始遍寻整个内存控制链,最后判断倒数第二个内存控制块是否是程序块,如果不是,则此内存控制块就是要保存的MCB地址,否则,&127;倒数第一个内存控制块是要保存的MCB地址。
3.保存最后一个可用扩展内存句柄:方法很简单,用XMS控制程序的9号功能申请一块1KB的XMS内存,获得DX寄存器中返回的句柄,它就是要保存的扩展内存句柄。
三、释放内存的方法
释放内存时,首先要查找内存中是否有第2份COMMAND.COM,&127;当有第&127;2&127;份COMMAND.COM存在时,释放内存是不安全的,应当先执行EXIT退出COMMAND.COM,然后再释放内存,往往执行COMMAND.COM的程序是暂驻在内存中的,当COMMAND.COM返回时,可以正常地从内存中退出,就没有再必要释放内存了。&127;如果内存中没有第2份COMMAND.COM时,就恢复中断向量表,释放常规和扩展内存。恢复中断向量表很简单,下面介绍释放常规内存和扩展内存的具体方法。
1.释放常规内存:根据原来保存的最后一个MCB的段地址,找到属于TSR的内存控制块后,将内存控制块所在段地址加1装入ES寄存器,调用DOS功能&127;49H号就可以释放这块内存。然后寻找下一个内存控制块,释放之,这样从一个内存控制块移向下一个内存控制块并释放它,直到最后一块,即完成了常规内存的释放。
2.释放扩展内存(XMS):根据原来保存的最后一个扩展内存块句柄,&127;调用扩展内存控制功能0DH,解锁它标识的扩展内存块,如果成功,&127;就调用扩展内存控制功能0AH,释放这块扩展内存,然后将扩展内存句柄加10,&127;即下一个扩展内存块,重复上述步骤,直到找不到已分配的扩展内存块为止。
四、程序设计与使用
根据上面介绍的方法,作者用汇编语言编写了一个RM.ASM(源程序见后),该程序用TASM汇编,用TLINK
RM /t命令连接生成RM.COM即可使用。该程序286&127;、386、486机器和MS DOS
5.0~6.X下通过。
该程序不驻留内存,在系统启动之后运行一次,将当前的内存状态保存起来,作为将来释放内存的依据。在需要释放内存资源时,再执行一次,系统就返回到以前保存的状态。
该程序的使用方法是:
1.保存系统的当前内存状态,以便将来释放内存时使用,执行:
RM /B
2.释放TSR程序所占用的内存,恢复原来保存的系统内存状态,执行:
RM /R
3.获得程序的帮助信息,执行:
RM /?
注意:该程序释放扩展内存部分符合扩展内存规范3.0(即XMS 3.0)的规定,所以在MS
DOS 4.0或5.0的HIMEM.SYS(XMS版本&127;2.&127;0)&127;下不能正常工作,&127;必须在WINDOWS
3.1或MSDOS 6.X系统所带的HIMEM.SYS(XMS版本3.0)下才能正常工作。如你的DOS系统是5.0,只需要用高版本的HIMEM.SYS替代MS
DOS 5.0&127;的对应程序即可。
五、程序清单
; 程序名: RM.ASM
; 释放常规内存和扩展内存(XMS)的程序
TITLE Release Memory
LOCALS @@
DOSSEG
.MODEL SMALL
.CODE
ASSUME CS:@CODE
ORG 100H
BEGIN: JMP MAIN_PROC
SIGN DB 'RM'
COPYRIGHTMSG DB 'Release Memory version 1.00, Copyright (c) 1995 Dong
Zhanshan',0DH,0AH,'$'
HELPMSG DB 0DH,0AH,'Syntex:',0DH,0AH
DB ' RM [/Switch]',0DH,0AH
DB 'Switches:',0DH,0AH
DB ' /B --- Build current status of memory',0DH,0AH
DB ' /R --- Release memory',0DH,0AH,'$'
ERRORMSG DB 0DH,0AH,07,'First, please execute:',0DH,0AH
DB ' RM /B',0DH,0AH,'$'
BUILDMSG DB 0DH,0AH,07,'Please BUILD current status of memory, use: ',0DH,0AH
DB ' RM /B',0DH,0AH
DB 'You can use RM /R to release memory from now on.',0DH,0AH,'$'
COMMANDNAME DB 'COMMAND'
COMMANDMSG DB 0DH,0AH,07,'There is the second copy of COMMAND.COM in memory.',0DH,0AH
DB 'Please execute EXIT to remove the copy of COMMAND.COM.',0DH,0AH,'$'
OKMSG DB 0DH,0AH,07,'OK! Memory is all released.',0DH,0AH,'$'
NEEDLESSMSG DB 0DH,0AH,07,'Not TSR program is running.',0DH,0AH
DB 'It is not necessary to release memory.',0DH,0AH,'$'
UNKNOWNOPT DB 0DH,0AH,07,'Unkown option',0DH,0AH,'$'
SAVEOK DB 0DH,0AH,07,'Ok! Current status of memory has been saved.',0DH,0AH,'$'
; 显示信息
DISPLAYMSG PROC NEAR
MOV AH,9
INT 21H
RET
DISPLAYMSG ENDP
; 显示版号
COPYRIGHT PROC NEAR
MOV DX,OFFSET COPYRIGHTMSG
CALL DISPLAYMSG
RET
COPYRIGHT ENDP
; 显示帮助信息
HELP PROC NEAR
MOV DX,OFFSET HELPMSG
CALL DISPLAYMSG
RET
HELP ENDP
; 查找最后一个可用的XMS句柄
FINDLASTXMSHDL PROC NEAR
MOV AX,4300H
INT 2FH
CMP AL,80H
JNE @@3
MOV AX,4310H
INT 2FH
LEA DI,XMS_CONTROL ; XMS控制程序入口地址
MOV [DI],BX
MOV [DI+2],ES
MOV AH,9
MOV DX,1
CALL DWORD PTR [DI]
MOV AH,0AH
CALL DWORD PTR [DI]
MOV AX,DX
@@3:
RET
FINDLASTXMSHDL ENDP
; 查找最后一个MCB的段地址
FINDLASTMCB PROC NEAR
MOV AH,52H
INT 21H
SUB BX,2
MOV AX,ES:[BX]
PUSH AX
POP BX
LEA DI,MCBARRAY
@@4:
MOV [DI],BX
INC DI
INC DI
MOV ES,BX
MOV AL,BYTE PTR ES:[0]
CMP AL,4DH
JNE @@5
ADD BX,ES:[3]
INC BX
JMP @@4
@@5:
SUB DI,4
MOV BX,WORD PTR [DI]
MOV ES,BX
INC BX
CMP BX,ES:[1] ; 是程序块吗?
JNE @@6 ; 否,转
ADD DI,2 ; 是
@@6:
MOV AX,WORD PTR [DI] ; 返回最后一个MCB段地址
RET
FINDLASTMCB ENDP
; 存储当前内存状态,包括中断向量表、XMS句柄和最后的MCB段地址
SAVEINTVEC PROC NEAR
CALL FINDLASTMCB
MOV WORD PTR CS:[LASTMCB],AX
CALL FINDLASTXMSHDL
MOV WORD PTR CS:[LASTXMSHDL],AX
LEA DX,FILENAME ; 打开该程序文件
MOV AX,3D02H
INT 21H
MOV BX,AX
XOR CX,CX
MOV DX,OFFSET EXTPARAM ; 到文件尾
SUB DX,100H
MOV AX,4200H
INT 21H
MOV CX,400H ; 写中断向量表
XOR DX,DX
PUSH DS
MOV DS,DX
MOV AH,40H
INT 21H
POP DS
LEA DX,LASTMCB ; 写最后的MCB段地址
MOV CX,2
MOV AH,40H
INT 21H
LEA DX,LASTXMSHDL ; 写XMS句柄
MOV AH,40H
INT 21H
LEA DX,SIGN ; 写标志字符
MOV AH,40H
INT 21H
MOV AH,3EH
INT 21H
MOV DX,OFFSET SAVEOK
CALL DISPLAYMSG
RET
SAVEINTVEC ENDP
; 释放常规内存过程
RELEASEBASEMEMORY PROC NEAR
XOR CX,CX
MOV AH,52H
INT 21H
SUB BX,2
MOV AX,ES:[BX]
MOV BX,AX
@@6:
MOV ES,BX
MOV AL,BYTE PTR ES:[0]
CMP AL,4DH
JNE @@9
MOV AX,WORD PTR ES:[1] ; 判断环境块是否是后来安装的
CMP AX,WORD PTR CS:[LASTMCB]
JB @@8 ; 不是转
INC BX
MOV ES,BX ; 释放内存块
MOV AH,49H
INT 21H
DEC BX
INC CX ; 释放内存块个数
@@8:
MOV ES,BX
ADD BX,ES:[3]
INC BX
JMP @@6
@@9:
MOV AX,CX ; 返回释放内存块个数
RET
RELEASEBASEMEMORY ENDP
; 释放扩展内存(XMS)
RELEASEXMS PROC NEAR
MOV AX,4300H
INT 2FH
CMP AL,80H
JNE @@2
MOV AX,4310H
INT 2FH
LEA DI,XMS_CONTROL
MOV [DI],BX
MOV [DI+2],ES
MOV DX,WORD PTR CS:[LASTXMSHDL]
@@1:
MOV AH,0DH ; 解锁XMS块
CALL DWORD PTR [DI]
CMP BL,0A2H
JE @@2
MOV AH,0AH ; 释放内存块
CALL DWORD PTR [DI]
ADD DX,10 ; 下一个XMS块
JMP @@1
@@2:
RET
RELEASEXMS ENDP
; 恢复文本显示格式
RESTOREVIDEO PROC NEAR
MOV AX,40H
PUSH AX
POP ES
MOV AL,BYTE PTR ES:[49H]
CMP AL,3
JE @@1
MOV AH,0
MOV AL,3
INT 10H
@@1:
RET
RESTOREVIDEO ENDP
; 显示错误信息
DISPLAYERRORMSG PROC NEAR
MOV DX,OFFSET ERRORMSG
CALL DISPLAYMSG
CALL HELP
MOV AX,4C00H
INT 21H
RET
DISPLAYERRORMSG ENDP
; 查找内存中是否有第2份COMMAND.COM,有返回1,无返回0
HAS2COMMAND PROC NEAR
XOR AX,AX
MOV BX,WORD PTR CS:[LASTMCB]
@@1:
MOV ES,BX
MOV DL,BYTE PTR ES:[0]
CMP DL,4DH
JNZ @@2
MOV DX,WORD PTR ES:[1] ; 是自由内存块?
CMP DX,0 ;
JE @@1 ; 是转
MOV CX,7
LEA SI,COMMANDNAME
MOV DI,8
CLI
REPZ CMPSB ; 内存块为COMMAND.COM的程序块
JE @@3 ; 是转
ADD BX,ES:[3]
INC BX
JMP @@1
@@3:
MOV AX,1
STI
@@2:
RET
HAS2COMMAND ENDP
; 释放内存
RELEASEMEMORY PROC NEAR
MOV DI,OFFSET EXTPARAM
ADD DI,404H
LEA SI,SIGN
MOV CX,2
CLI
REPZ CMPSB ; 查找标志
JNE @@1 ; 无转
STI
CALL HAS2COMMAND
CMP AX,1 ; 有第2份COMMAND.COM吗?
JE @@3 ; 有转
CALL FINDLASTMCB
CMP AX,WORD PTR CS:[LASTMCB] ; 最后的MCB与存储的相等吗?
JE @@5 ; 相等转
CALL RELEASEBASEMEMORY ; 释放常规内存
CMP AX,0
JE @@1
XOR DI,DI ; 恢复中断向量表
MOV ES,DI
MOV SI,OFFSET EXTPARAM
MOV CX,400H
CLI
REPZ MOVSB
STI
@@5:
CALL FINDLASTXMSHDL
CMP AX,WORD PTR CS:[LASTXMSHDL]
JE @@4
CALL RELEASEXMS ; 释放XMS
CALL RESTOREVIDEO ; 恢复文本显示
MOV DX,OFFSET OKMSG
CALL DISPLAYMSG
JMP @@2
@@4:
MOV DX,OFFSET NEEDLESSMSG
CALL DISPLAYMSG
JMP @@2
@@3:
MOV DX,OFFSET COMMANDMSG
CALL DISPLAYMSG
JMP @@2
@@1:
MOV DX,OFFSET BUILDMSG
CALL DISPLAYMSG
@@2:
RET
RELEASEMEMORY ENDP
; 获取命令行参数
GETCMDLINE PROC NEAR
XOR CX,CX
MOV CL,BYTE PTR CS:[80H]
CMP CX,0
JE @@1
LEA DI,SWITCH ; 取程序参数
MOV SI,81H
@@3:
CMP BYTE PTR [SI],32 ; 是空格吗?
JE @@4 ; 是转
CMP BYTE PTR [SI],'/' ; 是程序开关吗?
JNE @@4 ; 不是转
MOV AL,BYTE PTR [SI+1] ; 传递参数
MOV BYTE PTR [DI],AL
MOV AX,1
JMP @@2
@@4:
INC SI
LOOP @@3
@@1:
MOV AX,0
@@2:
RET
GETCMDLINE ENDP
; 从程序的环境块中获得程序名及其路径
GETPROGNAME PROC NEAR
PUSH DS
MOV BX,CS:[2CH] ; 取环境块段地址
DEC BX
MOV ES,BX
MOV CX,ES:[3] ; 取环境块的长度
SHL CX,4
INC BX
MOV ES,BX
XOR BX,BX
@@1:
CMP BYTE PTR ES:[BX],0
JE @@2
INC BX
JMP @@1
@@2:
INC BX
CMP BYTE PTR ES:[BX],0
JNE @@1
ADD BX,3
SUB CX,BX
LEA DI,FILENAME ; 取程序名
PUSH DS
PUSH ES
POP DS
MOV SI,BX
POP ES
CLI
REPNZ MOVSB
STI
POP DS
RET
GETPROGNAME ENDP
; 程序的主过程
MAIN_PROC:
CALL COPYRIGHT
CALL GETCMDLINE
CMP AL,0
JE @@5
CALL GETPROGNAME
MOV BX,OFFSET SWITCH
MOV AL,[BX]
CMP AL,'b'
JE @@3
CMP AL,'B'
JE @@3
CMP AL,'r'
JE @@4
CMP AL,'R'
JE @@4
CMP AL,'?'
JE @@5
JMP @@7
@@3:
CALL SAVEINTVEC
JMP @@6
@@4:
CALL RELEASEMEMORY
JMP @@6
@@7:
MOV DX,OFFSET UNKNOWNOPT
CALL DISPLAYMSG
JMP @@6
@@5:
CALL HELP
@@6:
MOV AX,4C00H
INT 21H
RET
EXTPARAM:
LASTMCB EQU EXTPARAM + 1024
LASTXMSHDL EQU LASTMCB + 2
FILENAME EQU LASTXMSHDL + 4
SWITCH EQU FILENAME + 80
XMS_CONTROL EQU SWITCH + 1
MCBARRAY EQU XMS_CONTROL + 4
END BEGIN
|