Contents

手写OS系列之完善堆内存管理

手写OS系列之完善堆内存管理

1.实现动态分配内存

a.相关数据结构

​ 引入新名词arena(舞台):将一大块内存划分成多个小内存,每个小内存之间互不干涉,可以分别管理,这些众多的小内存块就叫做arena,可以理解为内存仓库。arena占一个物理页框,包含元数据和arena中的内存池,内存池中以内存块为单位进行划分内存。

​ arena元数据数据结构:

  • desc:内存块描述符指针可以间接获取内存块信息。
  • cnt:代表内存块数量或页框数
  • large:用于判断申请的内存是否是大内存
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//memory repository
struct arena {
	struct mem_block_desc* desc;
	
	// if large =true , cnt is the number of page 
	// if large =false, cnt is the number of memory block
	uint32_t cnt;					

	bool large;
};

​ 内存块mem_block数据结构:

只有一个free_elem成员,用来配合内存块描述符中的链表使用。

1
2
3
4
//memory block
struct mem_block{
	struct list_elem free_elem;
};

​ 当不断地内存分配之后,将会产生大量的arena,为了更好地管理arena集群,引入内存块描述符(mem_block_desc)数据结构。用于管理同种规格的arena集群,mem_block_desc只包含两个属性,一个是内存块规模,一个是包含所有空闲内存块的链表。

​ mem_block_desc 数据结构:

  • block_size:内存块规模
  • block_per_arena:每个arena中的内存块容量
  • free_list:空闲内存块链表
1
2
3
4
5
6
//memory block descriptor
struct mem_block_desc{
	uint32_t block_size;				//size of memory block
	uint32_t block_per_arena;			//the number of memory block in per arena
	struct list free_list;
};
b.初始化

​ 针对不同的内存申请,我们有不同的处理策略,当申请的内存小于1024字节时,我们使用arena中的内存块,当申请内存大于1024字节时,我们将不会再拆分大内存,而是直接把整块内存分配出去,代码易维护。

​ (为什么是1024字节?因为arena占一页,元数据会占用内存,所以内存池必定小于4kb,内存块是平均分配的,所以最大的内存块肯定是小于2kb的,我们又是以2的指数来划分,所以最大为1024字节,这里我们仅支持7种规格内存块,16,32,64,128,256,512,1024。)

​ kernel/memory.c:初始化内核7种规格内存块描述符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct mem_block_desc k_block_descs[DESC_CNT];

//prepare for malloc :initial the memory block descriptor  
void block_desc_init(struct mem_block_desc* desc_array){
	uint16_t index ,size = 16;
	for (index = 0 ; index < DESC_CNT ; index ++ ){
		desc_array[index].block_size = size;

		//initial the number of memory block in arena
		desc_array[index].block_per_arena = (PG_SIZE - sizeof(struct arena)) / size;

		list_init(&desc_array[index].free_list);

		size *= 2;
	}
}

void mem_init(){
	//...
	block_desc_init(k_block_descs);
	//...
}

​ 初始化用户进程7种规格内存块描述符:

thread/thread.h:添加用户进程的内存块描述符,因为用户进程有自己专属的虚拟内存空间,所以每个进程有自己的内存块描述符,用来管理自己的虚拟内存。

1
2
3
4
5
6
7
8
//the PCB 
struct task_struct {
	//...
						
	struct mem_block_desc u_block_desc[DESC_CNT]; //memory block descriptor of user process

	//...
};

userprog/process.c:创建用户进程时,进行初始化

1
2
3
4
5
6
7
// create user process
void process_execute(void* filename , char* name) {
	//...
	block_desc_init(thread->u_block_desc);
	//...
}
	
c.内存操作

​ 相关操作函数:

  • arena2block:获取对应arena目标索引处的内存块
  • block2arena:获取内存块对应的arena
  • sys_malloc:在堆中申请size字节内存。先判断用哪个内存池,用户还是内核,然后根据申请的内存大小,分配页框还是内存块。如果>1024字节,直接向上取整分配页框;如果<1024字节,就分配适配的最小内存块,从内存块描述符中的free_list中直接获取内存块。

值得注意的是, 当描述符的链表中没有空闲的内存块了,才创建arena,且一个arena的所有内存块都是空闲的,将会回收该arena占用的页框。所以在分配内存块之前,还需要判断,是否有空闲的内存块,没有就创建arena,并将arena中的内存池一块一块地添加进描述符的free_list

  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
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
//return the address of 'index' in the arena
static struct mem_block* arena2block(struct arena* a , uint32_t index){
	return (struct mem_block*)((uint32_t)a + sizeof(struct arena) + index * a->desc->block_size);
}

