本章内容包含两个方面:内存容量检查和内存管理。

1.内存容量检查
要进行内存管理,首先要知道内存的容量大小。最初启动时,BIOS会检查内存容量,所以BIOS知道内存容量大小,但由于BIOS版本不同,要写一段通用的代码会很费事,所以不如直接自己去检查内存。

在进行内存容量检查之前先说一下高速缓存cache。486以后的CPU具有高速缓存,它的读写速度可以接近CPU速度。每次访问内存,都要将所访问的地址和内容存入到高速缓存里。也就是存放成这样:18号地址的值是54,如果下次要用18号地址的内容,CPU就不读内存,而是用cache的信息。往内存写入数据也一样,首先更新cache信息,然后再写入内存。

内存检查时,要往内存里写入一个值,然后马上读取,来检查读取的值与写入的值是否相等。如果内存连接正常,写入的值能够记在内存里,如果没连接上,则读取的值是乱七八糟的数。

BIOS 针脚 识别 内存 容量 bios检测内存_BIOS 针脚 识别 内存 容量


如果CPU使用了cache,则写入和读出的都是cache,所有内存检查都“正常”,检查处理不能完成。

综上,内存检查的步骤为:

1.首先关闭高速缓存:这里我们要先判断自己使用的CPU是否带有缓存(可以去掉判断,现在的CPU都有cache),然后关闭。

2.通过向内存写入数据,操作,看内存是否正确,然后在操作,看数据是否正确。最后回复原值。

注意:这里要注意C编译器的优化功能,因此作者把检测这一段使用了汇编来编写。

代码如下:

unsigned int memtest (unsigned int start, unsigned int end)
{
	char flg486 = 0;
	unsigned int eflg, cr0, i;
	
	/* 确认CPU是386还是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;
}

/*  注意:
    这里要注意C编译器的优化功能,因此作者把检测这一段使用了汇编语言写在了nasfun.nas里。*/
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){   /* 每次增加0x1000 相当于4KB */
		p = (unsigned int *)(i+0xffc);  //只检查后四个字节
		old = *p;            // 先记住修改前的值 
		*p = pat0;           // 试写 
		*p ^= 0xffffffff;    // 反转 
		if (*p != pat1){     // 检查反转结果 
	not_memory:
				*p = old;
				break;
		}
		*p ^= 0xffffffff;    // 再次反转 
		if (*p != pat0){     // 检查值是否恢复 
			goto not_memory; 
		}
		*p = old;            // 恢复为修改前的值 
	}
	return i;
}

nasfun.nas中的memtest_sub函数:

_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;  加12是因为前面有3次PUSH,堆栈指针下移12
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

bootpack.c节选:

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范围,0x00400000号以前的内存已经被使用了(参考8.5节内存分布图),没有内存程序根本不会运行到这里,所以没做内存检查。

如果在QEMU上运行,根据模拟器的设定,内存应为32MB。

BIOS 针脚 识别 内存 容量 bios检测内存_操作系统_02

2.内存管理
内存管理的基础:一是内存分配,而是内存释放。
书中介绍了两种方法:
法一:
以4KB(举例)为单位,然后建立数组。若已用,值为1;若未用,值为0。(这种方法很简单,但是有很大的压缩空间。因为记录一个单元的状态的只有0,1两种,也就是说1位就够了 ,内存记录所占空间变为原来的1/8,但程序变得复杂。)
优点:不用做拼接,碎片问题容易解决,内存使用的比较充分。
缺点:内存记录所使用的存储比较大,并且当分配或释放大块内存时,需要大量的写1或写0。

法二:
采用列表管理的方法,把类似“从xx号地址开始的yy字节是空的”这种信息都列在表里。
优点:省内存,而且在分配和释放大块内存时都很迅速。
缺点:管理程序麻烦,可用空间可能被搞得零零散散,怎么都归纳不到一块儿时,会将1000条可用空间管理信息全部用完。

作者最终在法二的基础上,加了一点处理,即“割舍掉的东西,只要以后还能找回来,就暂时不去管它”。

bootpack.c 代码如下:

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中代码:

...
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);

BIOS 针脚 识别 内存 容量 bios检测内存_操作系统_03

这些天由于忙于其他事而停下了对操作系统的学习,今天重新拾起,再接再厉,加油!