12 Day:内存管理

 前言:今天我们要完成我们操作系统的内存管理,以及一些数据结构和小组件的实现,在此之前大家需要了解我们前几天一些重要文件的内存地址存放在哪,以便我们更好的去编写内存管理模块 


 

一,实现ASSERT断言

不知道大家有没有在C或者Java中使用ASSERT断言函数,没用过的话也没关系,我们要实现这个函数的原因很直接,当内核出现问题我们不可能在bochs中一行一行汇编代码区查看问题,所以我们需要一个函数来起到Debug的作用,废话不多说先开始着手准备吧! 

ASSERT函数实现流程:

① 在关中断下运行,毕竟发生异常时,不能被其他的中断给干扰

② 实现其在屏幕上的输出(问题,代码,函数,行数)

① 在关中断下运行,毕竟发生异常时,不能被其他的中断给干扰

首先在interrupt.c中我们来实现开关中断(部分代码更新)

#define EFLAGS_IF 0x00000200 //eflags寄存器的if位
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g" (EFLAG_VAR)) //获取eflags的值

/**获取当前中断**/
enum intr_status intr_get_status() {
	uint32_t eflags = 0;
	GET_EFLAGS(eflags);
	return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}


/**打开中断**/
enum intr_status intr_enable() {
	enum intr_status old_status;
	if (intr_get_status() == INTR_ON) {
		old_status = INTR_ON;
		return old_status;
	}
	else {
		old_status = INTR_OFF;
		asm volatile("sti");
		return old_status;
	}
}

/**关闭中断**/
enum intr_status intr_disable() {
	enum intr_status old_status;
	if (intr_get_status() == INTR_ON) {
		old_status = INTR_ON;
		asm volatile("cli");
		return old_status;
	}
	else {
		old_status = INTR_OFF;
		return old_status;
	}
}

/**根据中断状态打开关闭中断**/
enum intr_status intr_set_status(enum intr_status status) {
	return status & INTR_ON ? intr_enable() : intr_disable();
}

interrupt.h

#ifndef  _KERNEL_INTERRUPT_H
#define _KERNEL_INTERRUPT_H
#include "stdint.h"
typedef void* intr_handler;

void idt_init(void);

enum  intr_status
{
	INTR_OFF,
	INTR_ON
};
enum intr_status intr_get_status();
enum intr_status intr_enable();
enum intr_status intr_disable();
enum intr_status intr_set_status(enum intr_status status);


//void register_intr(uint32_t vectr, intr_handler func, char* name);
#endif

② 实现ASSERT

kernel/debug.h 

#ifndef __KERNEL_DEBUG_H
#define __KERNEL_DEBUG_H
void panic_spin(char* filename, int line,const char* func,const char* condition);
#define PANIC(...) panic_spin(__FILE__,__LINE__,__func__,__VA_ARGS__)  //这里是ansi c中常用的宏,分别是文件地址,代码行数,函数以及变量

#ifdef NDEBUG	//这是一个宏变量,如果定义了这个宏变量,则ASSERT不会起作用
	#define ASSERT(CONDITION) ((void)0)
