[Rx86OS-X] 内存管理·简

平台

处理器:Intel Celeron® Dual-CoreCPU

操作系统:Windows 7 专业版 x86

阅读书籍:《30天自制操作系统》—川合秀实[2015.04.05-04.06]

工具:../toolset/


1 内存使用情况

编写完鼠标移动程序后,内存分布如下。

Figure1. 内存使用情况

2 检测可用内存容量大小

x86保护模式下的内存地址空间为0x00000000 ~ 0xFFFFFFFF,其中包含ROM等存储器的地址空间,需要检测出RAM容量。在检测可用内存容量大小时,需要将CPU内的高速缓存(cache)功能关闭。才无cache功能的情况下,检测出的RAM空间才是准确的。


2.1 关闭高速缓存(cache)功能

首先检查CPU是否支持cache功能(EFLAG),如果支持cache,则关闭cache功能(CR0)。

/*检查内存地址空间中的RAM
 *start,end分别是需要检查的内存的起始和结束地址 
 */
unsigned int memtest(unsigned int start, unsigned int end)
{
	char flg486 = 0;
	unsigned int eflg, cr0, i;

	//80386 CPU不支持cache,检查EFLAG的第18位
	//如果不支持cache,EFLAG的bit[17]始终为0
	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) {//支持cache
		flg486 = 1;
	}
	eflg &= ~EFLAGS_AC_BIT; 
	io_store_eflags(eflg);

	if (flg486 != 0) {
		cr0 = load_cr0();
		cr0 |= CR0_CACHE_DISABLE; //禁止cache功能:将cr0的bit[30:29]置1
		store_cr0(cr0);
	}

	i = memtest_sub(start, end); //调用反转法检查RAM的函数

	if (flg486 != 0) {
		cr0 = load_cr0();
		cr0 &= ~CR0_CACHE_DISABLE; //允许cache
		store_cr0(cr0);
	}

	return i;
}

load_cr0()和store_cr0(cr0)函数是在naskfunc.nas中用汇编指令来操作CR0寄存器的汇编函数:

 

1.        GLOBAL   _load_cr0, _store_cr0

2.        …

3.        _load_cr0:                 ; int load_cr0(void);

4.                           MOV                  EAX,CR0

5.                           RET

6.         

7.        _store_cr0:               ; void store_cr0(int cr0);

8.                           MOV                  EAX,[ESP+4]

9.                           MOV                  CR0,EAX

10.                   RET

 

2.2 反转法检查RAM

用“书”中“反转内存中的值”的方式检测内存是否为RAM(由于C编译器ccl.exe会优化掉反转代码,所以检测可用内存容量大小的程序直接在naskfunc.nas中采用汇编语言来写)。

 

1.        _memtest_sub:    ;unsigned int memtest_sub(unsigned intstart, unsigned int end)

2.                                         ;start为检查内存段的起始地址,end为结束地址

3.                 PUSH        EDI  ;备份进入此函数前的寄存器的值

4.                 PUSH        ESI

5.                 PUSH        EBX

6.                 MOV         ESI,0xaa55aa55

7.                 MOV         EDI,0x55aa55aa

8.                 MOV         EAX,[ESP+12+4]        ;i = start;12为EDI, ESI,EBX 3个寄存器使用的栈空间,

9.                                                               ;4为CALL指令引起的(CS:)IP压栈空间

10.    mts_loop:

11.             MOV         EBX,EAX

12.             ADD EBX,0xffc             ;以4KB为单位进行检查,检查4KB内存段的最后四个字节;

13.             MOV         EDX,[EBX]         ;将[EBX]中的内容备份

14.             MOV         [EBX],ESI           ;往[EBX]中写入0xaa55aa55

15.             XOR DWORD[EBX],0xffffffff    ;反转[EBX]内存中的每一位

16.             CMP          EDI,[EBX]             ;检查[EBX]内存中的值是否反转成功,

17.             JNE  mts_fin                       ;不成功则跳转到mts_fin处

18.            

19.             XOR DWORD[EBX],0xffffffff    ;再反转[EBX]4字节内存中的内存

20.             CMP          ESI,[EBX]                   ;检查是否反转成功,

21.             JNE  mts_fin                           ;反转不成功则跳转到mts_fin处

22.             MOV         [EBX],EDX                  ;将[EBX]的值恢复

23.             ADD EAX,0x1000                   ;检查下一个4KB内存块,

24.             CMP          EAX,[ESP+12+8]                 ;如果低于end则继续检查,否则返回EAX

25.             JBE   mts_loop

26.             POP  EBX

27.             POP  ESI

