自制操作系统日志——第九天

本文记录了自制操作系统第九天的工作,主要内容包括内存管理。首先整理源文件,然后进行内存容量检查,通过关闭高速缓存来避免检测错误。文章详细介绍了内存检查的步骤和遇到的编译器优化问题,最终采用汇编实现内存检查函数。接着,文章讨论了两种内存管理方法:位图管理和列表管理,并选择了列表法进行实践。最后,实现了内存管理的相关函数,并在主程序中进行测试。
摘要由CSDN通过智能技术生成

自制操作系统日志——第九天

今天主要的工作内容就是进行内存管理,至于前一天留下的叠加问题,我们放到明天去处理!



一、整理源文件、内存容量检查

在正式开始前,我们首先进行整理一下昨天遗留的源文件。使得整体的代码容量看起来更加的简洁:
我们将之前的鼠标与键盘的代码分别创建对应的c文件:keyboard.c mouse.c 这两个文件。进行分割完以后,再在makefile的OBJS_BOOTPACK 的后面加上keyboard.obj mouse.obj 这两个文件后便可以编译,链接与运行了!!!

接下来,我来解释一下为什么要做内存容量的管理:
⇒ 首先,这是因为如果我们不能很好的规划内存的使用状况的话,可能会使得系统变得一团糟。运行于系统之上的程序将不知道哪里的内存可用,从而导致程序使用的内存出现混乱,也可能会出现多个应用程序使用同一部分的内存,导致程序可能出错。

因此,我们需要进行有效的内存管理。那么再次之前,我们先查看一下模拟器给我们使用的内存容量大概有多大。

对了,因为由于486中含有高速缓冲寄存器的存在。因此,简要介绍一下。高速缓存: 主要为了解决cpu与内存中间存在的速度的差异性的问题,cache会存储着访问内存的地址和内容(一般都是即将或者最近要使用的)。cpu每次要访问内存时,都会先将所访问的地址和内容放入cache中,再有cache放入内存。而读数据时,也是先从cache里先读的。

因此,若我们想通过往内存里写入值来检测内存的可使用大小的话,我们需要先把cache关掉。不然,cache存储着内存的值,会让我们检测是否写入成功的操作变得没有用。(因为写入时值就在cache中,如果没有替换掉,那么继续读内存时候依旧会读取到的)

检测的整体思路以及bug修改:

整体的思路就是:

  • 确认cpu的型号;判断是否有高速缓冲寄存器;
  • 关掉高速缓冲寄存器;
  • 开始循环进行检查操作(为加快速度,每次循环4kb,检测最后的两字节)
  • 保存写入的内存地址的旧数据
    -向内存写入数据
  • 写入数据后,为防止机型原因,对内存的数据进行反转后对比,确认写入成功
  • 恢复旧数据

在这个整体思路的过程中,代码的逻辑上是没什么问题的,但是实际检测过程中却出现了意外(由于我们的模拟器默认可使用的是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,即拖入到下面的框框时,会把框框覆盖掉!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值