//return the address of which arena 'b' at
static struct arena* block2arena(struct mem_block* b){
	return (struct arena*)((uint32_t)b & 0xfffff000);
}

//malloc 'size' memory from heap
void* sys_malloc(uint32_t size){
	enum pool_flags PF;
	struct pool* mem_pool;
	uint32_t pool_size;
	struct mem_block_desc* descs;
	struct task_struct* cur_thread = running_thread();

	//judge use which memory pool
	if (cur_thread->pgdir == NULL ){				//kernel memory pool
		PF = PF_KERNEL;
		pool_size = kernel_pool.pool_size;
		mem_pool = &kernel_pool;
		descs = k_block_descs;
	}else {								//user process memory pool
		PF = PF_USER;
		pool_size = user_pool.pool_size;
		mem_pool = &user_pool;
		descs = cur_thread->u_block_desc; 
	}

	//judge if there is enough capacity
	if (!(size > 0 && size < pool_size)){
		return NULL;
	}
	struct arena* a;
	struct mem_block* b;
	lock_acquire(&mem_pool->lock);
	
	//begin malloc
	if (size > 1024){
		uint32_t page_cnt = DIV_ROUND_UP(size + sizeof(struct arena) , PG_SIZE);

		a = malloc_page(PF,page_cnt);

		if (a != NULL){
			memset(a, 0 , page_cnt * PG_SIZE);		//clear the malloc memory
			a->desc = NULL;
			a->cnt = page_cnt;
			a->large = true;
			lock_release(&mem_pool->lock);
			//+1 is mean skip a size of struct arena(meta message)
			return (void*)(a + 1);				
		}else {
			lock_release(&mem_pool->lock);
			return NULL;
		}

	}else {								

		//get the kind of block index
		uint8_t index;
		for (index = 0 ; index < DESC_CNT;  index++){
			if (size <= descs[index].block_size){
				break;
			}
		}
		
		// if the 'free_list' already have no available 'mem_block', now create new arena
		if (list_empty(&descs[index].free_list)){
			a = malloc_page(PF , 1);
			if (a == NULL){
				lock_release(&mem_pool->lock);
				return NULL;
			}
			memset(a , 0 , PG_SIZE);

			a->desc = &descs[index];
			a->large = false;
			a->cnt = descs[index].block_per_arena;
			
			uint32_t block_index;

			enum intr_status old_status = intr_disable();

			//begin to split the memory of arena
			//and add to the 'free_list' of memory block descriptor
			for (block_index = 0 ;block_index < descs[index].block_per_arena;block_index ++){
				b = arena2block(a, block_index);
				ASSERT(!elem_find(&a->desc->free_list , &b->free_elem));
				list_append( &a->desc->free_list , &b->free_elem);
			}
			intr_set_status(old_status);
		}
		
		//begin to malloc memory block
		b = (struct mem_block*)(list_pop(&(descs[index].free_list)) );
		memset(b , 0 , descs[index].block_size);

		a = block2arena(b);
		a->cnt --;
		lock_release(&mem_pool->lock);
		return (void*)b;
	}

}

2.实现内存回收

a.实现页框级别内存回收

​ 内存回收与内存申请的过程相反,回顾内存申请过程:

  • vaddr_get:分配虚拟内存,操作对应虚拟内存池位图,置1
  • pallc:分配物理地址,操作对应物理内存池位图,置1
  • page_table_add:页表中完成虚拟地址到物理地址的映射

​ 内存回收过程:

  • pfree:回收物理内存,操作物理内存池位图,置0
  • page_table_pte_remove:删除虚拟内存的映射,通过将pte的p位置0,通过invlpg指令更新快表TLB(也可以通过重新加载cr3)
  • vaddr_remove:回收虚拟内存,操作虚拟内存池位图,置0
 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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

//free physic memory to physic memory pool
void pfree(uint32_t pg_phy_addr){
	struct pool* mem_pool;
	uint32_t index = 0 ;
	if (pg_phy_addr >= user_pool.phy_addr_start) {				//user memory pool
		mem_pool = &user_pool;
		index = (pg_phy_addr - user_pool.phy_addr_start ) / PG_SIZE;
	}else {									//kernel memory pool
		mem_pool = &kernel_pool;
		index = (pg_phy_addr - kernel_pool.phy_addr_start) / PG_SIZE;
	}
	bitmap_set(&mem_pool->pool_bitmap , index , 0 );
}

//remove the map of 'vaddr', just to remove the pte of 'vaddr'
static void page_table_pte_remove(uint32_t vaddr){
	uint32_t* pte = pte_ptr(vaddr);
	*pte &= PG_P_0 ;
	asm volatile ("invlpg %0": : "m" (vaddr) : "memory");
}