#else
	#define ASSERT(CONDITION)\
			if(CONDITION){}else{\
				PANIC(#CONDITION); \
			}
#endif // NDEBUG

#endif // !__KERNEL_DEBUG_H

kernel/debug.c

#include "debug.h"
#include "print.h"
#include "interrupt.h"

void panic_spin(char* filename, int line,const char* func,const char* condition) {
	intr_disable();
	put_str("\n\n\n------------!!!OS ERROR!!!------------\n\n\n");
	put_str("filename:");put_str(filename);put_str("\n");
	put_str("line:");put_int(line);put_str("\n");
	put_str("function:");put_str(func);put_str("\n");
	put_str("condition:");put_str(condition);put_str("\n");
	put_str("\n\n\n------------!!!OS ERROR!!!------------\n\n\n");
	while (1);
}c

这样子ASSERT就完成了,如果你迫不及待想要试一下,你现在可以在main函数里面写一段

ASSERT(1==2);

 然后运行一下 看看是否会出现报错,如果出现报错就说明你成功了,由于我已经完成后面好几章了,忘记截屏成功截图了,所以我们接着往下面走吧!


 二,实现字符串操作函数

为了给我们后面的系统代码打下基础,我们先把这些"路"给铺好,字符串操作这里我就不过多介绍了,相信大家肯定看得懂的!

lib/string.h 

#include "string.h"
#include "global.h"
#include "debug.h"

/*将dst_地址起始的后size位置为value*/
void memset(void *dst_, uint8_t value, uint32_t size) {
	ASSERT(dst_ != NULL);
	uint8_t* dst = (uint8_t*)dst_;
	while (size-- > 0) {
		*(dst++) = value;
	}
}

/*将src_地址后size位的值复制到dst_中*/
void memcpy(void *dst_, const void *src_, uint32_t size) {
	ASSERT(dst_ != NULL);
	ASSERT(src_ != NULL);
	uint8_t* dst = dst_;
	const uint8_t* src = src_;
	while (size-- > 0) {
		*(dst++) = *(src++);
	}
}

/*比较,如果相等则为0,a>b为1,a<b为-1*/
int memcmp(const void* a_, const void* b_, uint32_t size) {
	const char* a = a_;
	const char* b = b_;
	ASSERT(a != NULL || b != NULL);
	while (size-- > 0) {
		if (*a > *b) {
			return 1;
		}
		else if (*a < *b) {
			return -1;
		}
		a++;
		b++;
	}
	return 0;
}

/*字符串从src_复制到dst_*/
char* strcpy(char* dst_, const char* src_) {
	ASSERT(dst_ != NULL && src_ != NULL);
	char* dst = dst_;
	while ((*(dst_++) = *(src_++)));
	return dst;
}

/*返回字符串长度*/
uint32_t strlen(const char* str_) {
	ASSERT(str_ != NULL);
	const char* p = str_;
	while (*(p++));
	return p - str_ - 1;
}

/*比较两个字符串,a=b返回0,a>b返回1,a<b返回-1*/
int8_t strcmp(const char* a, const char* b) {
	ASSERT(a != NULL && b != NULL);
	while (*a != 0 && *a == *b) {
		a++;
		b++;
	}
	return *a<*b ? -1 : *a>*b;
}

/*从前往后找到ch在str中首次出现的位置*/
char* strch(const char* str, const uint8_t ch) {
	ASSERT(str != NULL);
	while (*str!=0)
	{
		if (*str == ch) {
			return (char*)str;
		}
		str++;
	}
	return NULL;
}

/*从后往前找到ch在str中首次出现的位置*/
char* strrch(const char* str, const uint8_t ch) {
	ASSERT(str != NULL);
	const char* chr = NULL;

	while (*str != 0) {
		if (*str == ch) {
			chr = str;
		}
		str++;
	}
	return (char*)chr;
}

/*将字符串src_拼接到dst后*/
char* strcat(char* dst_, const char* src_) {
	ASSERT(dst_ != NULL && src_ != NULL);
	char* dst = dst_;
	while (*(dst_++));
	--dst_;
	while ((*(dst_++) = *(src_++)));
	return dst;
}

/*解决内存覆盖问题,将拼接的字符串重新分配一个地址*/
char* newstrcat(char* dst_, const char* src_) {
	ASSERT(dst_ != NULL && src_ != NULL);
	char* dst = "";
	char* p = dst;
	while (*(p++)=*(dst_++));
	--p;
	while ((*(p++) = *(src_++)));
	return dst;
}

/*在字符串str中查找字符ch出现的次数*/
uint32_t strchrs(const char* str, uint8_t ch) {
	ASSERT(str != NULL);
	uint32_t count = 0;
	while (*str != 0) {
		if (*(str++) == ch) {
			count++;
		}
	}
	return count;
}

 lib/string.h

#ifndef _LIB_STRING_H
#define _LIB_STRING_H
#include "stdint.h"
void memset(void *dst_, uint8_t value, uint32_t size);
void memcpy(void *dst_, const void *src_, uint32_t size);
int memcmp(const void* a_, const void* b_, uint32_t size);
char* strcpy(char* dst_, const char* src_);
uint32_t strlen(const char* str_);
int8_t strcmp(const char* a, const char* b);
char* strch(const char* str, const uint8_t ch);
char* strrch(const char* str, const uint8_t ch);
char* strcat(char* dst_, const char* src_);
char* newstrcat(char* dst_, const char* src_);
uint32_t strchrs(const char* str, uint8_t ch);
#endif


 三,bitmap

前面大费周章进行的铺路,现在终于走到内存管理的门口了,我们现在要介绍的是管理内存的数据结构---bitmap!!

什么是bitmap,从英语的角度解读你应该可以理解,就是位图,也就是一个矩阵中存放的都是bit位,0和1。

那这个位图又怎么应用到我们内存管理中来呢?

大家仔细想一想,在之前我们启用了内存分页对不对,在我们的分页系统中,一个页对应4KB物理内存,那在bitmap中我们这些bit映射的就是一个页,而0和1代表此页是否被占用,是不是很简单,而bit的位置就可以映射到当前物理内存的位置。

 我们该如何设计我们的位图?

① 用bit数组,数组元素存放的是0或者1

② 用字节数组,一个字节有8位,代表这一个数组元素管理8个资源单位

在此我选择的是第二种方法,两种方法都不难,你也可以选择第一种方法去实现都是🆗的

位图实现 

lib/kernel/bitmap.h

#ifndef _LIB_BITMAP_H
#define _LIB_BITMAP_H
#include "global.h"
#include "stdint.h"
#define BITMAP_MASK 1
struct bitmap {
	uint32_t btmp_bytes_len;    //bitmap的字节长度
	uint8_t* bits;              //bits数组
};

void bitmap_init(struct bitmap* btmp);
bool bitmap_scan_test(struct bitmap* btmp, uint32_t bit_idx);
int bitmap_scan(struct bitmap* btmp, uint32_t cnt);
void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, int8_t value);
#endif // _LIB_BITMAP_H

