内存管理初步
物理内存
认为的先规划成两部分,用户的物理内存和内核的物理内存,专项专用,内核有方法去占用用户的内存,但是规定两部分内存是专用的,内核只使用自己的物理内存。
物理内存的内核、用户部分分别用 bitmap 来统计管理。
虚拟内存
首先,虚拟内存也是需要管理的,毕竟程序要申请堆上的内存,假定我们只支持512M物理内存,那么我们只要用 4* 4K 就能 记录这512M。
备注: 把位图的地址放在这个地方是人为选择,也可以选其他地方
同样,我们把 内核堆 开始地址 放到 0xc0100000,当然你也可以放到更远。另外,注意到我们的页表就在物理地址1M以上,我们需要把页表的内存算作已经使用的内存。
/ 0xc0000000是内核从虚拟地址3G起. 0x100000意指跨过低端1M内存,使虚拟地址在逻辑上连续 /
#define K_HEAP_START 0xc0100000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| /* 初始化内存池 */ 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); /* 将位图置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); }
|
分配物理内存
malloc分配了虚拟地址,也会为该虚拟地址物理内存。linux的实现上,分配物理地址不是实时的,这里先假设是实时的,并且每次分配是按照页为单位。 关键的一点,分配的物理内存和分配的虚拟内存 要通过 页表 关联上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| /* 分配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; }
|