12 Day:内存管理

news2024/11/27 19:50:44

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


 

一,实现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也运作成功了!!

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/335912.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

< Linux >:Linux 进程概念 (4)

目录 五、孤儿进程 六、进程优先级 6.1、基本概念 6.2、查看时实系统进程 6.3、PRI and NI 七、其他概念 四、X 状态&#xff1a;死亡状态 所谓进程处于 X 状态(死亡状态)代表的就是该进程已经死亡了&#xff0c;即操作系统可以随时回收它的资源(操作系统也可以…

代码质量与安全 | 开发人员必备的安全编码实践指南

在任何新的软件开发项目开始时&#xff0c;您就应该考虑软件安全。开始一个新项目或许会令人望而生畏&#xff0c;因为有许多的决定要做&#xff0c;有许多想法必须考虑清楚。通常来说&#xff0c;这些决定和想法包括了定义项目需求、选择正确的流程、选择正确的工具以及确保软…

QML- 导入库包语法

QML- 导入库包语法一、概述二、Import语句的语法1. Module (namespace) 模块(命名空间)导入1. 非模块命名空间的导入2. 导入到限定局部命名空间2. 路径 import1. 本地目录导入2. 远程目录3. JavaScript资源导入三、QML导入路径四、调试一、概述 import 语句其实在QML文档里面体…

Springboot扩展点之SmartInstantiationAwareBeanPostProcessor

前言这是Springboot扩展点系列的第5篇了&#xff0c;主要介绍一下SmartInstantiationAwareBeanPostProcessor扩展点的功能特性、和实现方式。SmartInstantiationAwareBeanPostProcessor与其他扩展点最明显的不同&#xff0c;就是在实际的业务开发场景中应用到的机会并不多&…

机器学习框架sklearn之特征降维

目录特征降维概念特征选择过滤式①低方差特征过滤②相关系数③主成分分析特征降维 0维 标量 1维 向量 2维 矩阵 概念 降维是指在某些限定条件下&#xff0c;降低随机变量&#xff08;特征&#xff09;个数&#xff0c;得到一组“不相关”主变量的过程 注&#xff1a;正是…

微信小程序 java 医生预约挂号答疑问询系统

生预约答疑系统用户端是基于微信小程序端&#xff0c;医生和管理员是基于网页后端。本系统分为用户&#xff0c;管理员&#xff0c;医生三个角色&#xff0c;用户的主要功能是注册登陆小程序&#xff0c;查看新闻资讯&#xff0c;查看医生列表&#xff0c;预约医生&#xff0c;…

【unity细节】关于资源商店(Package Maneger)无法下载资源问题的解决

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 收录于专栏&#xff1a;unity细节和bug ⭐关于资源商店为何下载不了的问题⭐ 文章目录⭐关于资源商店为何下载不了的问题…

鸟哥的Linux私房菜读书笔记:文件系统的简单操作

磁盘与目录的容量 现在我们知道磁盘的整体数据实在superblock区块中,但是每个个别文件的容量则在inode当中记载的. 那在命令行下面该如何显示处这几个数据呢? df:列出文件系统的整体磁盘书用量du:评估文件系统的磁盘使用量(常用在推估目录所占容量)df先来说明一下范例一所输…

网络协议(四):网络互联模型、物理层、数据链路层

网络协议系列文章 网络协议(一)&#xff1a;基本概念、计算机之间的连接方式 网络协议(二)&#xff1a;MAC地址、IP地址、子网掩码、子网和超网 网络协议(三)&#xff1a;路由器原理及数据包传输过程 网络协议(四)&#xff1a;网络互联模型、物理层、数据链路层 目录一、网…

HER2靶向药物研发进展-销售数据-上市药品前景分析

HER2长期作为肿瘤领域的热门靶点之一&#xff0c;其原因是它在多部位、多种形式的癌症中均有异常的表达&#xff0c;据研究表明HER2除了在胃癌、胆道癌、胆管癌、乳腺癌、卵巢癌、结肠癌、膀胱癌、肺癌、子宫颈癌、子宫浆液性子宫内膜癌、头颈癌、食道癌中的异常表达还存在于多…

从0到0.1学习 maven(三:声明周期、插件、聚合与继承)

