《操作系统30天》-合川秀实-学习日志day9

一、整理源文件

  • 改造bootpack.c文件,整理程序:

  • 键盘中断放在keyboard.c中,鼠标中断放在mouse.c中,这样程序看起来简洁清晰

  • 在bootpack.c分了两个头文件出来之后,需要在makefile中的objs_bootpack中添加相应的.obj文件(编译后的二进制文件)

二、内存检查

  • 对内存检查前,要关闭高速缓存。如果不关闭高速缓存,我们往内存中写数据会直接保存到高速缓存中,再次查询会直接从高速缓存中查找,这样就不能实现内存检查。386的CPU没有缓存,所以要先检查CPU是386还是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是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;
}
代码所用到的load_cr0和store_cr0只能用汇编语言实现:
_load_cr0:		; int load_cr0(void);
		MOV		EAX,CR0
		RET

_store_cr0:		; void store_cr0(int cr0);
		MOV		EAX,[ESP+4]
		MOV		CR0,EAX
		RET
Memtest_sub函数是内存检查处理的实现部分:
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[作者本来是+4,运行时速度太慢改成了+0x1000,相当于4kb,速度提高1000倍。下面的p赋值本身是p=i(只检查开头的四个字节)现在修改成p=i+0xffc只检查末尾四个字节]) {
		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;
}

程序所做的事情:

最后恢复为修改前的old。以上每次检查如果出错都会在那个环节停止并返回终止地址。

主函数中加入:

                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号以上的内存已被占用(下图),内存单位为MB。

运行结果为3072MB

三、优化容量检查(改错)

_memtest_sub:
	PUSH	EBP						;C编译器的固定语句
	MOV	EBP,ESP
	MOV	EDX,DWORD [12+EBP]		;EDX=end;
	MOV	EAX,DWORD [8+EBP]		;EAX=start;/*EAX是i*/
	CMP	EAX,EDX					;if(EAX>EDX) goto L30
	JA	L30
L36:
L34:
	ADD	EAX,4096					;EAX+=0x1000
	CMP	EAX,EDX					;if(EAX<=EDX)goto L36
	JBE	L36	
L30:
	POP	EBP							;接收前文中的PUSH的EBP
	RET									;return

与memtest_sub比较,发现编译结果连异或语句都没有。分析编译器编译过程:

编译器做了最优化处理,作者最后用汇编写memtest_sub。保存在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

根据注释,汇编对应C语言的工作,最后运行结果:

四、内存管理

什么是内存管理?

内存管理的基础:内存分配,内存释放

作者举例说明: 

假设有128MB=0x08000000个字节的内存,假设以0x1000(4KB)管理,根据0x08000000/0x1000=0x08000=32768得

以上方法好理解但是如果内存过大,管理表也会因此变大,虽然最后计算出所占的比例之后0.02%,如果要用这种方法且使管理表尽量小,那么可以用bit来构成管理表,这样管理表可以缩减到原来的1/8,如果内存是3GB那么管理表大小这样算出的比例为0.003%。

第二种方法是类似于“从xxx号地址开始的yyy字节的空间是空着的”这种信息存在表里:

第二种方法比第一种占用的内存少,memman=8x1000+4,不到8KB的大小。除所占空间小之外,在分配内存时,运算只需要执行一次,缺点在于管理程序复杂,空间零碎,有些小块内存没有空余的信息表可用智能被割舍,作者用这种方法的同时,实现把小块内存找回。

#define MEMMAN_FREES		4090	/* 大约是32KB */
#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[内存初始化函数,全部置0处理]) 
{
	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) {
			/* 找到足够大的内存[找到时,地址往后移size大小,同时本身的size-分出去的size,如果size=0,表示刚好用完说明该块不可用,减去] */
			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]; /* 代入结构体[结构体赋值方法,可以直接赋值,表示结构体中的信息(size,addr一致)] */
				}
			}
			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[从i=0开始找,如果addr后面有可用的内存那么此时i保持找到内存的值,退出。此时的i大于0]) {
			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[如果前面两种情况都不满足,那么此时应先判断是否溢出,如果frees满了那么就不会保存了,返回失败,lost++]) {
		/* 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; /* 失败 */
}

放入主函数中:

释放了0x00001000开始0x0009e000大小和4MB后面的内存。

这个函数主要表达的意思:

  • 第一种情况:若此时的addr=free[i-1]+size且man[i-1]+size+size(新的)=free[i].addr说明刚好放进去,此时三个合并,frees--,后面的结构体往前移,如下图所示:

  • 第二种情况:若此时的addr=free[i-1]+size后面没有空间或者不满足man[i-1]+size+size(新的)=free[i].addr,则只是把新的和free[i-1]合并,如下图所示:

  • 第三种情况:若此时找到的i<frees(最大值)且addr+size=man.free[i].addr说明刚好与后面的块连接,如下图所示:

  • 第四种情况:若此时找到的i<frees(最大值)但是上面三种情况都不满足,只能新建一个free[i],后面的块往后一个挪一位,frees++如下图所示:

  • 如果都都不满足条件说明释放失败!lost+1,下面是运行结果

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值