lib/kernel/bitmap.c

#include "bitmap.h"
#include "string.h"
#include "debug.h"

/*初始化bitmap*/
void bitmap_init(struct bitmap* btmp) {
	memset(btmp->bits, 0, btmp->btmp_bytes_len);
}

/*判断 bit_idx位是否为1,为1则返回1,否则返回0*/
bool bitmap_scan_test(struct bitmap* btmp, uint32_t bit_idx) {
	uint32_t arr_index = bit_idx / 8;//在bits的数组索引
	uint32_t arr_member_index = bit_idx % 8;//在bits的数组元素中的索引

	return (btmp->bits[arr_index]) & (BITMAP_MASK << arr_member_index);
}

/*在位图中申请连续cnt个位,成功返回下标,失败返回-1*/
int bitmap_scan(struct bitmap* btmp, uint32_t cnt) {
	int arr_index = 0;

    //先8位8位的判断,看看是否有0
	while ((0xff == btmp->bits[arr_index]) && arr_index < btmp->btmp_bytes_len) {
		arr_index++;
	}

	ASSERT(arr_index < btmp->btmp_bytes_len);
	if (arr_index == btmp->btmp_bytes_len) {
		return -1;
	}

    //发现该字节段有0后,便进行遍历查找0
	int arr_member_index = 0;
	while ((btmp->bits[arr_index])&(BITMAP_MASK << arr_member_index)) {
		arr_member_index++;
	}
    
	int bit_idx_start = arr_index * 8 + arr_member_index;
	if (cnt == 1) {
		return bit_idx_start;
	}

	int bits_left = btmp->btmp_bytes_len * 8 - bit_idx_start;
	int count = 1;
	int next_bit = bit_idx_start + 1;
    
    //向后遍历,有0就+1,碰到1就重新计数,直到走到尽头
	bit_idx_start = -1;
	while (bits_left--) {
		if (!(bitmap_scan_test(btmp, next_bit))) {
			count++;
		}
		else {
			count = 0;
		}

		if (count == cnt) {
			bit_idx_start = next_bit - cnt + 1;
			break;
		}

		next_bit++;
	}
	return bit_idx_start;
}

void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, int8_t value) {
	ASSERT((value == 0) || (value == 1));
	uint32_t arr_index = bit_idx / 8;//在bits的数组索引
	uint32_t arr_member_index = bit_idx % 8;//在bits的数组元素中的索引
	if (value) {
		btmp->bits[arr_index] |= (value << arr_member_index);
	}
	else {
		btmp->bits[arr_index] &= ~(value << arr_member_index);
	}
	
}


四,内存管理系统 

