9.1、整理文件
把各个函数功能写到指定功能文件里,管理方便。
9.2、内存容量检查
做内存管理,肯定要知道有多少内存。
方法:往内存中写入指定数据,立刻取出,检查是不是和写入的内容相同,如果内存正常的话,读出的内容就和写入的一样,说明内存有效。如果写入的内存不正常(地址不存在或其他原因),那么读出的内容将会是乱七八糟,那么就确定这个内存无效。
在486开始的CPU都带有高速缓存机制,写入读出都是在缓存中,无法测量内存,所以检查内存时,如果CPU是486以上就关闭高速缓存。
#define EFLAGS_AC_BIT 0x00040000
#define CR0_CACHE_DISABLE 0x60000000
unsigned int memtest(unsigned int start, unsigned int end)
{
char flg486 = 0;
unsigned int eflg, cr0, i;
/* 确认CPU是不是486及以上 */
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,AC的值还是自动会回到0 */
flg486 = 1;
}
eflg &= ~EFLAGS_AC_BIT; /* AC-bit = 0 */
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;
}
AC是EFLAGS的第18位,如果是386CPU,就没有这个标志位,一直是0。
关闭高速缓存的方法是将CR0的第二、三位置为1。
unsigned int memtest_sub(unsigned int start, unsigned int end)
{
unsigned int i, *p, old, pat0 = 0xaa55aa55, pat1 = 0x55aa55aa;
for (i = start; i <= end; i += 0x1000) { /* 一次增加4KB */
p = (unsigned int *) (i + 0xffc); /*只检查最后四位 */
old = *p; /* 记住修改前的值 */
*p = pat0; /* 写入pat0 */
*p ^= 0xffffffff; /* 取反 */
if (*p != pat1) { /* 检查取反结果是否等于pat1;不等于就结束检查 */
not_memory:
*p = old;
break;
}
*p ^= 0xffffffff; /* 如果等于pat1,再取反 */
if (*p != pat0) { /* 检查结果是否等于第一次写入的值;不等于就结束检查 */
goto not_memory;
}
*p = old; /* 恢复原来的值 */
}
return i;
}
HariMain
i = memtest(0x00400000, 0xbfffffff) / (1024 * 1024);
sprintf(s, "memory %dMB", i);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 32, COL8_FFFFFF, s);
检查0x00400000 - 0xbfffffff,范围3GB - 0x400000B。
前0x400000之前在asmhead.nas中已经使用过了。
检查结果一直都是3072MB。但是虚拟机并没有分配这多内存,这锅C编译器背,因为C编译器的优秀,它把很多认为“不必要的”程序给省略了,“提高”运行效率。
memtest_sub函数就被优化成:
unsigned int memtest_sub(unsigned int start, unsigned int end)
{
unsigned int i;
for (i = start; i <= end; i += 0x1000) {}
return i;
}
完全没有检查到内存,就是做了一个循环。
所以还是采用汇编来写吧:
// naskfunc.nas
_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
这就对了。
9.3、内存管理
例:管理128MB内存
9.3.1、方案一:4KB单位管理
以4KB为一个单位进行管理。
128MB / 4KB = 32768
1、创建32768 Byte大小的数组,如果数组值为1,代表内存正在使用。0为未分配内存。
char a[32768];
for(i=0; i<1024; i++){//4MB正在使用
a[i] = 1;
}
for(i=1024; i<32768; i++){//剩下未分配
a[i] = 0;
}
如果需要分配100KB内存空间,只需要找到25个连续的0:
j = 0;
allocate:
for(i=0; i<25; i++){
if(a[j+i] != 0){
j++;
if(j<32768-25){
goto allocate;
}else{
// 没有空间的了,不做分配了
}
}
}
// 如果找到100KB的连续空间,就把数组置为1
for(i=0; i<25; i++){
a[j+i] = 1;
}
// addr = j * 0x1000 处就是分配内存的开始地址
如果内存用完要释放:
j = addr / 0x1000
for(i=0; i<25; i++){
a[j+i] = 0;
}
以上是举例100KB,所以25都是写死的,实际25应该按分配大小进行计算。
9.3.2、 方案二:列表管理方法:
列表管理方法:
标记,从addr地址开始,有size大小的空间空闲。
struct FREEINFO { /* 标记 */
unsigned int addr, size;
};
struct MEMMAN { /* 内存管理 */
int frees;
struct FREEINFO free[1000];
};
struct MEMMAN meman;
meman.frees = 1; // 1块空闲内存区间
meman.free[0].addr = 0x00400000; // 从0x00400000开始0x07c00000大小(124M)
meman.free[0].size = 0x07c00000;
如果分配100KB空间:
for(i = 0; i < meman.frees; i++){
if(meman.free[i].size >= 100 *1024){
// 有100KB空间可以分配
"meman.free[i].addr 往后100KB可以使用"
addr = meman.free[i].addr;
}
}
"没有空间可以分配"
分配完成后,管理信息中需要删除这段空间:
for(i = 0; i < meman.frees; i++){
if(meman.free[i].size >= 100 *1024){
// 有100KB空间可以分配
"meman.free[i].addr 往后100KB可以使用"
addr = meman.free[i].addr;
meman.free[i].addr += 100 * 1024; // 管理信息中删除这段内存
meman.free[i].size -= 100 * 1024;
}
}
"没有空间可以分配"
最后:
分配内存后,如果size = 0了,就要frees - 1,并且结构体数组前移。
释放内存后,如果不能和之前的内存合并,就要再加一条记录,同时frees + 1;如果能够合并,直接合并就好。
9.3.3、优化方案二
刚刚只设置了1000条记录,但是,如果内存太散,可能会耗光记录,那就增加一些管理记录:
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; /* 失败 */
}
HariMain:
#define MEMMAN_ADDR 0x003c0000
unsigned int memtotal;
struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
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);