動手寫操作系統10----內存管理實現

本節實現操作系統可用內存的檢測,分析以及簡單的鏈表內存管理

1.可用內存檢測的實現

使用BIOS 15h中斷來檢測內存,具體步驟如下:

1: 將寄存器ax 賦值爲 0E820h
2 :   將寄存器ebx 初始化爲0,該寄存器的內容會被BIOS修改,須保證內存查詢過程中,該寄存器不會被修改。
3: es:di 指向一塊足夠大的內存地址,BIOS會把有關內存的信息寫到這個地址,內存信息是一種數據結構,稱之爲地址範圍描述符
4: ecx 寄存器存儲es:di所指向的內存大小,以字節爲單位,BIOS最多會填充ecx個字節的數據,通常情況下,無論ecx的數值是多少,BIOS都只填充20字節,有些BIOS直接忽略ecx的值,總是填充20字節。
5: edx寄存器的值設置爲0534D4150h, 這個數值其實對應的是字符組合"SMAP",其作用我們可以暫時忽略。

配置後執行int 15h 中斷,中斷結果的分析如下:

1: 判斷CF位,如果CF位設置爲1,則表示出錯
2: eax 會被設置爲0534D4150h, 也就是字符串'SMAP'
3 :   es:di 返回地址範圍描述符結構指針,跟輸入時相同
4: 如果ebx的值爲0,表明查詢結束,如果不爲0,則繼續調用15h獲取有關內存的信息

持續調用15h中斷,BIOS會將返回的地址描述符信息從es:di寫入內存

定義存儲地址描述符的起始地址以及內存塊的個數

;檢測內存數據
LABLE_MEM_BUF:
	times 256 db 0
	
;內存塊數量
;dd定義雙字類型變量,一個雙字數據佔4個字節單元,讀完一個,偏移量加4
LABLE_MEM_COUNT:
	dd 0

開始執行內存檢測

        ;檢測系統內存
	mov ebx, 0
	mov di, LABLE_MEM_BUF
	
LABLE_MEM_CHK_LOOP:
	mov eax, 0xE820
	mov ecx, 20
	mov edx, 0x534D4150
	INT 0x15
	jc  LABLE_MEM_CHK_FAIL
	add di, 20
	inc dword [LABLE_MEM_COUNT]
	cmp ebx, 0
	jne LABLE_MEM_CHK_LOOP
	jmp LABLE_MEM_CHK_OK
	
LABLE_MEM_CHK_FAIL:
	mov dword [LABLE_MEM_COUNT], 0

2.內存塊的解析

內存地址描述符結構如下所示:

//內存段描述信息
//高4字節addrHigh和低4字節addrLow共同組成64位內存地址
//高4字節lenHigh和低4字節lenLow共同組成64位內存長度信息
//4字節type字段描述內存使用情況, 
//type等於1,表示當前內存塊可以被內核使用。
//type等於2,表示當前內存塊已經被佔用,系統內核絕對不能使用。
//type等於3,保留給未來使用,內核也不能用當前內存塊。

typedef struct _AddrRangeDesc{
	unsigned int addrLow;
	unsigned int addrHigh;
	unsigned int lenLow;
	unsigned int lenHigh;
	unsigned int type;
}AddrRangeDesc;

其中type屬性用來標識當前內存塊是否能夠被內核使用。

將內存地址描述符的起始地址和內存塊數量返回,用作C函數調用 對應聲明如下

//獲取內存塊數量
extern int mem_block_count();

//中斷返回內存信息起始地址
extern char* get_addr_mem_buffer();

對應彙編實現如下:

;獲取內存塊數量
mem_block_count:
	mov eax, dword [LABLE_MEM_COUNT]
	ret
	
;獲取內存信息數據指針
get_addr_mem_buffer:
	mov eax, LABLE_MEM_BUF
	ret