在我们的操作系统中,地址被分为了两部分,虚拟地址和物理地址,物理地址对应着内存资源,而虚拟地址经过分页机制映射到相应的物理地址,而目前我们内存管理系统要做的事情是:

  • 划分相应的内存池
  • 物理地址与虚拟地址建立联系
  • 为进程分配页空间

Ⅰ. 划分内存地址池

首先什么是池,深入学习过某些语言的同学应该知道,线程池,常量池,内存池等等

池:将资源统一集中的放入池中管理,从中取出,用后放回,这样就可以减少资源的开辟和浪费

那我们该怎么划分内存呢? 

  • 物理内存地址池

在我们的操作系统中有着内核进程与用户进程,所以根据此我们应该划分出两个池子,内核池与用户池。

  • 虚拟内存地址池

虚拟内存其实在分页中我们已经划分好,高1G为内核地址,低3G为用户地址,所以虚拟内存池我们就无需划分了。


 

池的存取

首先我们内存池存取内存也要按照单位来存取,自然而然就是4KB大小的内存块,当内存被耗尽时,就返回内存不足。


内存管理流程

首先不管是用户进程还是内核进程都会有中途申请内存的过程,那么一开始呢就先去虚拟内存池中查看是否有空闲的页可以分配,如果有的话,再去对应职责的物理内存池中查看是否有分配,当分配成功时,将虚拟内存的地址和物理内存的地址建立上练习即可。

实现内存管理代码

kernel/memory.h

#ifndef _KERNEL_MEMORY_H
#define _KERNEL_MEMORY_H
#include "stdint.h"
#include "bitmap.h"

/* 内存池职责标记 */
enum pool_flags {
	PF_KERNEL = 1,
	PF_USER = 2
};

/* 虚拟内存池 */
struct virtual_addr {
	struct bitmap vaddr_bitmap;
	uint32_t vaddr_start;		//管理的虚拟位置的起始
};

/*页的属性*/

#define PG_P_1 1
#define PG_P_0 0
#define PG_RW_R 0
#define PG_RW_W 2
#define PG_US_S 0
#define PG_US_U 4
extern struct pool kernel_pool, user_pool;
void mem_init(void);
void* get_kernel_pages(uint32_t pg_cnt);
#endif

kernel/memory.c

#include "memory.h"
#include "stdint.h"
#include "print.h"
#include "bitmap.h"
#include "debug.h"
#include "string.h"

#define PG_SIZE 4096	//页尺寸4096    
#define PDE_IDX(addr) ((addr&0xffc00000)>>22)    //页目录项索引
#define PTE_IDX(addr) ((addr&0x003ff000)>>12)    //页表项索引
/**
该项为位图地址,主程序的栈顶为0xc009f000,主程序的PCB为0xc009e000(实际上0xc009f000也是合理的),一个页框大小的位图可以表示128MB内存(4KB * 每位为8bit * 每位表达4KB)
本系统支持4个页框大小的位图,所以就是512MB,地址就为0xc009e00-4*0xc0001000
**/
#define MEM_BITMAP_BASE 0xc009a000

#define K_HEAP_START 0xc0100000	//内核的低1MB的虚拟地址是0xc0000000~~0xc00fffff,所以为了是地址紧凑,我们将堆地址放在0xc01000000

struct pool {
	struct bitmap pool_bitmap;
	uint32_t phy_addr_start;	//内存池的管理物理内存的起始地址
	uint32_t pool_size;		//内存池字节容量
};

//内核池与用户池
struct pool kernel_pool, user_pool;
struct virtual_addr kernel_vaddr;

/*
在pf表示的虚拟内存池中申请pg_cnt个虚拟页,
成功则返回虚拟页的起始地址,失败则返回NULL
*/
static void* vaddr_get(enum pool_flags pf, uint32_t pg_cnt) {
	int vaddr_start = 0, bit_idx_start = -1;
	uint32_t cnt = 0;
	//1,根据你的pflags决定获取哪个池子
	if (pf == PF_KERNEL) {
		bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);
		if (bit_idx_start == -1) {
			return NULL;
		}

		//根据页数去填充bitmap
		while (cnt < pg_cnt) {
			bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1);
		}

		//返回虚拟分配后的虚拟地址 注意bitmap中一个bit位代表一页 4KB
		vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
	}
	else {
		//用户池之后补充
	}
	return (void*)vaddr_start;
}