28.             POP  EDI

29.             RET

30.    mts_fin:

31.             MOV         [EBX],EDX                  ;恢复[EBX]的值

32.             POP  EBX

33.             POP  ESI

34.             POP  EDI

35.         RET
 

3 管理内存·简

3.1 选择数据结构

对于机器级及以上的程序来说,内存的管理都是基于逻辑的内存地址空间。首先需要一种数据结构来描述内存地址空间。

#define MEMMAN_FREES	4090  //空闲的不连续的内存块数目

struct FREEINFO {	//内存地址和大小
	unsigned int addr, size;
};

struct MEMMAN {
	//空闲内存空间个数,空闲内存空间的最大个数,
	//没能紧跟空闲内存释放的内存大小和个数
	int frees, maxfrees, lostsize, losts;
	struct FREEINFO free[MEMMAN_FREES];
};

3.2 内存分配

查看保存在数据结构内的空闲内存块,找到符合要求的内存块则返回此内存块的首地址。将分配出去的内存块从空闲内存块中除去。

/* 分配一块内存来使用
 * man是描述当前内存空闲与否的数据结构
 * size是当前要分配内存的大小
 * 如果分配成功则返回所分配内存的首地址,失败则返回0
 */
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;
			
			//如果free[i]这块内存刚好被分完,则在数据结构中将其溢出
			if (man->free[i].size == 0) {
				
				man->frees--;
				for (; i < man->frees; i++) {
					man->free[i] = man->free[i + 1];
				}
			}
			return a;
		}
	}
	return 0;	//无空闲内存
}

3.3 内存释放

在标记内存可用的数据结构中,按照内存地址从小到大的顺序标记每一块可用的内存空间。释放不用的内存时也按照内存地址大小顺序标记在数据结构中。

/* 释放内存:将不用的内存标记在数据结构中,表明这些内存处于空闲状态
 * man为描述内存空闲状态的数据结构
 * addr,size分别为释放内存的起始地址和大小
 * 释放[addr,addr + size]这段内存成功时返回0,否则返回-1
 */
int memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size)
{
	int i, j;
	
	//数据结构描述空闲内存空间以内存地址从小到大的方式
	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;
					//在数据结构中清除free[i],它已经合并到free[i-1]描述的空闲内存中了
					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) {
		
		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; 
}

3.4 其它函数

其它函数是为记录内存空闲的数据结构而服务的。

//给记录空闲内存的数据结构初始值
void memman_init(struct MEMMAN *man)
{
	man->frees = 0;
	man->maxfrees = 0;
	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;
}

4 分析

4.1 组织文件

将_memtest_sub函数写进naskfunc.nas文件中,将其余函数写进memman.c文件中,按照“书”中那样组织主函数HariMain()的内容。依据各文件关系在各个文件中作声明,并修改Makefile。通过编译、链接后,在“!cons_nt.bat”中使用“makerun”命令运行程序,得到如下界面:

Figure2. 程序运行结果

4.2 引用结构体元素

#define MEMMAN_FREES		4090
#define MEMMAN_ADDR		0x003c0000

struct FREEINFO {
	unsigned int addr, size;
};

struct MEMMAN {
	int frees, maxfrees, lostsize, losts;
	struct FREEINFO free[MEMMAN_FREES];
};
struct MEMMAN *memman = (struct MEMMAN *) 0x003c0000;

x86保护模式下,memman= 0x003c0000;memman->frees表示0x003c0000  +  0~0x003c0003这4字节内存;memman->maxfrees表示0x003c0000+ 4 ~ 0x003c0007这4字节内存;…;结构体被编译器编译后,每个元素表示相对结构体起始地址的偏移值。引用结构体元素时,按照“结构体起始地址+ 元素偏移值”访问到对应的内存。

Figure3. 编译器和结构体

4.3 内存管理函数的运行

内存检测函数检测0x00400000~ 0xbfffffff这段(0x00400000之前的都被使用过了,是RAM)内存空间(32M)都为RAM。


初始化structMEMMAN结构体的函数没有初始化结构体内的数组,在释放首地址为0x00001000,大小为0x0009e000这段内存空间时,这种情况属于释放内存不与数据结构记录的空闲空间相邻,故而.free[0].addr= 0x00001000,.free[0].size= 0x0009e000(647KB);再释放首地址为0x00400000,大小为32M– 0x00400000(28M)这段内存空间时,这段空闲内存将会被记录在.free.[1]中。所以在数据结构structMEMMAN中的空闲内存约29M左右。


4.4 以4KB为单位管理内存