在主函數中分析內存塊信息  獲取內存地址描述符起始地址 

    //中斷返回內存信息首地址
  	AddrRangeDesc* memDesc = (AddrRangeDesc*)get_addr_mem_buffer();

不斷監聽回車鍵鍵盤中斷 依次解析內存塊信息  並顯示在屏幕中

        for(;;){
		if (keybufInfo.len > 0) {
			io_cli();
			char data = fifo8_get(&keybufInfo);
			//回車鍵
			if(data == 0x1C){
				showMemInfo(memDesc + count, count);
				count += 1;
				if(count > memCount){
					count = 0;
				}
			}
			/*for(int i=0; i<keybufInfo.len; i++){
				char data = fifo8_get(&keybufInfo);
				static int x = 0;
				char2HexStr(data, keyval);
				showString(keyval, x%SCREEN_WIDTH, x/SCREEN_WIDTH*20, COL8_FFFFFF);
				x += 32;
			}*/
			io_seti();
		} else if (mousebufInfo.len > 0) {
			io_cli();
			for(int t=0;t<mousebufInfo.len;t++){
				mouseCursorMoved(&mouseDes, COL8_008484);
			}
			io_seti();
		} else {
			io_hlt();
		}
    }

具體的顯示函數如下:

void showMemInfo(AddrRangeDesc *desc, int page){
	
	int x = 0, y = 20, t = 8*12;

    fillRect(x, y, SCREEN_WIDTH, SCREEN_HEIGHT-30, COL8_008484);

	showString("page is: ", x, y, COL8_000000);
    char* pPageCnt = intToHexStr(page);
    showString(pPageCnt, x+t, y, COL8_000000);
	
    y += 16;
	showString("BaseAddrL: ", x, y, COL8_000000);
    char* pBaseAddrL = intToHexStr(desc->addrLow);
	showString(pBaseAddrL, x+t, y, COL8_000000);
	
    y += 16;
	showString("BaseAddrH: ", x, y, COL8_000000);
    char* pBaseAddrH = intToHexStr(desc->addrHigh);
	showString(pBaseAddrH, x+t, y, COL8_000000);
  
    y += 16;
	showString("lengthLow: ", x, y, COL8_000000);
    char* pLengthLow = intToHexStr(desc->lenLow);
	showString(pLengthLow, x+t, y, COL8_000000);

    y+= 16;
	showString("lengthHigh: ", x, y, COL8_000000);
    char* pLengthHigh = intToHexStr(desc->lenHigh);
	showString(pLengthHigh, x+t, y, COL8_000000);

    y+= 16;
	showString("type: ", x, y, COL8_000000);
    char* pType = intToHexStr(desc->type);
	showString(pType, x+t, y, COL8_000000);
}

最終結果如下所示:

     

虛擬機內存設置爲1G,由以上可知,內存塊一共8塊  其中page=0,3是可以供內核使用的。

3.簡單內存管理

本部分實現對於內核可用內存的簡單鏈式管理,即採用鏈表的形式來存儲可用的內存塊。

對於每一個內存塊來說,基本屬性有兩個 起始地址和塊大小。

//內存片
//起始地址和大小
typedef struct _FREEINFO{
	unsigned int *addr, size;
}FREEINFO;

定義內存管理器

內存管理器用來管理內存片,這裏定義內存片的最大數量爲#define MEMMAN_FREES 4096

//內存管理器
typedef struct _MEMMAN{
	int frees, maxfrees, lostsize, losts;
	struct _FREEINFO free[MEMMAN_FREES];
}MEMMAN;

//初始化內存管理器
void memman_init(MEMMAN *man);

//內存總共可用量
unsigned int memman_total(MEMMAN *man);

//分配內存
unsigned int* memman_alloc(MEMMAN *man, unsigned int size);

//釋放內存
int memman_free(MEMMAN *man, unsigned int *addr, unsigned int size);

初始化內存管理器實現