uint32_t* pte_ptr(uint32_t vaddr) {
	/*
		先访问到页表自己,页目录项pde作为pte的索引访问到页表+再用pte的索引作页内偏移,不理解的可以看7 Day虚拟地址访问页表
	*/
	uint32_t* pte = (uint32_t*)(0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr) << 2);
	return pte;
}

uint32_t* pde_ptr(uint32_t vaddr) {
	uint32_t* pde = (uint32_t*)(0xfffff000 + PDE_IDX(vaddr) * 4);
	return pde;
}

/*
	m_pool指向物理内存池中分配1个物理页成功返回页框的物理地址
*/
static void* palloc(struct pool* m_pool) {
	int bit_idx = bitmap_scan(&m_pool->pool_bitmap, 1);
	if (bit_idx == -1) {
		return NULL;
	}
	bitmap_set(&m_pool->pool_bitmap, bit_idx, 1);
	//获取物理池中的物理地址
	uint32_t page_phyaddr = m_pool->phy_addr_start + bit_idx * PG_SIZE;
	return (void*)page_phyaddr;
}

/*在页表中添加虚拟地址_vaddr与物理地址_page_phyaddr的映射*/
static void page_table_add(void* _vaddr, void* _page_phyaddr) {
	uint32_t vaddr = (uint32_t)_vaddr;
	uint32_t page_phyaddr = (uint32_t)_page_phyaddr;

	uint32_t* pde = pde_ptr(vaddr); //pde_ptr(vaddr)是得到的页目录项的地址,*pde就是页目录项里面的地址也就是,页表的地址
	uint32_t* pte = pte_ptr(vaddr); //pte_ptr(vaddr)得到的是页表的地址,*pte就是页表项的内容,也就是对应的物理地址

	/*判断页目录项p位,如果为1,则表示表已存在*/
	if (*pde & 0x00000001) {
		//判断页目录项是否存在
		ASSERT(!(*pte & 0x00000001));
		if (!(*pte & 0x00000001)) {
			*pte = page_phyaddr | PG_US_U | PG_RW_W | PG_P_1;
		}
	}
	else {
		//实际上是分配页的物理内存地址
		uint32_t pde_phy_addr = (uint32_t)palloc(&kernel_pool);
		*pde = pde_phy_addr | PG_US_U | PG_RW_W | PG_P_1;
		memset((void*)((int)pte & 0xfffff000),0,PG_SIZE);//因为新创了一个页目录项,也就是一个页表,一个页表大小为4KB,0xfffff定位到本目录页的某个目录项(通过分页查询找到页表地址),清空该页目录项的4KB物理内存地址
		ASSERT(!(*pte & 0x00000001));
		*pte = page_phyaddr | PG_US_U | PG_RW_W | PG_P_1;
	}
}

void* malloc_page(enum pool_flags pf, uint32_t pg_cnt)
{
	ASSERT(pg_cnt > 0 && pg_cnt < 3840);

	//1,获取虚拟内存的地址
	void* vaddr_start = vaddr_get(pf, pg_cnt);
	if (vaddr_start == NULL)	return NULL;

	uint32_t vaddr = (uint32_t)vaddr_start, cnt = pg_cnt;

	//2,根据flag确定内存池
	struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;

	//3,根据需要分配的页数来不断从内存池中分配内存
	while (cnt-- > 0)
	{	
		//4,获取池子中的物理地址
		void* page_phyaddr = palloc(mem_pool);
		if (page_phyaddr == NULL)	return NULL;
		//5,形成虚拟地址与物理地址的映射
		page_table_add((void*)vaddr, page_phyaddr);
		vaddr += PG_SIZE;
	}
	return vaddr_start;
}

//返回虚拟地址,同时将虚拟地址后的返回
void* get_kernel_pages(uint32_t pg_cnt)
{
	void* vaddr = malloc_page(PF_KERNEL, pg_cnt);
	if (vaddr != NULL)	memset(vaddr, 0, pg_cnt*PG_SIZE);
	return vaddr;
}

