內存管理
在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讀值然後釋放;