内存的零散指在数据结构中记录的空闲内存空间的不连续。以字节为单位来管理内存会比以更大单位管理内存零散度要大,就会增加记录空闲空间变量的压力。为了内存空间零散度减小,可以增加管理内存的单位。x86操作系统大都以4KB为单位来管理内存,跟随。
/*以4KB为单位的内存分配
 *man为记录内存空闲的数据结构
 *size所需分配内存大小
 *分配成功返回分配内存的首地址,分配失败返回0
 */
unsigned int memman_alloc_4k(struct MEMMAN *man, unsigned int size)
{
    unsigned int a;
    size    = (size + 0xfff) & 0xfffff000;    //以4KB(0x1000)向上舍入
    a      = memman_alloc(man, size);
    return a;
}


/*以4KB为单位进行释放内存,以空闲的状态记录在数据结构中
 *man为记录内存空闲的数据结构,
 *addr为即将释放内存的首地址,
 *size为即将释放内存的大小
 *内存释放成功返回0,否则返回-1
 */
int memman_free_4k(struct MEMMAN *man, unsigned int addr, unsigned int size)
{
    int i;
    size    = (size + 0xfff) & 0xfffff000; //以4KB(0x1000)向上舍入
    i      = memman_free(man, addr, size);
    return 0;
}

将这两个函数声明后,替换主函数HariMain()中的内存释放函数。就这样一步一步,内存分配和内存释放函数变得越来越靠谱。


HariMain()

/*bootpack.c*/

#include "bootpack.h"
#include <stdio.h>


void HariMain(void)
{
	int 	data;
	int 	mx, my;
	char	str[30];
	char	mcursor[256];
	char	mousebuf[128];	//接收鼠标数据缓冲区
	unsigned int memtotal;
	struct MOUSE_DEC mdec;
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
	
	init_gdtidt();
	
	
	init_pic();
	
	//CPU处理中断-----------
	io_sti();
	fifo8_init(&mousefifo, 32, mousebuf); //鼠标数据的缓冲区FIFO
	io_out8(PIC0_IMR, 0xfb);//主PIC IRQ2中断开启
	io_out8(PIC1_IMR, 0xef); //从PIC IRQ12中断开启

	
	init_keyboard();

	memtotal = memtest(0x00400000, 0xbfffffff);
	memman_init(memman);
	memman_free_4k(memman, 0x00001000, 0x00000300); //1kb
	memman_free_4k(memman, 0x00400000, 0x00000400);//2kb
	
	init_palette();
	enable_mouse(&mdec);
	
	init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
	
	
	init_mouse_cursor8(mcursor, COL8_000000);
	
	sprintf(str, "memory %dMB   free : %dKB",
			memtotal / (1024 * 1024), memman_total(memman) / 1024);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 32, COL8_FFFFFF, str);

	
	//不断查询缓冲区,如果有数据,将缓冲区内的数据显示完为止
	for (;;) {
		io_cli(); //查看缓冲区时,屏蔽中断
		if (fifo8_status(&mousefifo) == 0) {
			io_stihlt(); //如果缓冲区内无数据则开启中断并让CPU休眠,直到中断唤醒
		} else {
			data = fifo8_get(&mousefifo);
			io_sti(); //从缓冲区读完数据立即开启中断
			if (mouse_decode(&mdec, data) != 0) {
				//获得解读到的鼠标坐标
				sprintf(str, "[lcr %4d %4d]", mdec.x, mdec.y);
				if ((mdec.btn & 0x01) != 0) {
					str[1] = 'L';
				}
				if ((mdec.btn & 0x02) != 0) {
					str[3] = 'R';
				}
				if ((mdec.btn & 0x04) != 0) {
					str[2] = 'C';
				}
				boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 + 15 * 8 - 1, 31);
				putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, str);
					
				boxfill8(binfo->vram, binfo->scrnx, COL8_000000, mx, my, mx + 15, my + 15);//隐藏上次鼠标移动留下的痕迹
				mx += mdec.x;
				my += mdec.y;
				if (mx < 0) {
					mx = 0;
				}
				if (my < 0) {
					my = 0;
				}
				if (mx > binfo->scrnx - 16) {
					mx = binfo->scrnx - 16;
				}
				if (my > binfo->scrny - 16) {
					my = binfo->scrny - 16;
				}
				sprintf(str, "(%3d, %3d)", mx, my);
				putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
			}
		}
	}
}

虽然实际要求的是释放1kb + 2kb = 3kb的内存,但实际会释放4kb + 4kb = 8kb的内存:

Figure4. 以4kb为单位管理内存

[x86OS] Note Over.

[2015.04.17]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值