博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
操作系统真象还原实验记录之实验十三:内存管理系统
阅读量:2047 次
发布时间:2019-04-28

本文共 18170 字,大约阅读时间需要 60 分钟。

操作系统真象还原实验记录之实验十三:内存管理系统

对应书P374 8.3节

1.相关基础知识总结

1.1 内存池规划

通过分页实验已经很清楚,虚拟地址——实地址的映射规则依赖的是页表

在这里插入图片描述
对于所有进程和内核来说,都有自己独立的虚拟内存池,独立的页表。这就是为什么每个进程都有自己的独立的4GB,因为即使两个进程访问同一个虚拟地址,不同的页表会得到不同的物理地址映射。

2.实验代码

2.1 实现字符串操作函数 string.c

string.h

#ifndef __LIB_STRING_H#define __LIB_STRING_H#include "stdint.h"#define NULL ((void*)0)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* strchr(const char* string, const uint8_t ch);char* strrchr(const char* string, const uint8_t ch);char* strcat(char* dst_, const char* src_);uint32_t strchrs(const char* filename, uint8_t ch);#endif

2.1.1 针对size个字节的操作函数

以字节为单位就是以字符为单位,内存是一个字节对应一个内存地址编号的,

比如一个char a 或者一个uint8_t a。a均代表八位比特的数值(或ASCII码)。 *a代表这个ASCII码的内存地址。++(*a)代表a的下一个内存地址编号

