安全释放基本内存和扩展内存的程序
由于DOS操作系统640KB常规内存的限制,如果不能有效地控制和释放内存中的各种TSR程序,在运行一些大
型软件时就会出现内存不够的现象,往往不得不重新启动系统,不仅浪费了时间,而且也容易损坏机器。
最近,报刊杂志上刊登了大量有关清除内存的文章,这些文章提供的程序多是TSR程序。当这些程序驻留内存
后,通过热键或参数操作来将其后驻留内存的TSR程序清掉。在实际应用过程中,不难发现这些程序都不同程
度地存在着一些缺点:①程序自身必须驻留内存,从而占掉了宝贵的常规内存,增加了系统的开销;②大多程
序自身无法撤离内存,或者没有检测自身是否安装的功能,造成重复安装;③它要占用或借用了DOS的中断号
,如有的程序借用了中断5H,使原来的屏幕打印功能失效,这有点治聋子又治成了哑巴的味道;④均不能将其
它程序开辟的但没有释放掉的扩展内存(XMS)释放出来,当运行其它使用扩展内存(XMS)的程序时,造成XMS内
存不够。
本文从DOS对基本内存和扩展内存的分配方法入手,提出了安全释放常规内存和扩展内存(XMS)资源的方法,
并给出了TURBO C程序。
一、内存分配的方法
1.常规内存
当DOS装入一个程序执行时,一般要建立一个环境内存块和一个程序内存块。每个内存块有两部分组成:一个
16字节长的内存控制块(MCB)和以节为单位的内存块。如果多个程序建立了多个内存分配块,这些内存分配块
在内存中就形成一条内存控制链,调用DOS功能52H号,在中断返回时,ES:[BX-2]中即是第一个内存控制块
的地址。
内存控制块(MCB)的结构见表1。用标志字节可以判断内存控制链是否结束;用第2个域可以判断此内存块归谁
所有;用第3个域可以求得下一个内存控制块的地址,方法是用当前内存控制块的地址与此域值相加,再加
1就得到下一个内存控制块的地址;用第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号功能来分配
一块扩展内存,调用返回时,如果分配成功,在DX寄存器中返回一个句柄。扩展内存控制程序就是通过这个
唯一的句柄来存取和释放这块扩展内存的。一个扩展内存句柄加10或减10,即可得到下一个或上一个扩展内存
句柄,这为释放扩展内存提供了便利。扩展内存控制功能的具体使用方法参见有关文献。
二、保存当前的内存状态
根据内存分配的方法可知,欲释放TSR软件占用的内存资源,在运行TSR程序之前,只要将系统的中断向量表、
常规内存的最后一个MCB的地址和最后一个可用的扩展内存句柄保存起来,在需要撤除TSR程序时,将原来的
中断向量表恢复,把内存控制链中保存的MCB值之后的内存控制块全部释放,将保存的扩展内存句柄及其后的
有效句柄所标识的扩展内存全部释放,就完全将内存恢复到TSR程序运行前的状态了。这个过程没有必要用常
驻内存的TSR程序来实现,只需要事先保存欲恢复的内存状态,需要时用程序恢复一下即可。
保存当前的内存状态的方法如下:
1.保存中断向量表:将绝对地址00000-00400H共1024个字节的内容保存到磁盘上即可。
2.保存当前最后一个内存控制块(MCB)的段地址(即自由内存控制块的段地址):首先用DOS功能调用52H获得
第一个MCB的地址,然后从此地址开始遍寻整个内存控制链,最后判断倒数第二个内存控制块是否是程序块,
如果不是,则此内存控制块就是要保存的MCB地址,否则,倒数第一个内存控制块是要保存的MCB地址。
3.保存最后一个可用扩展内存句柄:方法很简单,用XMS控制程序的9号功能申请一块1KB的XMS内存,获得DX寄
存器中返回的句柄,它就是要保存的扩展内存句柄。
三、释放内存的方法
释放内存时,首先要查找内存中是否有第2份COMMAND.COM,当有第2份COMMAND.COM存在时,释放内存是不安
全的,应当先执行EXIT退出COMMAND.COM,然后再释放内存,往往执行COMMAND.COM的程序是暂驻在内存中的
,当COMMAND.COM返回时,可以正常地从内存中退出,就没有再必要释放内存了。如果内存中没有第2份
COMMAND.COM时,就恢复中断向量表,释放常规和扩展内存。恢复中断向量表很简单,下面介绍释放常规内存
和扩展内存的具体方法。
1.释放常规内存:根据原来保存的最后一个MCB的段地址,找到属于TSR的内存控制块后,将内存控制块所在段
地址加1装入ES寄存器,调用DOS功能49H号就可以释放这块内存。然后寻找下一个内存控制块,释放之,这样
从一个内存控制块移向下一个内存控制块并释放它,直到最后一块,即完成了常规内存的释放。
2.释放扩展内存(XMS):根据原来保存的最后一个扩展内存块句柄,调用扩展内存控制功能0DH,解锁它标识
的扩展内存块,如果成功,就调用扩展内存控制功能0AH,释放这块扩展内存,然后将扩展内存句柄加10,即
下一个扩展内存块,重复上述步骤,直到找不到已分配的扩展内存块为止。
四、程序设计与使用
根据上面介绍的方法,作者用TURBO C编写了一个释放常规内存和扩展内存的程序RM.C(源程序见后),该程序
用TCC编译,用TLINK命令连接生成RM.COM即可使用。该程序在286、386、486机器和MS DOS 5.0~6.X下通过
。
该程序不驻留内存,在系统启动之后运行一次,将当前的内存状态保存起来,作为将来释放内存的依据。在需
要释放内存资源时,再执行一次,系统就返回到以前保存的状态。
该程序的使用方法是:
1.保存系统的当前内存状态,以便将来释放内存时使用,执行:
RM /B
2.释放TSR程序所占用的内存,恢复原来保存的系统内存状态,执行:
RM /R
3.获得程序的使用方法,执行:
RM /?
注意:该程序释放扩展内存部分符合扩展内存规范3.0(即XMS 3.0)的规定,所以在MSDOS 4.0或5.0的
HIMEM.SYS(XMS版本2.0)下不能正常工作,必须在WINDOWS 3.1或MSDOS 6.X系统所带的HIMEM.SYS(XMS版本
3.0)下才能正常工作。如你的DOS系统是5.0,只需要用高版本的HIMEM.SYS替代MS DOS 5.0的对应程序即可
。
五、源程序清单
/********************************************************/
/* 程序名称: RM.C 1.0 */
/* 作 者: 董占山 */
/* 完成日期: 1995-10-31 */
/* 用 途: 安全释放常规内存和扩展内存,本身不驻留内存 */
/* 编译方法: 用下列命令编译连接可以得到RM.COM: */
/* tcc -mt rm */
/* tlink c:\tc\lib\c0t+rm,rm,,c:\tc\lib\cs\lib /t */
/********************************************************/
#include <stdio.h>
#include <dos.h>
#include <string.h>
#define FSize 10786l /* 可执行文件的实际长度 */
char sign[3] = "RM"; /* 程序的标志信息 */
char commandname[8]="COMMAND"; /* COMMAND.COM命令名 */
/* 显示版权信息函数 */
void Copyright()
{
printf("(RM)Release Memory version 1.00 Copyright (c) 1995 Dong Zhanshan\n");
}
/* 显示程序使用方法的函数 */
void help()
{
printf("Syntex:\n");
printf(" RM [/Switch]\n");
printf("Switches:\n");
printf(" /B --- Build the interrupt vector table\n");
printf(" /R --- Release memory\n");
}
/* 寻找最后一个扩展内存分配句柄 */
unsigned int FindLastXMSHdl()
{
long xms_control;
unsigned int XMSHdl;
asm mov ax,0x4300 /* 检查是否安装XMS驱动模块 */
asm int 0x2f /* 调用多路中断 */
asm cmp al,0x80 /* 是否安装XMS驱动程序 */
asm jnz l1
asm mov ax,0x4310 /* 取XMS驱动程序入口地址 */
asm int 0x2f
asm lea di,xms_control /* 取XMS驱动程序向量地址 */
asm mov [di],bx /* 保存XMS驱动程序入口偏移地址 */
asm mov [di+2],es /* 段地址 */
asm mov ah,0x09 /* 分配一块XMS内存 */
asm mov dx,0x01 /* XMS块为1KB */
asm call dword ptr [di] /* 调用XMS功能 */
asm mov ah,0x0a /* 释放已分配的XMS内存 */
asm call dword ptr [di]
asm mov XMSHdl,dx /* 保存第一个XMS自由句柄 */
l1:
return XMSHdl; /* 返回这个句柄 */
}
/* 查找最后一个内存分配块段地址 */
unsigned int FindLastMCB()
{
unsigned int i,j,MCBArray[100];
asm mov ah,0x52
asm int 0x21
asm sub bx,2
asm mov ax,es:[bx] /* 第一个内存分配块段地址 */
asm push ax
asm pop bx
asm lea di,MCBArray /* 装入MCBArray变量的地址 */
l1:
asm mov [di],bx /* 保存MCB段地址 */
asm inc di
asm inc di
asm mov es,bx
asm mov al,byte ptr es:[0] /* 取MCB的标志 */
asm cmp al,0x4d /* 是否为最后MCB块 */
asm jnz l2 /* 是,转 */
asm add bx,es:[3] /* 计算下一个MCB段地址 */
asm inc bx
asm jmp l1
l2:
asm sub di,6 /* 计算最后一个MCB段地址 */
asm mov bx,word ptr [di]
asm mov es,bx
asm inc bx
asm cmp bx,es:[1]
asm jne l3
asm add di,2
l3:
asm mov ax,word ptr [di]
asm mov i,ax /* 返回最后一个MCB段地址 */
return i;
}
/* 存储最后一个MCB段地址、第一个XMS自由句柄和中断向量表 */
void SaveIntVec(filename)
char *filename;
{
FILE *fh1;
int i,LastMCB,LastXMSHdl,IntVec[512];
LastMCB = FindLastMCB();
LastXMSHdl = FindLastXMSHdl();
fh1 = fopen(filename,"rb+"); /* 打开一个二进制流文件 */
fseek(fh1,FSize,SEEK_SET); /* 到文件的指定位置 */
fwrite(sign,3,1,fh1); /* 写标志字节 */
for (i=0;i<512;i++) { /* 写中断向量表 */
IntVec[i] = peek(0,i*2);
fwrite(&IntVec[i],sizeof(IntVec[i]),1,fh1);
}
fwrite(&LastMCB,sizeof(LastMCB),1,fh1); /* 写最后一个MCB段地址 */
fwrite(&LastXMSHdl,sizeof(LastXMSHdl),1,fh1); /* 写第一个XMS自由句柄 */
fclose(fh1); /* 关闭文件 */
printf("Ok! Current status of memory has been saved.\n");
}
/* 释放基本内存 */
int ReleaseBaseMemory(LastMCB)
int LastMCB;
{
int i;
asm xor cx,cx
asm mov ah,0x52
asm int 0x21
asm sub bx,2
asm mov ax,es:[bx]
asm mov bx,ax
l1:
asm mov es,bx
asm mov al,byte ptr es:[0]
asm cmp al,0x4d
asm jne l2
asm mov ax,word ptr es:[1] /* 判断环境块是否是后来安装的 */
asm cmp ax,LastMCB
asm jb l3
asm inc bx
asm mov es,bx
asm mov ah,0x49 /* 释放常规内存 */
asm int 0x21
asm dec bx
asm inc cx /* 累加释放内存块个数 */
l3:
asm mov es,bx
asm add bx,es:[3]
asm inc bx
asm jmp l1
l2:
asm mov i,cx
return i; /* 返回释放内存块个数 */
}
/* 释放扩展内存(XMS) */
void ReleaseXMS(LastXMSHdl)
int LastXMSHdl;
{
long xms_control;
asm mov ax,0x4300
asm int 0x2f
asm cmp al,0x80
asm jnz l3
asm mov ax,0x4310
asm int 0x2f
asm lea di,xms_control
asm mov [di],bx
asm mov [di+2],es
asm mov dx,LastXMSHdl
l4:
asm mov ah,0x0d /* 解锁XMS块 */
asm call dword ptr [di]
asm cmp bl,0xa2
asm jz l3
asm mov ah,0x0a /* 释放内存块 */
asm call dword ptr [di]
asm add dx,10 /* 下一个XMS块 */
asm jmp l4
l3: ;
}
/* 恢复文本显示格式 */
void RestoreVideo()
{
asm mov ax,0x40
asm push ax
asm pop es
asm mov al,byte ptr es:[0x49]
asm cmp al,3
asm je l1
asm mov ah,0
asm mov al,3
asm int 0x10
l1:;
}
/* 显示错误信息 */
void DisplayErrorMsg()
{
printf("First, please execute:\n");
printf(" RM /B\n");
help();
exit();
}
/* 查找内存中是否有第2份COMMAND.COM,有返回1,无返回0 */
unsigned int Has2Command(lastmcb)
unsigned int lastmcb;
{
unsigned int i;
asm xor ax,ax
asm mov bx,lastmcb
l1:
asm mov es,bx
asm mov dl,byte ptr es:[0]
asm cmp dl,0x4d
asm jnz l2
asm mov dx,word ptr es:[1] /* 是自由内存块? */
asm cmp dx,0
asm je l4 /* 是转 */
asm mov cx,7
asm lea si,commandname
asm mov di,8
asm cld
asm repz cmpsb /* 内存块为COMMAND.COM的程序块 */
asm je l3 /* 是转 */
l4:
asm add bx,es:[3] /* 下一个内存块的段地址 */
asm inc bx
asm jmp l1
l3:
asm mov ax,1 /* 存在第二份COMMAND.COM */
l2:
asm mov i,ax
return i;
}
/* 释放内存 */
void ReleaseMemory(filename)
char *filename;
{
FILE *fh1;
int i,ErrorNo,LastMCB,LastXMSHdl,IntVec[512];
char mysign[3];
fh1 = fopen(filename,"rb");
fseek(fh1,FSize,SEEK_SET);
ErrorNo = fread(mysign,3,1,fh1);
if (!ErrorNo) DisplayErrorMsg();
if (strcmp(mysign,sign)==0) { /* 查找标志 */
ErrorNo = fread(IntVec,sizeof(IntVec),1,fh1);
if (!ErrorNo) DisplayErrorMsg();
ErrorNo = fread(&LastMCB,sizeof(LastMCB),1,fh1);
if (!ErrorNo) DisplayErrorMsg();
ErrorNo = fread(&LastXMSHdl,sizeof(LastXMSHdl),1,fh1);
if (!ErrorNo) DisplayErrorMsg();
fclose(fh1);
if (Has2Command(LastMCB)==1) { /* 有第2份COMMAND.COM吗? */
printf("There is the second copy of COMMAND.COM in memory.\n");
printf("Please execute EXIT to remove the copy of COMMAND.COM.\n");
exit();
}
if (LastMCB == FindLastMCB()) { /* 最后的MCB与存储的相等吗? */
printf("No TSR program is running.\n");
printf("It's not necessary to release memory.\n");
exit();
}
if (ReleaseBaseMemory(LastMCB)) { /* 释放常规内存 */
for (i=0;i<512;i++) poke(0,i*2,IntVec[i]);
ReleaseXMS(LastXMSHdl);
RestoreVideo();
printf("OK! Memory is all released.\n");
}
else {
printf("\nPlease BUILD current status of memory, use: \n");
printf(" RM /B\n");
printf("You can use RM /R to release memory from nom on.\n");
}
}
}
/* 主程序 */
main(argc,argv)
int argc;
char *argv[];
{
int LastMCB;
char *temp,ch;
Copyright();
if (argc == 2) {
temp = argv[1];
ch = toupper(temp[1]); /* 取开关字符 */
switch (ch) {
case 'B' : /* 建立内存当前状态 */
SaveIntVec(argv[0]);
break;
case 'R' : /* 恢复以前存储的内存状态 */
ReleaseMemory(argv[0]);
break;
default: help();
}
}
else help();
return 0;
}
©董占山Zhanshan Dong
|