void memman_init(MEMMAN *man){
	man->frees = 0;
	man->maxfrees = 0;
	man->lostsize = 0;
	man->losts = 0;
	return;
}

計算所有可用內存如下

unsigned int memman_total(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(MEMMAN *man, unsigned int size){
	unsigned int *a;
	for(int i=0; i<man->frees; i++){
		if(man->free[i].size>=size){
			a = man->free[i].addr;
			man->free[i].size -= size;
			man->free[i].addr = (unsigned int *)(((char *)(man->free[i].addr))+size);
			if(man->free[i].size==0){
				for(int j=i+1; j<man->frees; j++){
					man->free[j-1] = man->free[j];
				}
				man->frees--;
			}
			return a;
		}
	}
	return (unsigned int*)0;
}

當釋放內存時,找到待釋放內存塊起始地址位於內存管理器鏈表中的位置,同時也要考慮待釋放的內存塊是否能與前後進行合併成爲更大的可用內存塊

int memman_free(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;
		}
	}
	
	//可以往前合併或者同時前後合併
	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->frees--;
				}
			}
			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;
		}
	}
	
	//不能和前後合併 
	//如果此時內存片數量沒有超過最大值 則新增一個空閒塊
	//將釋放的內存塊放在i位置  i位置的內存塊依次後移
	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;
}

        主函數,獲取內存地址描述符的起始地址,轉換爲結構體指針,開始遍歷每一個可用內存塊,將其加入到內存管理器中,然後返回可用內存總數量。 這裏要預留出內存管理器的內存,其餘內存加入到可用內存鏈表中。

//顯示總可用內存塊數量
	int memCount = mem_block_count();
	char2HexStr(memCount, keyval);
	showString(keyval, 0, 0, COL8_FFFFFF);
	
	//中斷返回內存信息首地址
	AddrRangeDesc* memDesc = (AddrRangeDesc*)get_addr_mem_buffer();
	
	int y = 0;
	int c = 0;
	unsigned int addr;
	unsigned int size;
	unsigned int type;
	int mem_total = 0;
	for(int i=0; i<memCount; i++){
		addr = (memDesc+i)->addrLow;
		size = (memDesc+i)->lenLow;
		type = (memDesc+i)->type;
		//檢測所有可用內存
		//以1G內存爲例 一共有8塊 1,4塊是可用內存
		//0x00000000-0x0009FC000
		//0x00010000-0x3FEF0000
		if(type == 1){
			char* pBaseAddrL = intToHexStr(addr);
			showString(pBaseAddrL, 60, y, COL8_000000);
			char* lenLow = intToHexStr(size);
			showString(lenLow, 160, y, COL8_000000);
			y += 20;
			//找到第一塊可用內存
			// 從網上資料  直接從第三塊內存開始初始化使用會報錯
			// 從第一塊內存初始化內存管理器,第一塊內存加入到內存管理器memman
			// 但是第一塊內存要空餘4096*1024=0x8000內存用來存放最多4096個內存塊
			if(c == 0){
				memman = (MEMMAN *)(addr);
				memman_init(memman);
				memman_free(memman, addr+0x8000, size-0x8000);
				c++;
			}else{
				//其餘的可用內存塊直接放入內存管理器
				memman_free(memman, addr, size);
				mem_total = memman_total(memman)/1024/1024;
				char* memTotal = intToHexStr(mem_total);
				//顯示所有可用內存 以MB爲單位
				showString("Toatl Mem: ", 60, y, COL8_000000);
				showString(memTotal, 160, y, COL8_000000);
				y += 20;
			}
		}
	}

最終結果顯示如下

可知對於1G虛擬內存來說,可用內存有3FF=1023MB

代碼位置:https://github.com/ChenWenKaiVN/bb_os_core

參考鏈接:

https://www.jianshu.com/p/479b2e951c23

https://blog.csdn.net/Zllvincent/article/details/84138323

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章