//free the 'pg_cnt' consecutive pages virtual memory from virtual memory pool
static void vaddr_remove(enum pool_flags pf , void* _vaddr , uint32_t pg_cnt){
	uint32_t bit_start_index = 0 , vaddr = (uint32_t)_vaddr , count = 0 ;
	if (pf == PF_KERNEL){				//kernel virtual memory pool
		bit_start_index = (vaddr - kernel_vaddr.vaddr_start ) / PG_SIZE;
		while(count < pg_cnt) {
			bitmap_set(&kernel_vaddr.vaddr_bitmap , bit_start_index+ count++, 0);
		}
	}else {						//user virtual memory pool
		struct task_struct * cur_thread = running_thread();
		bit_start_index =( vaddr - cur_thread->userprog_vaddr.vaddr_start) / PG_SIZE;
		while (count < pg_cnt ) {
			bitmap_set(&cur_thread->userprog_vaddr.vaddr_bitmap,bit_start_index + count++ , 0 );
		}
	}
}



//free 'pg_cnt' page memory
/***********************   mfree_page 	    ******************
 * 	1.vaddr_remove :		free virtual memory into virtual memory pool
 * 	2.pfree: 			free physic memory into  physic memory pool
 * 	3.page_table_pte_remove: 	remvoe the map
 ******************************************************************/
void mfree_page(enum  pool_flags pf , void* _vaddr , uint32_t pg_cnt){
	//struct pool* mem_pool;
	uint32_t phy_addr ;
	uint32_t vaddr = (uint32_t)_vaddr, count = 0;

	ASSERT(pg_cnt > 0 && vaddr % PG_SIZE == 0 );

	phy_addr = addr_v2p(vaddr); 		//get the physic memory by the virtual address;
	
	//make sure the target page is exist at the out of low 1mb , 1kb pdt and 1kb pt
	ASSERT( (phy_addr % PG_SIZE ) == 0 && phy_addr >= 0x102000);

	//judge use which memory pool
	if (phy_addr >= user_pool.phy_addr_start){			//in user memory pool
		vaddr -= PG_SIZE;	
		while(count < pg_cnt){
			vaddr += PG_SIZE;
			phy_addr = addr_v2p(vaddr);
			ASSERT((phy_addr % PG_SIZE ) == 0 && phy_addr >= user_pool.phy_addr_start);
			pfree(phy_addr);
			page_table_pte_remove(vaddr);
			count++;
		}
	}else {
		vaddr -= PG_SIZE;
		while(count < pg_cnt){
			vaddr += PG_SIZE;
			phy_addr = addr_v2p(vaddr);
			ASSERT((phy_addr % PG_SIZE ) == 0 &&	\
			phy_addr >= kernel_pool.phy_addr_start &&	\
			phy_addr < user_pool.phy_addr_start);
			pfree(phy_addr);
			page_table_pte_remove(vaddr);
			count++;
		}
	}
	//clear the target bit of virtual memory's bitmap
	vaddr_remove(pf , _vaddr , pg_cnt);
}
b.实现任意字节内存回收

​ 针对内存>1024,直接使用free_page回收页框

​ 针对内存<1024, 将内存块回收进对应规格内存块描述符的空闲块链表,如果该arena的内存块都是空闲,就回收arena占用的页框。

 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
//free memory 'ptr'
void sys_free(void* ptr){
	ASSERT( ptr != NULL);
	if (ptr != NULL){
		enum pool_flags pf;
		struct pool* mem_pool;

		//judge thread of process
		if (running_thread()->pgdir == NULL){
			ASSERT((uint32_t)ptr >= K_HEAP_START);
			pf = PF_KERNEL;
			mem_pool = &kernel_pool;
		}else {
			pf = PF_USER;
			mem_pool = &user_pool;
		}

		lock_acquire(&mem_pool->lock);
		struct mem_block* b = ptr;
		struct arena* a = block2arena(b);

		ASSERT(a->large == 1 || a->large == 0);
		if (a->desc == NULL && a->large == true ){
			mfree_page(pf , a , a->cnt);
		}else {
			//recycle the memory block into free_list
			list_append(&a->desc->free_list , &b->free_elem);

			//judge the all block in arena is it all free,
			//if all free that free the whole arena
			if (++a->cnt == a->desc->block_per_arena){
				uint32_t index;
				for (index = 0 ; index < a->desc->block_per_arena ; index++){
					struct mem_block* b = arena2block(a , index);
					ASSERT(elem_find(&a->desc->free_list ,  &b->free_elem));
					list_remove(&b->free_elem);
				}
				mfree_page( pf , a , 1);
			}
		}
		lock_release(&mem_pool->lock);
	}
}

3.总结

​ 至此loong-OS的内存管理基本完成。