static void mem_pool_init(uint32_t all_mem) {
	uint32_t page_table_size = PG_SIZE * 256;       //页表占用的大小 769~1023页 + 第0页 + 第768页
	uint32_t used_mem = page_table_size + 0x100000; //低端1MB的内存 + 页表所占用的大小
	uint32_t free_mem = all_mem - used_mem;

	uint32_t all_free_pages = free_mem / PG_SIZE;   //空余的页数 = 总空余内存 / 一页的大小

	uint32_t kernel_free_pages = all_free_pages / 2; //内核 与 用户 各平分剩余内存
	uint32_t user_free_pages = all_free_pages - kernel_free_pages; //万一是奇数 就会少1 减去即可

	//kbm kernel_bitmap ubm user_bitmap
	uint32_t kbm_length = kernel_free_pages / 8;    //一位即可表示一页 8位一个数
	uint32_t ubm_length = user_free_pages / 8;


	//内核池和用户池映射的起始物理地址
	uint32_t kp_start = used_mem;
	uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE;

	kernel_pool.phy_addr_start = kp_start;
	user_pool.phy_addr_start = up_start;

	kernel_pool.pool_size = kernel_free_pages * PG_SIZE;
	user_pool.pool_size = user_free_pages * PG_SIZE;

	kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;
	user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);

	kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;
	user_pool.pool_bitmap.btmp_bytes_len = ubm_length;

	put_str("\n-------kernel_pool init start!-------\n");
	put_str("\nkernel_pool bitmap start addr:");
	put_int((int)kernel_pool.pool_bitmap.bits);
	put_str("\n");
	put_str("\nkernel_pool phy addr start:");
	put_int(kernel_pool.phy_addr_start);
	put_str("\n");
	bitmap_init(&kernel_pool.pool_bitmap);
	put_str("\n------- kernel_pool init end! -------\n");

	put_str("\n-------user_pool init start!-------\n");
	put_str("\nuser_pool bitmap start addr:");
	put_int((int)user_pool.pool_bitmap.bits);
	put_str("\n");
	put_str("\nuser_pool phy addr start:");
	put_int(user_pool.phy_addr_start);
	put_str("\n");
	bitmap_init(&user_pool.pool_bitmap);
	put_str("\n------- user_pool init end! -------\n");


	put_str("\n-------vitrual_kernel_pool init start!-------\n");
	kernel_vaddr.vaddr_bitmap.bits = (void*)MEM_BITMAP_BASE + kbm_length + ubm_length;
	kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length;
	kernel_vaddr.vaddr_start = K_HEAP_START;
	put_str("vitrual_kernel_pool vaddr_start:");
	put_int(kernel_vaddr.vaddr_start);
	put_str("\n");
	put_str("\n------- vitrual_kernel_pool init end! -------\n");

}

void mem_init() {
	put_str("\n******************mem_init start******************\n");
	uint32_t mem_bytes_total = *((uint32_t*)(0xb00)); //之前内存总量数据存放的地方
	mem_pool_init(mem_bytes_total);
	put_str("\n******************mem_init done******************\n");

}

这么大一串代码我相信大家肯定没看到懂,讲实话在一开始的时候我也理解困难,因为之前写的一些核心组件以及咱们整个系统内存分布已经忘记了,所以在此我来讲解一下一些关键点。


当前系统各个模块的内存 物理地址和虚拟地址

注:0xc00代表着第768个页目录项,对应的是高1G的内核态

  • Loader:0x900,0xc0000900
  • MBR:0x7c00,0xc0007c00
  • 内核文件缓冲区:0x70000,0xc0070000
  • 内核:0x1500,0xc0001500
  • 当前esp栈顶:0x9f000,0xc009f000
  • 显卡地址:0xb8000~0xbffff
  • 页目录地址:0x100000
  • 第一个页表地址:0x101000

 首先是0xc009a000这行地址是怎么来的呢?

我们要清楚在之前的loader里面我们将esp栈顶赋值为0x9f000对应的虚拟地址也就是0xc009f000,在之后我们要编写线程等等,需要存放PCB,首当其冲的就是main函数的PCB,每个PCB大概要占用4KB,且地址必须为0xXXXXX000~0xXXXXXfff

同时我们的位图可以管理512MB的内存,大家可以算一算一个页框可以管理128MB的内存,

(4KB*数组元素是8bit*一位代表4KB),那么512MB就是4KB

