今天主要的工作内容就是进行内存管理,至于前一天留下的叠加问题,我们放到明天去处理!
在正式开始前,我们首先进行整理一下昨天遗留的源文件。使得整体的代码容量看起来更加的简洁:
我们将之前的鼠标与键盘的代码分别创建对应的c文件:keyboard.c mouse.c 这两个文件。进行分割完以后,再在makefile的OBJS_BOOTPACK 的后面加上keyboard.obj mouse.obj 这两个文件后便可以编译,链接与运行了!!!
接下来,我来解释一下为什么要做内存容量的管理:
⇒ 首先,这是因为如果我们不能很好的规划内存的使用状况的话,可能会使得系统变得一团糟。运行于系统之上的程序将不知道哪里的内存可用,从而导致程序使用的内存出现混乱,也可能会出现多个应用程序使用同一部分的内存,导致程序可能出错。
因此,我们需要进行有效的内存管理。那么再次之前,我们先查看一下模拟器给我们使用的内存容量大概有多大。
对了,因为由于486中含有高速缓冲寄存器的存在。因此,简要介绍一下。高速缓存: 主要为了解决cpu与内存中间存在的速度的差异性的问题,cache会存储着访问内存的地址和内容(一般都是即将或者最近要使用的)。cpu每次要访问内存时,都会先将所访问的地址和内容放入cache中,再有cache放入内存。而读数据时,也是先从cache里先读的。
因此,若我们想通过往内存里写入值来检测内存的可使用大小的话,我们需要先把cache关掉。不然,cache存储着内存的值,会让我们检测是否写入成功的操作变得没有用。(因为写入时值就在cache中,如果没有替换掉,那么继续读内存时候依旧会读取到的)
检测的整体思路以及bug修改:
整体的思路就是:
在这个整体思路的过程中,代码的逻辑上是没什么问题的,但是实际检测过程中却出现了意外(由于我们的模拟器默认可使用的是32mb)但是我们向3g的内存写入时,最终出现结果依旧是3g的。
经过排查,发现出错原因竟然是编译器“过于优秀导致的” 。其原因就是因为觉得我们写入内存的数据进行反转后进行比较(因为如果能写入的话肯定是一样的),然后又反转回去,并吧旧数据还原的操作,根本就没什么意义,但是这种没什么意义的操作却又重复循环好多次!!!很浪费cpu的处理时间,因此,在编译后的汇编里直接把这部分的操作给删去了:
所以,最终这个检查的函数我们还是使用汇编来编写吧:
bootpack.c:
void HariMain(void)
{
....
s = memtest(0x00400000, 0xbfffffff); //算出内存地址,因为0x00400000之前的地址我们依已经用来做系统启动了肯定是能用的
sprintf(s, "memory %dMB ", s / (1024 * 1024));
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 32, COL8_FFFFFF, s);
...
}
#define EFLAGS_AC_BIT 0x00040000
#define CR0_CACHE_DISABLE 0x60000000
/*如果是486cpu 则elags寄存器的最高位是AC标志;如果是386则无这个标志,那么将一直是0;
对cr0寄存器的第31、30位设置为1,因此就用0x600000000进行与,此时可以禁止缓存
*/
unsigned int memtest(unsigned int start, unsigned int end)
{
char flg486 = 0 ;
unsigned int eflg, cr0, i;
//确认cpu是386还是486,486 ~ 100mhz
eflg = io_load_eflags();
eflg |= EFLAGS_AC_BIT; /* AC-bit = 1 */
io_store_eflags(eflg);
eflg = io_load_eflags();
if((eflg & EFLAGS_AC_BIT) != 0 )//386 的话即使设定AC等于1,其值还是会自动回到0
{
flg486 = 1;
}
eflg &= ~EFLAGS_AC_BIT; /* AC-bit = 0,~EFLAGS_AC_BIT = 0xfffbffff */
io_store_eflags(eflg);
if( flg486 != 0)
{
cr0 = load_cr0();
cr0 |= CR0_CACHE_DISABLE; //禁止缓存
store_cr0(cr0);
}
i = memtest_sub(start, end);
if( flg486 != 0)
{
cr0 = load_cr0();
cr0 &= ~CR0_CACHE_DISABLE; //允许缓存
store_cr0(cr0);
}
return i;
}
naskfun.nas:
_load_cr0: ;int load_cro(void)
mov eax,cr0
RET
_store_cr0: ;void load_cro(int cr0)
mov EAX,[esp+4]
mov cr0,EAX
RET
_memtest_sub: ; unsigned int memtest_sub(unsigned int start, unsigned int end)
PUSH EDI ; (EBX, ESI, EDI も使いたいので)
PUSH ESI
PUSH EBX
MOV ESI,0xaa55aa55 ; pat0 = 0xaa55aa55;
MOV EDI,0x55aa55aa ; pat1 = 0x55aa55aa;
MOV EAX,[ESP+12+4] ; i = start;
mts_loop:
MOV EBX,EAX
ADD EBX,0xffc ; p = i + 0xffc;
MOV EDX,[EBX] ; old = *p;
MOV [EBX],ESI ; *p = pat0;
XOR DWORD [EBX],0xffffffff ; *p ^= 0xffffffff;
CMP EDI,[EBX] ; if (*p != pat1) goto fin;
JNE mts_fin
XOR DWORD [EBX],0xffffffff ; *p ^= 0xffffffff;
CMP ESI,[EBX] ; if (*p != pat0) goto fin;
JNE mts_fin
MOV [EBX],EDX ; *p = old;
ADD EAX,0x1000 ; i += 0x1000;
CMP EAX,[ESP+12+8] ; if (i <= end) goto mts_loop;
JBE mts_loop
POP EBX
POP ESI
POP EDI
RET
mts_fin:
MOV [EBX],EDX ; *p = old;
POP EBX
POP ESI
POP EDI
RET
关于内存管理有以下两种方法:这里假设有0x08000000也就是128m
方法一:
假设我们以0x1000 4kb为单位进行管理,则128m 需要32768个字节。然后通过往里面填入0,1:
如果我们需要100kb的空间,我们就从a中找出连续25个进行标记:
j = 0
retry:
for(i = 0; i < 25 ;i++)
{
if(a[j+i] != 0 )
{
j++;
if(j<32768 -25) goto retry;
// "无可用内存"
}
}
//找到了从j到j+24个连续为0的空闲地址
//然后把这些地方标记为1,利用j值计算对应的地址放大0x1000倍即可
如果释放地址的话:比如从0x00123000开始的100kb不需要了:
j = 0x00123000 / 0x1000;
for(i = 0; i<25; i++)
{
a[j + i ] = 0;
}
上述这个方法方法虽然看起来操作简单,但是其管理表所占用的内存地址空间会很大,比如管理3gb内存时,需要786432,即768kb的大小。虽然管理表可以利用位来代替char类型进行管理,使得整体大小降低1/8, 经需96kb即可。(Windows 管理软盘就是用这个方法的)但是使用位的话,可能操作就要复杂很多了!!!
方法二:
利用列表的管理方法:
即把从xxx开始的yyy字节地址空间都是空着的这种信息存于列表当中进行管理。
利用结构体:
struct FREEINFO { //可用信息
unsigned int addr, size;
};
struct MEMMAN { //进行内存管理
int frees, maxfrees, lostsize, losts;
struct FREEINFO free[MEMMAN_FREES[1000];
例如:memman.free[0].addr = 0x00400000 ; memman.free[0].size = 0x07c00000 代表着从0x00400000开始有124mb空间可用。
使用后,直接将这一段信息从可用空间中删去即可。释放时,需查看前后是否可以合并,能合并就合并,不能就单独添加进入即可。
单该方法可能会产生碎片空间,这一段空间可能无法利用到,而被割舍。
本次就使用列表法进行管理,如果有问题后续在优化:
bootpack.c:
#define MEMMAN_FREES 4090 //整个的管理表大约是32kb <== 有4090组空余空间
#define MEMMAN_ADDR 0x003c0000
struct FREEINFO { //可用信息
unsigned int addr, size;
};
struct MEMMAN { //进行内存管理
int frees, maxfrees, lostsize, losts;
struct FREEINFO free[MEMMAN_FREES];
};
void memman_init(struct MEMMAN *man)
{
man->frees = 0;//可用信息数目
man->maxfrees =0;//用于观察可用状况:frees的最大值
man->lostsize = 0;//释放失败的内存的大小总和
man->losts = 0; //释放失败的次数
return;
}
unsigned int memman_total(struct MEMMAN *man)
{//报告空余内存大小的合计
unsigned int i, t = 0;
for(i = 0; i < man->frees; i++)
{
t += man->free[i].size;
}
return t;
}
unsigned int memman_alloc(struct MEMMAN *man, unsigned int size)
{//分配
unsigned int i, a;
for(i = 0; i < man->frees; i++)
{
if(man->free[i].size >= size)//如果可以找到足够大的内存
{
a = man->free[i].addr;
man->free[i].addr += size;
man->free[i].size -= size;
if(man->free[i].size == 0)
{//如果free[i] 变成了0 ,就减掉一个可用信息
man->frees--;
for(; i < man->frees; i++)
{
man->free[i] = man->free[i+1]; //带入结构体
}
}
return a;
}
}
return 0; //没有可用空间
}
int memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size)
{//释放
int i, j;
/*为了便于归纳内存,将free[]按照addr的顺序排列,递增
所以,下面先决定应该放在哪里
*/
for(i = 0; i < man->frees; i++)
{
if(man->free[i].addr > addr)
{
break;
}
}
//free[i-1].addr < addr < free[i].addr
if(i>0)
{
//前面有可用内存
if(man->free[i-1].addr + man->free[i-1].size == addr)
{//与前面可用内存放在一起
man->free[i-1].size += size;
if(i < man->frees)
{//后面也有
if(addr + size == man->free[i].addr)
{//与后面可用内存放在一起
man->free[i-1].size += man->free[i].size;
//删去man->free[i]
//free[i]变成0后归纳到前面
man->frees--;
for(; i < man->frees; i++)
{
man->free[i] = man->free[i+1]; //带入结构体
}
}
}
return 0;//成功完成
}
}
//不能与前面的可用空间合在一起
if(i < man->frees)
{//后面还有
if(addr + size == man->free[i].addr)
{//可以与后面内容归纳在一起
man->free[i].addr = addr;
man->free[i].size += size;
return 0;//完成
}
}
//既不能与前面归纳,也不能与后面归纳
if(man->frees < MEMMAN_FREES)
{//free[i]之后的,向后移动,腾出空间
for(j = man->frees; j > i; j--) //如果还有可用数量的话,则往后移动
{
man->free[j] = man->free[j-1];
}
man->frees++;
if(man->maxfrees < man->frees)
{
man->maxfrees = man->frees; //更新最大值
}
man->free[i].addr = addr;
man->free[i].size = size;
return 0; //成功完成
}
//不能后移
man->losts++;
man->lostsize += size;
return -1;//失败
}
然后让我们在主程序中进行添加对应的函数进行计算:
unsigned int memtotal;
struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;//设定了管理表位于内存的0x003c0000地址,预估今后不会用到
....
memtotal = memtest(0x00400000, 0xbfffffff); //算出内存地址
memman_init(memman);
memman_free(memman, 0x00001000, 0x0009e000); /* 0x00001000 - 0x0009efff */
memman_free(memman, 0x00400000, memtotal - 0x00400000);
....
sprintf(s, "memory %dMB free : %dKB",
memtotal / (1024 * 1024), memman_total(memman) / 1024);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 32, COL8_FFFFFF, s);
以上,我们完成了内存的管理任务,明天开始进行叠加处理,以便解决之前鼠标的bug,即拖入到下面的框框时,会把框框覆盖掉!!