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位的判断,看看是否有0while ((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后,便进行遍历查找0int 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,所以为了是地址紧凑,我们将堆地址放在0xc01000000struct 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;}//根据页数去填充bitmapwhile (cnt < pg_cnt) {bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1);}//返回虚拟分配后的虚拟地址 注意bitmap中一个bit位代表一页 4KBvaddr_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_bitmapuint32_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=notruncclean:cd $(BUILD_DIR) && rm -f ./*build: $(BUILD_DIR)/kernel.binall: 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也运作成功了!!
今天可算完成了一个大工程(好像也没多大),芜湖 快去休息一下吧,我也要去休息一下打瓦洛兰特了,明天我们来一起编写操作系统的线程,好好加油吧~。
相关文章:
12 Day:内存管理
前言:今天我们要完成我们操作系统的内存管理,以及一些数据结构和小组件的实现,在此之前大家需要了解我们前几天一些重要文件的内存地址存放在哪,以便我们更好的去编写内存管理模块 一,实现ASSERT断言 不知道大家有没有…...
linux基本功系列之lsof命令实战
文章目录前言一. lsof命令介绍二. 语法格式及常用选项三. 参考案例3.1 显示系统打开的文件3.2 查找某个文件相关的进程3.3 列出某个用户打开的文件信息3.4 列出某个程序进程所打开的文件信息3.5 查看某个进程号打开的文件3.6 列出所有的网络连接3.7 列出谁在使用某个端口3.8 恢…...
基础篇:02-SpringCloud概述
1.SpringCloud诞生 基于前面章节,我们深知微服务已成为当前开发的主流技术栈,但是如dubbo、zookeeper、nacos、rocketmq、rabbitmq、springboot、redis、es这般众多技术都只解决了一个或一类问题,微服务并没有一个统一的解决方案。开发人员或…...
【软件测试】软件测试工作上95%会遇到的问题,你遇到多少?
目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 1、测试负责人要进行…...
4.5.4 LinkedList
文章目录1.特点2.常用方法3.练习:LinkedList测试1.特点 链表,两端效率高,底层就是链表实现的 List接口的实现类,底层的数据结构为链表,内存空间是不连续的 元素有下标,有序允许存放重复的元素在数据量较大的情况下,查询慢&am…...
Python之FileNotFoundError: [Errno 2] No such file or directory问题处理
错误信息:FileNotFoundError: [Errno 2] No such file or directory: ../AutoFrame/temp/report.xlsx相对于当前文件夹的路径,其实就是你写的py文件所在的文件夹路径!python在对文件的操作时,需要特别注意文件地址的书写。文件的路…...
C语言中耳熟能详的printf与scanf
没有什么比时间更有说服力了,因为时间无需通知我们就可以改变一切了。---余华《活着》大家好,今天给大家分享的是C语言中的scanf与printf函数,一提起这两个函数,大家可能觉得这不就是打印和输入嘛?有什么可以说的&…...
【数据结构】复杂度讲解
目录 时间复杂度与空间复杂度:: 1.算法效率 2.时间复杂度 3.空间复杂度 4.常见时间复杂度以及复杂度OJ练习 时间复杂度与空间复杂度:: 什么是数据结构? 数据结构中是计算机存储,组织数据的方式,指相互之间存在一种或多种特定关…...
JAVA-线程池技术
目录 概念 什么是线程? 什么是线程池? 线程池出现背景 线程池原理图 JAVA提供线程池 线程池参数 如果本篇博客对您有一定的帮助,大家记得留言点赞收藏哦。 概念 什么是线程? 是操作系统能够进行运算调度的最小单位。&am…...
【C++】从0到1入门C++编程学习笔记 - 提高编程篇:STL常用算法(算术生成算法)
文章目录一、accumulate二、fill学习目标: 掌握常用的算术生成算法 注意: 算术生成算法属于小型算法,使用时包含的头文件为 #include <numeric> 算法简介: accumulate // 计算容器元素累计总和 fill // 向容器中添加元…...
【C++】static成员
💙作者:阿润菜菜 📖专栏:C 目录 概念 特性 出个题 概念 声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量; 用static修饰的成员函数,称之为静态…...
Python Scrapy 爬虫简单教程
1. Scrapy install 准备知识 pip 包管理Python 安装XpathCssWindows安装 Scrapy $>- pip install scrapy Linux安装 Scrapy $>- apt-get install python-scrapy 2. Scrapy 项目创建 在开始爬取之前,必须创建一个新的Scrapy项目。进入自定义的项目目录中&am…...
【DOCKER】容器概念基础
文章目录1.容器1.概念2.特点3.与虚拟机的对比2.docker1.概念2.命名空间3.核心概念3.命令1.镜像命令2.仓库命令1.容器 1.概念 1.不同的运行环境,底层架构是不同的,这就会导致测试环境运行好好的应用,到了生产环境就会出现bug(就像…...
第九层(16):STL终章——常用集合算法
文章目录前情回顾常用集合算法set_intersectionset_unionset_difference最后一座石碑倒下,爬塔结束一点废话🎉welcome🎉 ✒️博主介绍:一名大一的智能制造专业学生,在学习C/C的路上会越走越远,后面不定期更…...
一起学习用Verilog在FPGA上实现CNN----(六)SoftMax层设计
1 SoftMax层设计 1.1 softmax SoftMax函数的作用是输入归一化,计算各种类的概率,即计算0-9数字的概率,SoftMax层的原理图如图所示,输入和输出均为32位宽的10个分类,即32x10320 本项目softmax实现逻辑为: …...
pixhawk2.4.8-APM固件-MP地面站配置过程记录
目录一、硬件准备二、APM固件、MP地面站下载三、地面站配置1 刷固件2 机架选择3 加速度计校准4 指南针校准5 遥控器校准6 飞行模式7 紧急断电&无头模式8 基础参数设置9 电流计校准10 电调校准11 起飞前检查(每一项都非常重要)12 飞行经验四、遇到的问…...
【unity细节】关于资源商店(Package Maneger)无法下载资源问题的解决
👨💻个人主页:元宇宙-秩沅 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 本文由 秩沅 原创 收录于专栏:unity细节和bug ⭐关于资源商店为何下载不了的问题⭐ 文章目录⭐关于资源商店为何下载不了的问题…...
[Arxiv 2022] A Novel Plug-in Module for Fine-Grained Visual Classification
Contents MethodPlug-in ModuleLoss functionExperimentsReferencesMethod Plug-in Module Backbone:为了帮助模型抽取出不同尺度的特征,作者在 backbone 里加入了 FPNWeakly Supervised Selector:假设 backbone 的 i i...
RocketMQ Broker消息处理流程及部分源码解析
🍊 Java学习:Java从入门到精通总结 🍊 深入浅出RocketMQ设计思想:深入浅出RocketMQ设计思想 🍊 绝对不一样的职场干货:大厂最佳实践经验指南 📆 最近更新:2023年2月10日 &#x…...
Java面试题:Java集合框架
文章目录一、Java集合框架二、Java集合特性三、各集合类的使用ArrayListLinkedListHashSetHashSet源码解析对源码进行总结HashSet可同步HashSet的使用HashMap四、Iterator迭代器五、遍历集合元素的若干方式参考文章:Hash详解参考文章:深入浅出学Java——…...
时间之间的比较与计算相差年、月、日、小时、分钟、毫秒、纳秒以及判断闰年--LocalDateTime
如何把String/Date转成LocalDateTime参考String、Date与LocalDate、LocalTime、LocalDateTime之间互转 String、Date、LocalDateTime、Calendar与时间戳之间互相转化参考String、Date、LocalDateTime、Calendar与时间戳之间互相转化 比较方法介绍 isBefore(ChronoLocalDateT…...
PyTorch学习笔记:nn.L1Loss——L1损失
PyTorch学习笔记:nn.L1Loss——L1损失 torch.nn.L1Loss(size_averageNone, reduceNone, reductionmean)功能:创建一个绝对值误差损失函数,即L1损失: l(x,y)L{l1,…,lN}T,ln∣xn−yn∣l(x,y)L\{l_1,\dots,l_N\}^T,l_n|x_n-y_n| l(…...
Java程序设计-ssm企业财务管理系统设计与实现
摘要系统设计系统实现开发环境:摘要 对于企业集来说,财务管理的地位很重要。随着计算机和网络在企业中的广泛应用,企业发展速度在不断加快,在这种市场竞争冲击下企业财务管理系统必须优先发展,这样才能保证在竞争中处于优势地位。…...
疑难杂症篇(二十一)--Ubuntu18.04安装usb-cam过程出现的问题
对Ubuntu18.04{\rm Ubuntu 18.04}Ubuntu18.04环境下的ROS{\rm ROS}ROS的melodic{\rm melodic}melodic版本安装usb−cam{\rm usb-cam}usb−cam过程出现的两个常见问题提出解决方案。 1.问题1:usb-cam功能包编译时出现"未定义的引用"的问题 问题描述&#…...
npm-npm i XX --save 和--save-dev
之前使用npm i XX --save 和--save-dev 没太在意,就想记录一下,查到一篇比较全的(链接:NPM install -save 和 -save-dev 傻傻分不清),直接看好了,哈哈~ # 安装模块到项目目录下 npm install moduleName # -g 的意思是…...
可重构或可调谐微波滤波器技术
电子可重构,或者说电调微波滤波器由于其在改善现在及未来微波系统容量中不断提高的重要性而正吸引着人们越来越多的关注来对其进行研究和开发。例如,崭露头脚的超宽带(UWB)技术要求使用很宽的无线电频谱。然而,作为资源…...
医院智能化解决方案-门(急)诊、医技、智能化项目解决方案
【版权声明】本资料来源网络,知识分享,仅供个人学习,请勿商用。【侵删致歉】如有侵权请联系小编,将在收到信息后第一时间删除!完整资料领取见文末,部分资料内容:篇幅有限,无法完全展…...
判断元素是否在可视区域
前言 在日常开发中,我们经常需要判断目标元素是否在视窗之内或者和视窗的距离小于一个值(例如 100 px),从而实现一些常用的功能,例如: 图片的懒加载列表的无限滚动计算广告元素的曝光情况可点击链接的预加…...
告别传统繁杂的采购合同管理 打造企业自动化采购管理模式
随着企业竞争日趋激烈,采购成本压力剧增,企业对于采购合同管理更加严格,从而把控物资成本。对于任何一家企业采购来说,规范化合同的全面管理,是采购活动中重要的一个环节。 但在如今,依旧有很多企业采购合…...
【prism】路由事件映射到Command命令
在之前的一篇文章中,我介绍了普通的自定义事件: 【wpf】自定义事件总结(Action, EventHandler)_code bean的博客-CSDN博客_wpf action可以说通过Action和EventHandle,自定义事件是相当的方便简单了。https…...
做pc端网站适配/网站运营主要做什么工作
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!1. Kafka整体结构图Kafka名词解释…...
房山网站建设服务/seo优化常识
说明: 本文原创作者『Allen5G』 首发于微信公众号『Allen5G』,同时也更新在我的:CSDN,简书 标签:嵌入式软件,算法,通信 上拉电路 --- 应用于总线或者开漏模式,提供稳定电压 码字不…...
做外贸必须有公司网站么/全自动引流推广软件免费
2.Ransac是一种非常简单的算法 用于在一群样本中去掉噪声样本,得到有效的样本采用随机抽样验证的方法,以下节选自wikipedia,选有用的贴了过来 RANSAC RANSAC is an abbreviation for "RANdom SAmple Consensus". It is an algorith…...
沈阳网站建设21anshan/如何制作网页教程
背景框架是前端面试中的常客。尤其是 React 和 Vue。React 和 Vue 这两个极其优秀的前端类库,基本上占据了前端开发的半壁江山。如果把这两个神仙框架放在一起比较一下, 一定会发现一些比较有意思的知识点。掌握这些知识点, 并灵活运用&#…...
政府网站集约化建设的报告/上海网站建设联系方式
Web基础配置篇(十六): Kubernetes集群的安装使用一、概述Kubernetes 简称为K8S,是用于自动部署,扩展和管理容器化应用程序的开源系统。Kubernetes的目标是让部署容器化的应用简单并且高效(powerful),Kubern…...
建设什么网站/官网优化 报价
图论涉及的内容广泛,复杂,综合性较强。我在学习《算法竞赛进阶指南》图论部分后,为了方便日后复习让自己感觉学了东西,写下这篇大杂汇,内容主要源自我自己对《算法竞赛进阶指南》图论部分概括以及学习做题的经验。 一、…...