实现内存的整页分配

1、位图和内存池:
位图:位图中的一位表示物理内存中的一页是否被分配,API见blog:位图API

内存池:建立内存池对位图进行操作,分配页内存;
在分页机制下有虚拟和物理两种地址,分别为了管理,需要创建虚拟内存地址池和物理内存地址池,内存池。

2、物理内存和虚拟地址池结构:
位图结构:

struct bitmap {
   uint32_t btmp_bytes_len;
   uint8_t* bits;//位图指针
};

物理地址池,包括表示容量大小的变量:

struct pool {
   struct bitmap pool_bitmap;	 // 本内存池用到的位图结构,用于管理物理内存
   uint32_t phy_addr_start;	 // 本内存池所管理物理内存的起始地址
   uint32_t pool_size;		 // 本内存池字节容量
};

虚拟地址池,地址空间都是4GB:

struct virtual_addr {
   struct bitmap vaddr_bitmap; // 虚拟地址用到的位图结构 
   uint32_t vaddr_start;       // 虚拟地址起始地址
};

3、初始化位图和内存池:

 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的倍数,
 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;


// 内核位图地址为0xc009a000
   kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;
							       
/* 用户内存池的位图紧跟在内核内存池位图之后 */
   user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);
   /* 将位图置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);

内核PCB地址,栈地址,位图地址:
目前低端1MB空闲地址为:0x7E00~0x9FBFF,因为PCB占用一页(4KB)大小,而栈一般位于PCB顶端,因此将栈地址定为0x9f000;PCB地址为0x9e000;位图因为有三个(内核物理,内核虚拟,用户物理),1页位图可管理128MB内存,规划每位图共管理512MB,因此位图占用空间为:0x9e000 - 0x4000 = 0x9a000;

实现内存整页分配

实现逻辑:
(1)先在内核虚拟内存池中寻找空闲页
(2)在物理内存池中寻找空闲页
(2)编辑页表,在虚拟内存和物理内存间建立映射
malloc_page ->
1、vaddr_get申请虚拟页
2、palloc申请物理页
3、page_table_add建立映射

malloc_page:

void* malloc_page(enum pool_flags pf, uint32_t pg_cnt) }{
 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;		 // 下一个虚拟页
}

一、vaddr_get:在虚拟内存池中申请pn_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;
}

二、palloc:在m_pool指向的内存池中分配一个物理页

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;
}

三、page_table_add:编辑页表,建立虚拟地址到物理地址的映射

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为0时,*pte只能出现在下面else语句块中的*pde后面。
 * *********************************************************/
   /* 先在页目录内判断目录项的P位,若为1,则表示该表已存在 */
   if (*pde & 0x00000001) {	 // 页目录项和页表项的第0位为P,此处判断目录项是否存在
      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("pte repeat");
	 *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);      // US=1,RW=1,P=1
      }
   } else {			    // 页目录项不存在,所以要先创建页目录再创建页表项.
      /* 页表中用到的页框一律从内核空间分配 */
      uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);

      *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);

      //将分配到的物理内存清0
      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
   }
}

如果没有PDE,则需要palloc一个物理页作为页表项,并将其物理地址填入PDE中

编辑页表的方法:需要获取进程页表PDE和PTE的指针,之前在建立页目录项时,在最后一项填入了页目录项的地址。

(1)获取PDE指针的方法:前20位填充1,后12位填充PDE的索引

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

(2)获取PTE指针的方法:前10位填充1,中间十位填充PDE索引,最后12位填充PTE索引

uint32_t* pte_ptr(uint32_t vaddr) {
uint32_t* pte = (uint32_t*)(0xffc00000 + 
	 ((vaddr & 0xffc00000) >> 10) + 
	 PTE_IDX(vaddr) * 4);
   return pte;

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为了动态分配二维数组的内存,有几种方法可以使用。一种方法是通过分配一块存放m个int *类型元素的内存,再分配一块存储m * n个元素数据的内存空间来实现,然后将首地址返回给** arr作为二维数组的索引。具体的实现代码如下所示: int** ArrMalloc2d(int ***arr, const int m, const int n) { *arr = (int **)malloc(m * sizeof(int *)); if(! *arr) { return ERROR; } **arr = (int *)malloc(m * n * sizeof(int)); if(! **arr) { return ERROR; } for (int i = 1; i < m; i++) { *(*arr + i) = *(*arr + i - 1) + n; if(! *(*arr + i)) { return ERROR; } } return *arr; } 这段代码会分配一维数组来存储指向二维数组每一行首元素的指针,然后分配一块连续的内存来存储二维数组的元素。通过这种方式,我们可以按照arr[i][j的方式访问二维数组的元素。为了释放内存,我们可以使用下面的代码: Status ArrFree2d(int ***arr, const int m) { free(*(*arr + 0)); *(*arr + 0) = NULL; if(*(*arr + 0)) { return ERROR; } free(*arr); *arr = NULL; if(*arr) { return ERROR; } return OK; } 这样就能动态地分配和释放二维数组的内存了。但需要注意的是,使用这种方式分配的二维数组,在进行整块内存操作时会遇到问题,例如对所有元素清零。因为使用memset进行清零操作会清除连续的m * n个内存单元,而实际的*(arr + i)并不是连续的。因此,在对整块内存进行操作时需要注意。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [二维数组及其动态内存分配](https://blog.csdn.net/weixin_43955214/article/details/104126662)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值