本章之前实现的内存分配均是以页为单位即4KB大小的内存分配,无法实现精细的内存分配对内存空间浪费严重,因此本章对内存管理系统进行完善以提供精细化的内存分配。
一、精细化内存管理准备工作
精细化的内存分配依赖于arena、mem_block_desc、mem_block三种基础的数据结构。
1、arena:arena是内存分配的仓库,其中包含仓库的元信息以及数个规模一致的内存块。以该仓库本质上是一页内存,然后将该页内存开始12字节储存仓库元信息,后面的所有字节均用于等分成小内存块。
2、mem_block_desc:该结构是内存块描述符,这个描述符主要用于管理由多个同类arena产生的内存块,其结构为当前描述的内存块大小、一个arena中可容纳这样大小的内存块数量、以及该规模空闲的内存块链表。
3、mem_block:该结构用于将所有空闲内存块串联起来形成链表,该链表即是2中的链表
示意图如下:
三个基础数据结构如下(memory.c、memroy.h中):
struct arena {
struct mem_block_desc* desc;
uint32_t cnt;
bool large;
};
struct mem_block_desc {
uint32_t block_size;
uint32_t blocks_per_arena;
struct list free_list;
};
struct mem_block {
struct list_elem free_elem;
};
我们需要提前设置好内存块的种类,这里以2为底的指数方程来设计,准备了16、32、64、128、256、512、1024这七种大小的内存块。因为4KB-12字节后最多能容纳1个2048字节大小的内存块,所以单页能容纳的内存块规格截止到1024字节大小。大于1024则直接分配整页内存块,会在后面细说。
设置并初始化内存块描述符数组代码如下(memory.c、memory.h中):
#define DESC_CNT 7
struct mem_block_desc k_block_descs[DESC_CNT];
void block_desc_init(struct mem_block_desc* desc_array) {
uint16_t desc_idx, block_size = 16;
for(desc_idx = 0; desc_idx < DESC_CNT; desc_idx++) {
desc_array[desc_idx].block_size = block_size;
desc_array[desc_idx].blocks_per_arena = (PG_SIZE - sizeof(struct arena)) / block_size;
list_init(&desc_array[desc_idx].free_list);
block_size *= 2;
}
}
void mem_init() {
put_str("mem_init start\n");
uint32_t mem_bytes_total = (*(uint32_t*)(0xb00));
mem_pool_init(mem_bytes_total);
block_desc_init(k_block_descs); //此行新加,初始化内存块描述符用于精细化管理内存
put_str("mem_init done\n");
}
二、实现sys_malloc
前面已经将精细化内存管理所需要的数据结构准备好并初始化了,现在就可以开始实现真正进行小内存块分配的函数sys_malloc了。
内核线程内存块管理用k_block_descs描述符数组,每个用户进程的堆内存块管理也要单独使用一个描述符数组,因此为了试验用户进程的堆内存管理功能,需要在pcb中增加内存块描述符数组u_block_descs[DESC_CNT]。且该数组在使用前必须初始化,我们是在process.c中的函数process_execute中完成初始化工作的。
改动代码如下(pcb改动在process.h中,初始化内存块描述符数组改动在process.c中):
/* 进程或线程的pcb,程序控制块 */
struct task_struct {
uint32_t* self_kstack;
pid_t pid;
enum task_status status;
uint32_t priority;
char name[16];
uint32_t ticks;
uint32_t elapsed_ticks;
struct list_elem thread_ready_list_tag;
struct list_elem thread_all_list_tag;
/* 用户进程 */
uint32_t* pgdir;
struct virtual_addr userprog_vaddr;
struct mem_block_desc u_block_descs[DESC_CNT];
uint32_t stack_magic;
};
void process_execute(void* _filename, char* name) {
/*
* 创建用户进程主要步骤如下:
* 1、在内核中申请一页大小的内存作为pcb,将返回类型强转为struct task_struct*类型
* 2、初始化pcb相关字段(页表、用户进程虚拟内存池在后面单独初始化)
* 3、用户进程页表初始化
* 4、用户进程虚拟内存池初始化
* 5、此步与内核线程一样,初始化用户进程的内核栈(准备好相关参数供switch_to调用)
* 6、将用户进程加入等待队列(临界资源需要上锁)
* */
/* 初始化用户进程pcb相关参数 */
struct task_struct* pcb = get_kernel_pages(1);
init_thread_pcb(pcb, name, default_prio);
pcb->pgdir = create_page_dir();
create_user_vaddr_bitmap(pcb);
/* 初始化switch_to函数用到的内核栈 */
init_thread_stack(pcb, start_process, _filename);
/* 初始化内存块描述符 */
block_desc_init(pcb->u_block_descs);
/* 确保线程不在thread_ready_list和all_list中 */
/* 将线程加入thread_ready_list和all_list中 */
enum intr_status old_status = intr_disable();
ASSERT(!elem_find(&thread_ready_list, &pcb->thread_ready_list_tag));
list_append(&thread_ready_list, &pcb->thread_ready_list_tag);
ASSERT(!elem_find(&thread_all_list, &pcb->thread_all_list_tag));
list_append(&thread_all_list, &pcb->thread_all_list_tag);
intr_set_status(old_status);
}
接下来,我们正式开始实现memory.c中sys_malloc函数。
思路如下:
1、首先声明enum pool_flags PF、struct pool* mem_pool、uint32_t pool_size、struct mem_block_desc* descs以及struct task_struct* cur_thread
cur_thread用于判断是核心线程还是用户进程,PF用于判断使用核心内存池还是用户内存池,mem_pool用于分配内存时上锁,pool_size用于检查申请的内存大小是否超出内存池容量,descs用于匹配内存块规格及管理(放入、弹出)对应规格的内存块。
2、判断使用那个内存池,并对1的变量进行初始化
3、判断申请内存块的大小,超过1024字节直接分配页框(对应if)
4、未超过1024字节需要在各种规格的mem_block_desc中去进行适配,找到大于等于需求内存块大小并且离该大小最近的内存块,若该规格mem_block_desc的free_list中无可用的mem_block则申请一页内存作为arena来提供mem_block,需要对新分配的arena进行初始化并将arena拆分成内存块并添加到内存块描述符free_list中(对应else中的if)
5、从free_list中弹出一个mem_block返回给调用方(对应最后分配内存块420~426行)
代码如下:
static struct mem_block* arena2block(struct arena* a, uint32_t idx) {
return (struct mem_block*) ((uint32_t)a + sizeof(struct arena) + (a->desc->block_size)*idx);
}
static struct arena* block2arena(struct mem_block* b) {
return (struct arena*) ((uint32_t)b & 0xfffff000);
}
void* sys_malloc(uint32_t size) {
enum pool_flags pf;
uint32_t pg_cnt;
struct mem_block_desc* descs;
struct task_struct* cur_thread = running_thread();
struct pool* mem_pool;
uint32_t pool_size;
if(cur_thread->pgdir != NULL) {
/* 用户进程 */
pf = PF_USER;
descs = cur_thread->u_block_descs;
mem_pool = &user_pool;
pool_size = user_pool.pool_size;
} else {
/* 内核线程 */
pf = PF_KERNEL;
descs = k_block_descs;
mem_pool = &kernel_pool;
pool_size = kernel_pool.pool_size;
}
/* 申请的内存超出内存池容量范围返回NULL */
if(!(size > 0 && size < pool_size)) {
return NULL;
}
struct arena* a;
struct mem_block* b;
lock_acquire(&mem_pool->lock);
if(size > 1024) {
/* 超过1024直接给页框 */
uint32_t all_size = size + sizeof(struct arena);
pg_cnt = DIV_ROUND_UP(all_size, PG_SIZE);
a = malloc_page(pf, pg_cnt);
if(a == NULL) {
lock_release(&mem_pool->lock);
return NULL;
}
memset(a, 0, pg_cnt * PG_SIZE);
a->desc = NULL;
a->cnt = pg_cnt;
a->large = true;
lock_release(&mem_pool->lock);
return (void*)(a + 1);
} else {
/* 不超1024给内存块 */
/* 匹配合适的内存块 */
uint32_t desc_idx = 0;
for (desc_idx = 0; desc_idx < DESC_CNT; desc_idx++) {
if(descs[desc_idx].block_size >= size) {
break;
}
}
struct list* block_list = &descs[desc_idx].free_list;
if(list_empty(block_list)) {
pg_cnt = 1;
a = (struct arena*) malloc_page(pf, pg_cnt);
if(a == NULL) {
lock_release(&mem_pool->lock);
return NULL;
}
memset(a, 0, PG_SIZE);
a->desc = &descs[desc_idx];
a->cnt = descs[desc_idx].blocks_per_arena;
a->large = false;
enum intr_status old_status = intr_disable();
uint32_t block_idx;
for (block_idx = 0; block_idx < a->cnt; block_idx++) {
b = arena2block(a, block_idx);
ASSERT(!elem_find(&a->desc->free_list, &b->free_elem));
list_append(&a->desc->free_list, &b->free_elem);
}
intr_set_status(old_status);
}
/* 从desc中的free_list中弹出一个mem_block并返回 */
enum intr_status old_status = intr_disable();
b = elem2entry(struct mem_block, free_elem, list_pop(&(descs[desc_idx].free_list)));
memset(b, 0, descs[desc_idx].block_size);
intr_set_status(old_status);
console_put_str("mem_block address is 0x");
console_put_int((uint32_t) b);
console_put_char('\n');
a = block2arena(b);
a->cnt--;
lock_release(&mem_pool->lock);
return (void*) b;
}
}
改动main.c如下:
#include "print.h"
#include "init.h"
#include "debug.h"
#include "memory.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "process.h"
#include "syscall.h"
#include "syscall-init.h"
#include "stdio.h"
int prog_a_pid = 0;
int prog_b_pid = 0;
void k_thread_a(void* arg);
void k_thread_b(void* arg);
int main(void) {
put_str("I am kernel\n");
init_all();
intr_enable();
thread_start("k_thread_a", 31, k_thread_a, "I am thread_a ");
thread_start("k_thread_b", 31, k_thread_b, "I am thread_b ");
while (1);
return 0;
}
void k_thread_a(void* arg) {
char* para = (char*) arg;
void* addr = sys_malloc(33);
console_put_str("I am thread_a, sys_malloc(33), addr is 0x");
console_put_int((int)addr);
console_put_char('\n');
while (1);
}
void k_thread_b(void* arg) {
char* para = (char*) arg;
void* addr = sys_malloc(63);
console_put_str("I am thread_b, sys_malloc(63), addr is 0x");
console_put_int((int)addr);
console_put_char('\n');
while (1);
}
常规makefile后,运行结果如下:
本部分结束,准备进入下节内存释放!