由此可得 0xc009f000 - 0xc0001000*5 = 0xc009a000


 关于分页中,页目录访问修改和页表访问修改我已经做介绍过,再次我就贴出截图给大家看看

 


五,开始编译 

上面的代码都完成后,就可以开始编译了,注意哈,要在init.c文件中添加上mem_init()函数进行初始化哦!

紧接着在main.c中添加下以下测试代码即可

 在这里我们不再使用脚本编译了,而是使用MakeFile,关于什么是MakeFile,我这里不多赘述,因为在这里不是核心点,我这里直接给出步骤大家照做即可。

1首先在自己的项目文件下新建一个MakeFile文件(不要在意为什么是windows,因为我懒得开linux了😋)

 2,其次编写MakeFile

BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB =  -m32 -I ./lib -m32 -I ./lib/kernel -m32 -I ./lib/usr -m32 -I ./kernel -m32 -I ./device 
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main 
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
	   $(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
	   $(BUILD_DIR)/debug.o $(BUILD_DIR)/string.o $(BUILD_DIR)/bitmap.o \
	   $(BUILD_DIR)/memory.o

########## C Code Compile Part #############
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
			lib/stdint.h kernel/init.h kernel/debug.h
			$(CC) $(CFLAGS) -o $@ $< 

$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
			lib/stdint.h kernel/interrupt.h device/timer.h thread/thread.h
			$(CC) $(CFLAGS) -o $@ $<

$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
			lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
			$(CC) $(CFLAGS) -o $@ $< 

$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/stdint.h\
			lib/kernel/io.h lib/kernel/print.h kernel/debug.h
			$(CC) $(CFLAGS) -o $@ $< 

$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
			lib/kernel/print.h lib/stdint.h kernel/interrupt.h lib/string.h
			$(CC) $(CFLAGS) -o $@ $<

$(BUILD_DIR)/string.o: lib/string.c lib/string.h\
			kernel/global.h kernel/debug.h
			$(CC) $(CFLAGS) -o $@ $<

$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h\
			kernel/global.h kernel/debug.h lib/stdint.h lib/string.c
			$(CC) $(CFLAGS) -o $@ $<

$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h\
			kernel/debug.h lib/stdint.h lib/kernel/bitmap.h lib/kernel/print.h
			$(CC) $(CFLAGS) -o $@ $<




######### ASM Code Compile Part #############
$(BUILD_DIR)/kernel.o: kernel/kernel.S
		$(AS) $(ASFLAGS) -o $@  $< 

$(BUILD_DIR)/print.o: lib/kernel/print.S
		$(AS) $(ASFLAGS) -o $@ $<

		
######### LD All Files ##############
$(BUILD_DIR)/kernel.bin: $(OBJS)
			$(LD) $(LDFLAGS) $^ -o $@ 


.PHONY :mk_dir os clean all 

mk_dir:
		if [[! -d $(BUILD_DIR) ]];then mkdir $(BUILD_DIR);fi 


######### 注意!注意!我这里的seek是6,你们之前的seek是多少要填写你们自己的,否则会出错 #######
os:
		dd if=$(BUILD_DIR)/kernel.bin \
			of=../geniusos.img bs=512 count=200 seek=6 conv=notrunc

clean:
		cd $(BUILD_DIR) && rm -f ./*

build: $(BUILD_DIR)/kernel.bin

all: mk_dir build os

3,在项目目录下建立一个build文件夹,从今往后这里就集中存访编译好的内核代码

4, 使用make all是编译所有文件,make clean是清理build文件中所有编译好的文件,注意使用make all的时候要在当前makefile所在的目录下!!!

紧接着你的bochs里面应该可以看到初始化成功的输出了,如果没有的话那就再去看看代码吧,然后我们在bochs中用info tab来验证我们main.c的分配三页内存页是否成功了

 可以看到0xc0100000~0xc0102fff,这里是3KB的大小正好对应三页说明我们成功啦,然后再看看我们bitmap,我们内核bitmap的地址在0xc009a000,我们用 x/10 0xc009a000来查看,可以发现第一个数组元素为7,也就是0111,说明我们bitmap也运作成功了!!

今天可算完成了一个大工程(好像也没多大),芜湖 快去休息一下吧,我也要去休息一下打瓦洛兰特了,明天我们来一起编写操作系统的线程,好好加油吧~。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值