内存管理
在RT-Thread中分为动态内存管理和静态内存管理。静态的又称为内存池管理,动态的称为内存堆管理。
内存堆:
官方提供了三种管理方式。
- 小内存管理。申请时在一块大的、连续的内存中按需求分割出相匹配的小内存块;释放时,归还给堆管理系统。每个内存块都包含一个管理用的数据头,通过双向链表来管理使用块和空闲块。
- slab 管理算法。TODO!
- memheap 管理算法。适用于系统含有多个地址可不连续的内存堆。
内存池:
内存池在创建时先向系统申请一大块内存,然后分成同样大小的多个小内存块,小内存块直接通过链表连接起来(此链表也称为空闲链表)。每次分配的时候,从空闲链表中取出链头上第一个内存块,提供给申请者。
小内存管理算法实现
三个核心函数
- void rt_system_heap_init(void *begin_addr, void *end_addr);
- void *rt_malloc(rt_size_t size);
- void rt_free(void *rmem);
rt_system_heap_init 初始化堆
void rt_system_heap_init(void *begin_addr, void *end_addr)
{
struct heap_mem *mem;
rt_ubase_t begin_align = RT_ALIGN((rt_ubase_t)begin_addr, RT_ALIGN_SIZE);
rt_ubase_t end_align = RT_ALIGN_DOWN((rt_ubase_t)end_addr, RT_ALIGN_SIZE);
if((end_align > (2 * SIZEOF_STRUCT_MEM)) &&
((end_align - 2 * SIZEOF_STRUCT_MEM) >= begin_align)){ // 最少要存两个heap_mem
mem_size_aligned = end_align - begin_align - 2 * SIZEOF_STRUCT_MEM;
}else{
printf("mem init, error begin address 0x%x, and end address 0x%x\n",
(rt_uint32_t)begin_addr, (rt_uint32_t)end_addr);
return;
}
heap_ptr = (rt_uint8_t *)begin_align;
mem = (struct heap_mem *)heap_ptr;
mem->magic = HEAP_MAGIC;
mem->used = 0;
mem->prev = 0; // pre、next是相对于heap_ptr的偏移量
mem->next = mem_size_aligned + SIZEOF_STRUCT_MEM; // mem_size_aligned是减去两个heap_mem后的大小
heap_end = (struct heap_mem *)&heap_ptr[mem->next];
heap_end->magic = HEAP_MAGIC;
heap_end->used = 1;
heap_end->prev = mem->next;
heap_end->next = mem->next;
rt_sem_init(&heap_sem, "heap", 1, RT_IPC_FLAG_FIFO); // 初始化内存管理专用信号量
lfree = (struct heap_mem *)heap_ptr; // lfree记录最头部的可用内存块
}
先说明内存控制块结构,申请的每个内存块都包含这样一个数据头;
/* 内存控制块 */
struct heap_mem
{
/* magic and used flag */
rt_uint16_t magic;
rt_uint16_t used;
rt_size_t next, prev;
};
- mem.magic被称为幻数,他会被初始化成0x1ea0,用于标记这个内存块是一个内存管理用的内存数据;
- mem.used表示当前内存块是否被使用;
- mem.next和 mem.prev用于双向链表的管理;
初始化内存堆时,需要传入起始和结束地址,通过编写链接脚本来自定义用于堆的内存大小,以及堆位置。尾部的数据头表示在这个地址之后没有内存空间可以分配了。lfree
记录系统当前最头部的空闲块。
rt_malloc 申请内存
void *rt_malloc(rt_size_t size){
struct heap_mem *mem, *mem2;
rt_size_t ptr, ptr2;
if(size == 0){
return RT_NULL;
}
size = RT_ALIGN(size, RT_ALIGN_SIZE);
if(size > mem_size_aligned){
printf("no memory %d\r\n", size);
return RT_NULL;
}
if(size < MIN_SIZE_ALIGNED){
size = MIN_SIZE_ALIGNED;
}
/* 信号量保护资源 */
rt_sem_take(&heap_sem, RT_WAITING_FOREVER);
/* ptr是空闲内存的偏移量,ptr < mem_size_aligned - size说明可能存在能分配的块 */
for(ptr = (rt_uint8_t *)lfree - heap_ptr; ptr < mem_size_aligned - size; ptr = ((struct heap_mem *)&heap_ptr[ptr])->next){
mem = (struct heap_mem *)&heap_ptr[ptr];
/* mem->next - (ptr + SIZEOF_STRUCT_MEM)是当前内存块大小 */
if((!mem->used) && mem->next - (ptr + SIZEOF_STRUCT_MEM) >= size){
/* 如果该内存块足够大,将剩余部分初始化成一个空闲块 */
if(mem->next - (ptr + SIZEOF_STRUCT_MEM) >= size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED){
ptr2 = ptr + SIZEOF_STRUCT_MEM + size;
mem2 = (struct heap_mem *)&heap_ptr[ptr2];
mem2->magic = HEAP_MAGIC;
mem2->used = 0;
mem2->prev = ptr;
mem2->next = mem->next;
mem->next = ptr2;
mem->used = 1;
if(mem2->next != mem_size_aligned + SIZEOF_STRUCT_MEM){
((struct heap_mem *)&heap_ptr[mem2->next])->prev = ptr2;
}
}else{
mem->used = 1;
}
/* 寻找新的、最头部的空闲块 */
if(mem == lfree){
while(lfree->used && lfree != heap_end)
lfree = (struct heap_mem *)&heap_ptr[lfree->next];
}
rt_sem_release(&heap_sem);
return (rt_uint8_t *)mem + SIZEOF_STRUCT_MEM;
}
}
rt_sem_release(&heap_sem);
return RT_NULL;
}
从lfree处往后找大于size的空闲块,如果找到的内存块足够大,将剩余部分初始化成一个新的空闲块。同时更新lfree
rt_free 释放内存
void rt_free(void *rmem){
struct heap_mem *mem;
if(rmem == RT_NULL){
printf("addr is invalid %d\r\n", 0);
return;
}
if((rt_uint8_t *)rmem < heap_ptr || (rt_uint8_t *)rmem >= heap_end){
printf("addr is invalid %d\r\n", -1);
return;
}
/* 获取信号量 */
rt_sem_take(&heap_sem, RT_WAITING_FOREVER);
mem = (struct heap_mem *)((rt_uint8_t *)rmem - SIZEOF_STRUCT_MEM);
if (!mem->used || mem->magic != HEAP_MAGIC)
{
printf("to free a bad data block: %c\n", ' ');
printf("mem: 0x%08x, used flag: %d, magic code: 0x%04x\n", mem, mem->used, mem->magic);
}
RT_ASSERT(mem->used);
RT_ASSERT(mem->magic == HEAP_MAGIC); // 针对上面的if
/* 标记为未使用即可 */
mem->used = 0;
mem->magic = HEAP_MAGIC;
if(mem < lfree){
lfree = mem;
}
/* 用于合并前后未使用的内存块 */
plug_holes(mem);
rt_sem_release(&heap_sem);
}
释放时,修改.used
标志即可,然后调用plug_holes(mem)
合并前后未使用的内存块。
动态内存管理是动态创建线程,以及其他内核应用的前提。
工程文件
rtt2a9_heap
按键1申请内存并写值,按键2读值然后释放;