#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 && src_ != NULL);   uint8_t* dst = dst_;   const uint8_t* src = src_;   while (size-- > 0)      *dst++ = *src++;}
/* 连续比较以地址a_和地址b_开头的size个字节,若相等则返回0,若a_大于b_返回+1,否则返回-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 *a > *b ? 1 : -1;       }      a++;      b++;   }   return 0;}

2.1.2 针对字符串函数的操作

/* 将字符串从src_复制到dst_ */char* strcpy(char* dst_, const char* src_) {   assert(dst_ != NULL && src_ != NULL);   char* r = dst_;		       // 用来返回目的字符串起始地址   while((*dst_++ = *src_++));   return r;}

和memcpy相比,*src='0’时,程序终止,因为src是字符串首地址。

所以针对的是strcpy字符串,memcpy针对的是size个字符。

/* 返回字符串长度 */uint32_t strlen(const char* str) {   assert(str != NULL);   const char* p = str;   while(*p++);   return (p - str - 1);}/* 比较两个字符串,若a_中的字符大于b_中的字符返回1,相等时返回0,否则返回-1. */int8_t strcmp (const char* a, const char* b) {   assert(a != NULL && b != NULL);   while (*a != 0 && *a == *b) {      a++;      b++;   }/* 如果*a小于*b就返回-1,否则就属于*a大于等于*b的情况。在后面的布尔表达式"*a > *b"中, * 若*a大于*b,表达式就等于1,否则就表达式不成立,也就是布尔值为0,恰恰表示*a等于*b */   return *a < *b ? -1 : *a > *b;}/* 从左到右查找字符串str中首次出现字符ch的地址(不是下标,是地址) */char* strchr(const char* str, const uint8_t ch) {   assert(str != NULL);   while (*str != 0) {      if (*str == ch) {	 return (char*)str;	    // 需要强制转化成和返回值类型一样,否则编译器会报const属性丢失,下同.      }      str++;   }   return NULL;}
/* 从后往前查找字符串str中首次出现字符ch的地址(不是下标,是地址) */char* strrchr(const char* str, const uint8_t ch) {   assert(str != NULL);   const char* last_char = NULL;   /* 从头到尾遍历一次,若存在ch字符,last_char总是该字符最后一次出现在串中的地址(不是下标,是地址)*/   while (*str != 0) {      if (*str == ch) {	 last_char = str;      }      str++;   }   return (char*)last_char;}

也就是最后一个ch字符的地址

/* 将字符串src_拼接到dst_后,将回拼接的串地址 */char* strcat(char* dst_, const char* src_) {   assert(dst_ != NULL && src_ != NULL);   char* str = dst_;   while (*str++);   --str;      // 别看错了,--str是独立的一句,并不是while的循环体   while((*str++ = *src_++));	 // 当*str被赋值为0时,此时表达式不成立,正好添加了字符串结尾的0.   return dst_;}/* 在字符串str中查找指定字符ch出现的次数 */uint32_t strchrs(const char* str, uint8_t ch) {   assert(str != NULL);   uint32_t ch_cnt = 0;   const char* p = str;   while(*p != 0) {      if (*p == ch) {	 ch_cnt++;      }      p++;   }   return ch_cnt;}

2.2 位图的实现

2.2.1 bitmap.h

#ifndef __LIB_KERNEL_BITMAP_H#define __LIB_KERNEL_BITMAP_H#include "global.h"#define BITMAP_MASK 1struct bitmap {   uint32_t btmp_bytes_len;/* 在遍历位图时,整体上以字节为单位,细节上是以位为单位,所以此处位图的指针必须是单字节 */   uint8_t* 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

btmp_bytes_len:位图比特数量

bits:位图首字节地址 bits[0]是第一个字节 位图内字节有效访问数是
0~ btmp_bytes_len-1,字节内有效访问位数是0~7

2.2.2 bitmap.c

#include "bitmap.h"#include "stdint.h"#include "string.h"#include "print.h"#include "interrupt.h"#include "debug.h"/* 将位图btmp初始化 */void bitmap_init(struct bitmap* btmp) {   memset(btmp->bits, 0, btmp->btmp_bytes_len);   }/* 判断bit_idx位是否为1,若为1则返回true,否则返回false */bool bitmap_scan_test(struct bitmap* btmp, uint32_t bit_idx) {   uint32_t byte_idx = bit_idx / 8;    // 向下取整用于索引数组下标   uint32_t bit_odd  = bit_idx % 8;    // 取余用于索引数组内的位   return (btmp->bits[byte_idx] & (BITMAP_MASK << bit_odd));}/* 在位图中申请连续cnt个位,返回其起始位下标 */int bitmap_scan(struct bitmap* btmp, uint32_t cnt) {   uint32_t idx_byte = 0;	 // 用于记录空闲位所在的字节/* 先逐字节比较,蛮力法 */   while (( 0xff == btmp->bits[idx_byte]) && (idx_byte < btmp->btmp_bytes_len)) {/* 1表示该位已分配,所以若为0xff,则表示该字节内已无空闲位,向下一字节继续找 */      idx_byte++;   }   ASSERT(idx_byte < btmp->btmp_bytes_len);   if (idx_byte == btmp->btmp_bytes_len) {  // 若该内存池找不到可用空间		      return -1;   } /* 若在位图数组范围内的某字节内找到了空闲位,  * 在该字节内逐位比对,返回空闲位的索引。*/   int idx_bit = 0; /* 和btmp->bits[idx_byte]这个字节逐位对比 */   while ((uint8_t)(BITMAP_MASK << idx_bit) & btmp->bits[idx_byte]) { 	 idx_bit++;   }	    int bit_idx_start = idx_byte * 8 + idx_bit;    // 空闲位在位图内的下标   if (cnt == 1) {      return bit_idx_start;   }   uint32_t bit_left = (btmp->btmp_bytes_len * 8 - bit_idx_start);   // 记录还有多少位可以判断   uint32_t next_bit = bit_idx_start + 1;   uint32_t count = 1;	      // 用于记录找到的空闲位的个数   bit_idx_start = -1;	      // 先将其置为-1,若找不到连续的位就直接返回   while (bit_left-- > 0) {      if (!(bitmap_scan_test(btmp, next_bit))) {	 // 若next_bit为0	 count++;      } else {	 count = 0;      }      if (count == cnt) {	    // 若找到连续的cnt个空位	 bit_idx_start = next_bit - cnt + 1;	 break;      }      next_bit++;             }   return bit_idx_start;}/* 将位图btmp的bit_idx位设置为value */void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, int8_t value) {   ASSERT((value == 0) || (value == 1));   uint32_t byte_idx = bit_idx / 8;    // 向下取整用于索引数组下标   uint32_t bit_odd  = bit_idx % 8;    // 取余用于索引数组内的位/* 一般都会用个0x1这样的数对字节中的位操作, * 将1任意移动后再取反,或者先取反再移位,可用来对位置0操作。*/   if (value) {		      // 如果value为1      btmp->bits[byte_idx] |= (BITMAP_MASK << bit_odd);   } else {		      // 若为0      btmp->bits[byte_idx] &= ~(BITMAP_MASK << bit_odd);   }}

bitmap_init : 从位图首字节开始,一个字节一个字节全部置0

bitmap_scan_test:获取bit_idx位在位图中是第几个字节以及该字节的第几位,然后返回和1相与的值。

bitmap_scan:先位图从第一个字节开始后依次和0xff比较,直到不等为止,说明该字节含有空闲位;然后在该字节内每位依次进行和1与的操作,直到找到第一个空位,用idx_bit记录其在字节内的偏移;最后,从该位开始,依次遍历位图所有位,直到找到cnt个连续空闲位。找到返回位下标,找不到返回-1

bitmap_set:value为1或运算,value为0取反后再与

2.3 实现虚拟内存池

2.1 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	     // 用户内存池};#define	 PG_P_1	  1	// 页表项或页目录项存在属性位#define	 PG_P_0	  0	// 页表项或页目录项存在属性位#define	 PG_RW_R  0	// R/W 属性位值, 读/执行#define	 PG_RW_W  2	// R/W 属性位值, 读/写/执行#define	 PG_US_S  0	// U/S 属性位值, 系统级#define	 PG_US_U  4	// U/S 属性位值, 用户级/* 用于虚拟地址管理 */struct virtual_addr {/* 虚拟地址用到的位图结构,用于记录哪些虚拟地址被占用了。以页为单位。*/  struct bitmap vaddr_bitmap;/* 管理的虚拟地址 */  uint32_t vaddr_start;};extern struct pool kernel_pool, user_pool;void mem_init(void);void* get_kernel_pages(uint32_t pg_cnt);void* malloc_page(enum pool_flags pf, uint32_t pg_cnt);void malloc_init(void);uint32_t* pte_ptr(uint32_t vaddr);uint32_t* pde_ptr(uint32_t vaddr);#endif

vaddr_bitmap:虚拟地址位图结构

vaddr_start:虚拟地址起始地址

global.h 增加一点定义

#define NULL ((void*)0)#define bool int#define true 1#define false 0
2.2 memory.c
#include "memory.h"#include "bitmap.h"#include "stdint.h"#include "global.h"#include "debug.h"#include "print.h"#include "string.h"#include "interrupt.h"#define PG_SIZE 4096/***************  位图地址 ********************* 因为0xc009f000是内核主线程栈顶,0xc009e000是内核主线程的pcb.* 一个页框大小的位图可表示128M内存, 位图位置安排在地址0xc009a000,* 这样本系统最大支持4个页框的位图,即512M内存 */#define MEM_BITMAP_BASE 0xc009a000/*************************************/#define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)#define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)/* 0xc0000000是内核从虚拟地址3G起. 0x100000意指跨过低端1M内存,使虚拟地址在逻辑上连续 */#define K_HEAP_START 0xc0100000/* 内存池结构,生成两个实例用于管理内核内存池和用户内存池 */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;  if (pf == PF_KERNEL) {     // 内核内存池     bit_idx_start  = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);     if (bit_idx_start == -1) {    return NULL;     }     while(cnt < pg_cnt) {    bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1);     }     vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;  } else {	     // 用户内存池	     //将来实现用户进程再补充    	 }    return (void*)vaddr_start;}/* 得到虚拟地址vaddr对应的pte指针*/uint32_t* pte_ptr(uint32_t vaddr) {  /* 先访问到页表自己 + \   * 再用页目录项pde(页目录内页表的索引)做为pte的索引访问到页表 + \   * 再用pte的索引做为页内偏移*/  uint32_t* pte = (uint32_t*)(0xffc00000 + \    ((vaddr & 0xffc00000) >> 10) + \    PTE_IDX(vaddr) * 4);  return pte;}/* 得到虚拟地址vaddr对应的pde的指针 */uint32_t* pde_ptr(uint32_t vaddr) {  /* 0xfffff是用来访问到页表本身所在的地址 */  uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr) * 4);  return pde;}/* 在m_pool指向的物理内存池中分配1个物理页,* 成功则返回页框的物理地址,失败则返回NULL */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);	// 将此位bit_idx置1  uint32_t page_phyaddr = ((bit_idx * PG_SIZE) + m_pool->phy_addr_start);  return (void*)page_phyaddr;}/* 页表中添加虚拟地址_vaddr与物理地址_page_phyaddr的映射 */static void page_table_add(void* _vaddr, void* _page_phyaddr) {  uint32_t vaddr = (uint32_t)_vaddr, page_phyaddr = (uint32_t)_page_phyaddr;  uint32_t* pde = pde_ptr(vaddr);  uint32_t* pte = pte_ptr(vaddr);/************************   注意   ************************** 执行*pte,会访问到pde。所以确保pde创建完成后才能执行*pte,* 否则会引发page_fault。因此在pde未创建时,* *pte只能出现在下面最外层else语句块中的*pde后面。* *********************************************************/  /* 先在页目录内判断目录项的P位,若为1,则表示该表已存在 */  if (*pde & 0x00000001) {     ASSERT(!(*pte & 0x00000001));     if (!(*pte & 0x00000001)) {   // 只要是创建页表,pte就应该不存在,多判断一下放心    *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);    // US=1,RW=1,P=1     } else {	  // 调试模式下不会执行到此,上面的ASSERT会先执行.关闭调试时下面的PANIC会起作用    PANIC("pte repeat");     }  } else {	   // 页目录项不存在,所以要先创建页目录项再创建页表项.     /* 页表中用到的页框一律从内核空间分配 */     uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);     *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);/*******************   必须将页表所在的页清0   ********************** 必须把分配到的物理页地址pde_phyaddr对应的物理内存清0,* 避免里面的陈旧数据变成了页表中的页表项,从而让页表混乱.* pte的高20位会映射到pde所指向的页表的物理起始地址.*/     memset((void*)((int)pte & 0xfffff000), 0, PG_SIZE); /************************************************************/     ASSERT(!(*pte & 0x00000001));     *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);      // US=1,RW=1,P=1  }}/* 分配pg_cnt个页空间,成功则返回起始虚拟地址,失败时返回NULL */void* malloc_page(enum pool_flags pf, uint32_t pg_cnt) {  ASSERT(pg_cnt > 0 && pg_cnt < 3840);/***********   malloc_page的原理是三个动作的合成:   ***********     1通过vaddr_get在虚拟内存池中申请虚拟地址     2通过palloc在物理内存池中申请物理页     3通过page_table_add将以上两步得到的虚拟地址和物理地址在页表中完成映射***************************************************************/  void* vaddr_start = vaddr_get(pf, pg_cnt);  if (vaddr_start == NULL) {     return NULL;  }  uint32_t vaddr = (uint32_t)vaddr_start, cnt = pg_cnt;  struct pool* mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;/* 因为虚拟地址是连续的,但物理地址可以是不连续的,所以逐个做映射*/  while (cnt-- > 0) {     void* page_phyaddr = palloc(mem_pool);/* 失败时要将曾经已申请的虚拟地址和物理页全部回滚,* 在将来完成内存回收时再补充 */     if (page_phyaddr == NULL) {      return NULL;     }     page_table_add((void*)vaddr, page_phyaddr); // 在页表中做映射      vaddr += PG_SIZE;		 // 下一个虚拟页  }  return vaddr_start;}/* 从内核物理内存池中申请pg_cnt页内存,* 成功则返回其虚拟地址,失败则返回NULL */void* get_kernel_pages(uint32_t pg_cnt) {  void* vaddr =  malloc_page(PF_KERNEL, pg_cnt);  if (vaddr != NULL) {	   // 若分配的地址不为空,将页框清0后返回     memset(vaddr, 0, pg_cnt * PG_SIZE);  }  return vaddr;}/* 初始化内存池 */static void mem_pool_init(uint32_t all_mem) {  put_str("   mem_pool_init start\n");  uint32_t page_table_size = PG_SIZE * 256;	  // 页表大小= 1页的页目录表+第0和第768个页目录项指向同一个页表+                                                 // 第769~1022个页目录项共指向254个页表,共256个页框  uint32_t used_mem = page_table_size + 0x100000;	  // 0x100000为低端1M内存  uint32_t free_mem = all_mem - used_mem;  uint16_t all_free_pages = free_mem / PG_SIZE;		  // 1页为4k,不管总内存是不是4k的倍数,   						  // 对于以页为单位的内存分配策略,不足1页的内存不用考虑了。  uint16_t kernel_free_pages = all_free_pages / 2;  uint16_t user_free_pages = all_free_pages - kernel_free_pages;/* 为简化位图操作,余数不处理,坏处是这样做会丢内存。好处是不用做内存的越界检查,因为位图表示的内存少于实际物理内存*/  uint32_t kbm_length = kernel_free_pages / 8;			  // Kernel BitMap的长度,位图中的一位表示一页,以字节为单位  uint32_t ubm_length = user_free_pages / 8;			  // User BitMap的长度.  uint32_t kp_start = used_mem;				  // Kernel Pool start,内核内存池的起始地址  uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE;	  // User Pool start,用户内存池的起始地址  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.btmp_bytes_len = kbm_length;  user_pool.pool_bitmap.btmp_bytes_len	  = ubm_length;/*********    内核内存池和用户内存池位图   ************   位图是全局的数据,长度不固定。*   全局或静态的数组需要在编译时知道其长度,*   而我们需要根据总内存大小算出需要多少字节。*   所以改为指定一块内存来生成位图.*   ************************************************/// 内核使用的最高地址是0xc009f000,这是主线程的栈地址.(内核的大小预计为70K左右)// 32M内存占用的位图是2k.内核内存池的位图先定在MEM_BITMAP_BASE(0xc009a000)处.  kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;   						       /* 用户内存池的位图紧跟在内核内存池位图之后 */  user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);  /******************** 输出内存池信息 **********************/  put_str("      kernel_pool_bitmap_start:");put_int((int)kernel_pool.pool_bitmap.bits);  put_str(" kernel_pool_phy_addr_start:");put_int(kernel_pool.phy_addr_start);  put_str("\n");  put_str("      user_pool_bitmap_start:");put_int((int)user_pool.pool_bitmap.bits);  put_str(" user_pool_phy_addr_start:");put_int(user_pool.phy_addr_start);  put_str("\n");  /* 将位图置0*/  bitmap_init(&kernel_pool.pool_bitmap);  bitmap_init(&user_pool.pool_bitmap);  /* 下面初始化内核虚拟地址的位图,按实际物理内存大小生成数组。*/  kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length;      // 用于维护内核堆的虚拟地址,所以要和内核内存池大小一致 /* 位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外*/  kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length);  kernel_vaddr.vaddr_start = K_HEAP_START;  bitmap_init(&kernel_vaddr.vaddr_bitmap);  put_str("   mem_pool_init done\n");}/* 内存管理部分初始化入口 */void mem_init() {  put_str("mem_init start\n");  uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));  mem_pool_init(mem_bytes_total);	  // 初始化内存池  put_str("mem_init done\n");}

各个函数总结:

mem_init:调用mem_pool_init 传入参数为内存容量

mem_pool_init:总结这个函数前,先看这2个结构体

struct virtual_addr {/* 虚拟地址用到的位图结构,用于记录哪些虚拟地址被占用了。以页为单位。*/   struct bitmap vaddr_bitmap;/* 管理的虚拟地址 */   uint32_t vaddr_start;   };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;	 // 此结构是用来给内核分配虚拟地址

memory.c申请了两个pool结构体 kernel_pool, user_pool用作内核内存池和用户内存池

还申请了 kernel_vaddr用作虚拟内存池。

mem_pool_init这个函数就是在获得参数内存总量的情况下,完成了对这3个已申请的结构体的成员变量的填写,从而确定了内存池的地址以及他们位图的地址,建立了内存管理系统

其中3个内存池的位图地址均在实地址1MB以下。内核内存池首地址在2MB处,虚拟内存池虚拟地址被赋值为K_HEAP_START即0xc0100000。
这个函数执行完毕后,整个内存结构将呈现如下图,

在这里插入图片描述

位图均位于1MB以下,内存池均位于2MB以上

上面的函数用于建立位图结构和内存池

下面的函数用于分配内存
vaddr_get 在虚拟内存池申请pg_cnt个虚拟页,返回虚拟首地址,虚拟内存池大小与内核内存池大小一致。

pte_ptr:获得参数虚拟地址对应的页表项的物理地址对应的虚拟地址

pde_ptr:获得参数虚拟地址对应的页目录项的物理地址对应的虚拟地址
palloc:向参数指向的内存池申请一个物理页,返回物理地址

page_table_add:分配内存的核心函数。

利用上述3个函数,完成页表中虚拟地址与物理地址完成映射。这个函数的执行有两种情况,第一种:虚拟地址对应的页目录项存在,直接进入页表,将物理地址填入虚拟地址对应的页表项;第二种:虚拟地址对应的页目录项不存在,先调用palloc获得一页物理页的物理地址,然后将此物理地址填入虚拟地址对应的页目录项,也就是将此页作为一个页表,再进入页表,将参数物理地址写入虚拟地址对应页表项。
注意,有了页目录表,页表可以不连续,分散成多个物理页。

malloc_page:

1通过vaddr_get在虚拟内存池中申请虚拟地址
2通过palloc在物理内存池中申请物理页
3通过page_table_add将以上两步得到的虚拟地址和物理地址在页表中完成映射
申请的pg_cnt页虚拟内存是连续的,分配的物理地址不一定连续,成功返回pg_cnt页虚拟内存首虚拟地址。失败返回null

get_kernel_pages 调用malloc_page,返回不为null,则将分配的物理页清0

main.c

#include "print.h"#include "init.h"#include "memory.h"int main(void) {   put_str("I am kernel\n");   init_all();      void* addr = get_kernel_pages(3);   put_str("\n get_kernel_page start vaddr is");   put_int((uint32_t)addr);   put_str("\n");      while(1);   return 0;   }

申请3页,也就是完成了3个虚拟页首地址到物理地址映射

init.c 增加一句代码

mem_init();	     // 初始化内存管理系统

makefile增加的代码

$(BUILD_DIR)/string.o: lib/string.c lib/string.h lib/stdint.h kernel/global.h \	lib/stdint.h kernel/debug.h	$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \    	kernel/global.h lib/stdint.h lib/string.h lib/stdint.h \     	lib/kernel/print.h kernel/interrupt.h kernel/debug.h	$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h lib/stdint.h lib/kernel/bitmap.h \   	kernel/global.h kernel/global.h kernel/debug.h lib/kernel/print.h \	lib/kernel/io.h kernel/interrupt.h lib/string.h lib/stdint.h	$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \        lib/stdint.h kernel/init.h kernel/memory.h 	$(CC) $(CFLAGS) $< -o $@

生成了string.o bitmap.o memory.o main.o要加个memory.h

OBJS 也要增加3个新增的链接目标

实验结果

编译链接

make all

运行

./bochs -f bochsrc.disk

在这里插入图片描述

0xC0100000就是我们mem_init里设定的虚拟内存池虚拟地址,结果符合代码运行

在这里插入图片描述

3页虚拟地址向物理地址的映射
完成3页虚拟地址和物理地址的映射,只需要记录3个虚拟页首地址在页表的映射即可,因为虚拟地址后12位是偏移量。

最后再注意一个点:

在上次分页实验中我们已经完成了3GB~ 3GB+1MB 与 0~1MB的映射
和虚拟地址0~ 1MB 到实地址 0~1MB的映射。

因此这次试验虚拟内存池的首地址是3GB+1MB往上的地址,3GB~ 3GB+1MB和0~1MB这两块虚拟地址默认为永远不空闲,永远不可被分配。

另外内核内存池和用户内存池的起始地址是2MB往上到32MB,所以0~1MB的内核程序包括1MB ~2MB的页表也是永远不空闲,无法被分配。

当虚拟内存池首地址为3GB+1MB时,位图初始化应该全部置0

同理,物理内存池位图也应该全部置0.

转载地址:http://dyqof.baihongyu.com/

你可能感兴趣的文章
Java8学习笔记(三)—— Optional类的使用
查看>>
Java8学习笔记(四) —— Stream流式编程
查看>>
Java8学习笔记(五)—— 方法引用(::双冒号操作符)
查看>>
数据结构与算法(四)—— 栈与队列
查看>>
数据结构与算法(五)—— 广义表
查看>>
微服务简介
查看>>
CAP定理
查看>>
Docker初探
查看>>
Docker镜像常用命令
查看>>
使用Dockerfile定制镜像
查看>>
Docker容器数据持久化
查看>>
Docker Compose
查看>>
GitLab克隆项目出现 “git未能顺利结束(退出码128)”问题的解决
查看>>
SpringBoot整合FastDFS(附源码)
查看>>
在RoboWare Studio下利用python语言实现话题
查看>>
科学计算库——NumPy库
查看>>
数据分析处理库——Pandas
查看>>
Ubuntu 18.04 swap分区扩展
查看>>
Sophus的编译与使用
查看>>
Python中切片的用法
查看>>