该文章为maven系列学习的第三篇&#xff0c;也是最后一篇 第一篇快速入口&#xff1a;从0到0.1学习 maven(一&#xff1a;概述及简单入门) 第二篇快速入口&#xff1a;从0到0.1学习 maven(二&#xff1a;坐标、依赖和仓库) 文章目录啥子叫生命周期生命周期详解clean生命周期def…

统计检验(一)// 方差分析

【应用案例】 检验不同组&#xff08;即不同收入者&#xff09;是否存在“品类满意度”显著差异。各组的满足度平均值如下&#xff1a; 【操作步骤】 方差分析的前提条件是各组总体方差没有显著差异。 第一步&#xff1a;方差同质性检验 原假设&#xff1a;没有差异。 结论…

接口测试入门,如何划分接口文档

1.首先最主要的就是要分析接口测试文档&#xff0c;每一个公司的测试文档都是不一样的。具体的就要根据自己公司的接口而定&#xff0c;里面缺少的内容自己需要与开发进行确认。 我认为一针对于测试而言的主要的接口测试文档应该包含的内容分为以下几个方面。 a.具体的一个业…

时间复杂度的计算(2023-02-10)

时间复杂度的计算 时间复杂度的计算分为三大类&#xff1a;一层循环、二层循环和多层循环。 一层循环 1.找出循环趟数t及每轮循环i的变化值 2.确立循环停止的条件 3.得出t与i之间的关系 4.联立两式&#xff0c;得出结果 eg: void fun(int n) {int i0;while (i*i*i<n)i;…

LeetCode刷题模版:292、295、297、299-301、303、304、309、310

目录 简介292. Nim 游戏295. 数据流的中位数297. 二叉树的序列化与反序列化【未理解】299. 猜数字游戏300. 最长递增子序列301. 删除无效的括号【未理解】303. 区域和检索 - 数组不可变304. 二维区域和检索 - 矩阵不可变309. 最佳买卖股票时机含冷冻期310. 最小高度树【未理解】…

测试开发,测试架构师为什么能拿50 60k呢需要掌握哪些技能呢

这篇文章是软件工程系列知识总结的第五篇&#xff0c;同样我会以自己的理解来阐述软件工程中关于架构设计相关的知识。相比于我们常见的研发架构师&#xff0c;测试架构师是近几年才出现的一个岗位&#xff0c;当然岗位title其实没有特殊的含义&#xff0c;在我看来测试架构师其…

产业互联网是对互联网的衍生和进化,也是一次重塑和再造

互联网并不仅仅只是充当撮合和中介的角色&#xff0c;它应当具备更多的功能和意义。只有这样&#xff0c;它的发展才能够真正全面和完善。产业互联网的衍生和出现&#xff0c;正是在互联网进化的基础之上出现的。这是我们看到之所以会有那么多的互联网玩家投身到产业互联网的浪…

FITC-PEG-FA,荧光素-聚乙二醇-叶酸,FA-PEG-FITC,实验室科研试剂,提供质量检测

FITC-PEG-FA&#xff0c;荧光素-聚乙二醇-叶酸 中文名称&#xff1a;荧光素-聚乙二醇-叶酸 英文名称&#xff1a;FITC-PEG-FA 英文别名&#xff1a;Fluorescein-PEG-Folic Acid 性状&#xff1a;基于不同的分子量&#xff0c;呈白色/类白色固体&#xff0c;或粘稠液体。 溶…

第九节 使用设备树实现RGB 灯驱动

通过上一小节的学习&#xff0c;我们已经能够编写简单的设备树节点&#xff0c;并且使用常用的of 函数从设备树中获取我们想要的节点资源。这一小节我们带领大家使用设备树编写一个简单的RGB 灯驱动程序&#xff0c;加深对设备树的理解。 实验说明 本节实验使用到STM32MP1 开…

使用gitlab ci/cd来发布一个.net 项目

gitlab runner的安装和基本使用:https://bear-coding.blog.csdn.net/article/details/120591711安装并给项目配置完gitlab runner后再操作后面步骤。实现目标&#xff1a;master分支代码有变更的时候自动构建build。当开发人员在gitlab上给项目打一个tag标签分支的时候自动触发…