目錄
1、內存堆控制
? ? ? ? ?1.1 內存堆控制器? ?
1.2 內存塊節點
1.3 內存堆管理
2、內存堆初始化
2.1 初始化接口
2.2 初始化示例
2.3 源碼分析
3、內存堆操作
3.1 內存塊申請
3.1.1?相關接口
3.1.2 原理分析
3.1.3 示例分析
3.1.4 代碼分析
3.2 內存塊伸縮
3.2.1 相關接口
3.2.2 原理分析
3.2.3 示例分析
3.2.4 源碼分析
3.3 內存快釋放
3.3.1 相關接口
3.3.2 原理分析
3.3.3 示例分析
3.3.4 源碼分析
???本章講解RT_Thread Nano實時操作系統的動態內存(內存堆)部分,RT-Thread提供三種動態內存管理算法:小堆內存管理算法、堆內存管理方法、SLAB內存管理算法。小堆內存管理模塊主要針對系統資源比較少,一般用于小于2M內存空間的系統;堆內存可以應用于多塊不連續的內存堆,SLAB內存管理模塊則主要是在系統資源比較豐富時,提供了一種近似多內存池管理算法的快速算法。三種內存管理模塊在系統運行時只能選擇其中之一或者完全不使用動態堆內存管理器。
? ? ? ? 三種管理模塊提供的API接口完全相同,小堆內存管理算法接口在mem.c,堆內存管理方法在memheap.c文件中定義,SLAB內存管理模塊在slab.c文件中定義。
內存管理方法 | 適用場景 | 宏定義設置 | 接口定義位置 |
小堆內存 | 小于2M內存空間 | RT_USING_HEAP RT_USING_SMALL_MEM | mem.c |
堆內存 | 多塊不連續的內存堆 | RT_USING_MEMHEAP_AS_HEAP RT_USING_MEMTRACE | memheap.c |
SLAB | 系統資源比較豐富 | RT_USING_HEAP RT_USING_SLAB | slab.c |
? ? ? ? 本章主要講解靜態小堆內存管理算法,其特點是內存利用率高,適用于小存儲空間。
本章基于RT_Thread Nano V3.1.5版本分析
1、內存堆控制
? 1.1 內存堆控制器? ?
????????內存堆控制器由一些全局變量定義,如下所示:
static rt_uint8_t *heap_ptr; // 內存堆首地址
static struct heap_mem *heap_end; // 內存堆尾部節點
static struct heap_mem *lfree; // 指向最低地址空閑塊節點,用于管理空閑內存塊
static struct rt_semaphore heap_sem; // 內存信號,用于臨界資源權限保護
static rt_size_t mem_size_aligned; // 對齊后小堆內存總大小
static rt_size_t used_mem; // 記錄已使用字節數
static rt_size_t max_mem; // 記錄已使用字節數的最大值
1.2 內存塊節點
?????????內存堆鏈表節點如下所示,宏定義ARCH_CPU_64BIT表示系統內核為64位。
struct heap_mem
{rt_uint16_t magic; /*內存塊標志字,默認0x1ea0*/rt_uint16_t used; /*=0表示內存塊空閑,=1表示內存塊被申請*/#ifdef ARCH_CPU_64BITrt_uint32_t resv;#endifrt_size_t next, prev; /*next表示上個內存塊節點的相對位置,prev表示下個內存塊節點的相對位置*//*內存監視使能*/#ifdef RT_USING_MEMTRACE#ifdef ARCH_CPU_64BITrt_uint8_t thread[8]; /* 記錄線程名稱*/#elsert_uint8_t thread[4]; /* 記錄線程名稱*/#endif#endif
};
1.3 內存堆管理
? ? ? ? 小堆內存中通過一個雙向鏈表管理內存塊,每個內存塊首定義一個節點,節點按照地址從小到大順序雙向連接。
? ? ? ? 內存節點參數next, prev非指針變量,而是表示節點地址相對于堆首地址heap_ptr的偏移,通過偏移定位和連接上下內存塊節點位置,例如一內存節點指針struct heap_mem *mem,其下個相鄰節點位置為heap_ptr[mem->next],上個相鄰節點位置為heap_ptr[mem->prev],同時,可計算出其內存塊大小為
size=(&heap_ptr(mem->next)-(char*)mem-sizeof(struct heap_mem));
? ? ? ? 空閑內存塊指針*lfree指向地址最低的空閑內存塊,內存申請時,從lfree指向位置開始向高地址遍歷所有內存塊,直至找到合適的空閑內存塊。當內存塊釋放后,需要重新判斷和定位lfree位置。
????????系統內核為64位時,最小內存堆默認24字節,否則默認12字節。最小長度可調。
#ifdef ARCH_CPU_64BIT
#define MIN_SIZE 24
#else
#define MIN_SIZE 12
#endif
2、內存堆初始化
? ? ? ? 小內存堆通過全局變量管理,將內存區對齊后,首尾各定義一個內存塊節點,堆首內存節點為空閑內存塊節點,堆尾節點屬性是已使用,將2個節點雙向連接,并將空閑內存塊指針指向堆首節點。
2.1 初始化接口
/*** 系統堆初始化.* @param begin_addr 系統堆起始地址* @param end_addr 系統堆結束地址*/
void rt_system_heap_init(void *begin_addr, void *end_addr)
2.2 初始化示例
????????代碼示例:
unsigned char heap[1024*1024];rt_system_heap_init(heap,heap+sizeof(heap));
? ? ? ? 內存結構圖示:初始化完成后lfree指向首節點,heap_end指向尾部節點,收尾節單向鏈接。已使用字節數used_mem為0,已使用字節數的最大值max_mem為0。
2.3 源碼分析
/*** 系統堆初始化.* @param begin_addr 系統堆起始地址* @param end_addr 系統堆結束地址*/
void rt_system_heap_init(void *begin_addr, void *end_addr)
{struct heap_mem *mem;/*內存堆首尾地址對齊,默認4字節對齊*/ 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);RT_DEBUG_NOT_IN_INTERRUPT;/*內存堆大小檢驗*/if ((end_align > (2 * SIZEOF_STRUCT_MEM)) &&((end_align - 2 * SIZEOF_STRUCT_MEM) >= begin_align)){/* 有效內存大小 */mem_size_aligned = end_align - begin_align - 2 * SIZEOF_STRUCT_MEM;}else{rt_kprintf("mem init, error begin address 0x%x, and end address 0x%x\n",(rt_ubase_t)begin_addr, (rt_ubase_t)end_addr);return;}/* 內存頭 */heap_ptr = (rt_uint8_t *)begin_align;/* 調試代碼*/RT_DEBUG_LOG(RT_DEBUG_MEM, ("mem init, heap begin address 0x%x, size %d\n",(rt_ubase_t)heap_ptr, mem_size_aligned));/* 初始化內存頭節點,next鏈接尾部節點位置 */mem = (struct heap_mem *)heap_ptr;mem->magic = HEAP_MAGIC;mem->next = mem_size_aligned + SIZEOF_STRUCT_MEM;// 指向內存相對位置(數組下標),非指針mem->prev = 0;mem->used = 0;/*記錄線程名稱"INIT"*/
#ifdef RT_USING_MEMTRACErt_mem_setname(mem, "INIT");
#endif/*初始化內存堆尾部節點*/heap_end = (struct heap_mem *)&heap_ptr[mem->next];heap_end->magic = HEAP_MAGIC;heap_end->used = 1;heap_end->next = mem_size_aligned + SIZEOF_STRUCT_MEM;/*與尾部鏈表連接*/heap_end->prev = mem_size_aligned + SIZEOF_STRUCT_MEM;
#ifdef RT_USING_MEMTRACErt_mem_setname(heap_end, "INIT");
#endif/*初始化內存權限信號*/rt_sem_init(&heap_sem, "heap", 1, RT_IPC_FLAG_FIFO);/* 初始化空閑塊指針,指向堆首節點 */lfree = (struct heap_mem *)heap_ptr;
}
3、內存堆操作
????????內存堆操作可以分為內存申請、內存伸縮、內存釋放。
3.1 內存塊申請
3.1.1?相關接口
????????接口rt_malloc僅申請一定大小的內存,并不進行初始化,所以申請的內存數據值不確定。接口rt_calloc審請內存后,會初始化為0。可以類比于C庫的malloc、calloc接口。
/*動態內存申請接口,size--申請內存大小,申請成功后返回內存塊首地址,否則返回Null*/
void *rt_malloc(rt_size_t size)/*動態內存申請接口,count--申請內存單元個數,size--單個單元大小
內存申請完成后初始化為0,申請成功后返回內存塊首地址,否則返回NULL*/
void *rt_calloc(rt_size_t count, rt_size_t size)
3.1.2 原理分析
????????內存控制塊申請,是從最低地址空閑內存塊節點lfree指向的位置開始向高地址遍歷所有內存塊,直至找到滿足申請大小的空閑內存塊,將該內存塊標記為已使用,如果該內存塊比較大,從該內存塊中切割出所需的內存,剩余部分形成新的內存塊,并定義新的內存塊節點,插入到內存塊雙向鏈表中。
3.1.3 示例分析
unsigned char heap[1024*1024];
// 初始化調用
rt_system_heap_init(heap,heap+sizeof(heap));
// 線程1調用,假設線程名稱"Led1"
void Thread1()
{char *p1=rt_malloc(1200);
}
// 線程2調用,假設線程名稱"Led2"
void Thread1()
{char *p2=rt_malloc(1000);
}
3.1.4 代碼分析
/*動態內存申請基礎接口*/
void *rt_malloc(rt_size_t size)
{rt_size_t ptr, ptr2;struct heap_mem *mem, *mem2;if (size == 0) return RT_NULL;RT_DEBUG_NOT_IN_INTERRUPT;/*調試代碼,長度是否對齊判斷, */if (size != RT_ALIGN(size, RT_ALIGN_SIZE))RT_DEBUG_LOG(RT_DEBUG_MEM, ("malloc size %d, but align to %d\n",size, RT_ALIGN(size, RT_ALIGN_SIZE)));elseRT_DEBUG_LOG(RT_DEBUG_MEM, ("malloc size %d\n", size));/*長度對齊,默認4字節 */size = RT_ALIGN(size, RT_ALIGN_SIZE);/*分配長度size超過可用內存長度,返回失敗*/if (size > mem_size_aligned){RT_DEBUG_LOG(RT_DEBUG_MEM, ("no memory\n"));return RT_NULL;}/* 數據塊長度容錯 */if (size < MIN_SIZE_ALIGNED)size = MIN_SIZE_ALIGNED;/* 獲取信號,阻塞方式為永久阻塞 */rt_sem_take(&heap_sem, RT_WAITING_FOREVER);/*根據相對位置,遍歷空閑鏈表,即從空閑表頭開始遍歷,一直找到符合大小的控制塊*/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];/*判斷內存塊屬性,未使用,且大小滿足size*/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;/* 設置新節點 create mem2 struct */mem2 = (struct heap_mem *)&heap_ptr[ptr2];mem2->magic = HEAP_MAGIC;mem2->used = 0;mem2->next = mem->next;mem2->prev = ptr;#ifdef RT_USING_MEMTRACE/*內存監視使能*/rt_mem_setname(mem2, " ");#endif/*按照地址順序 插入節點and insert it between mem and 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;}
#ifdef RT_MEM_STATS/*記錄當前使用內存和最大使用內存*/used_mem += (size + SIZEOF_STRUCT_MEM);if (max_mem < used_mem)max_mem = used_mem;
#endif}/* 不需要分割內存塊 */else{/*設置為已使用*/mem->used = 1;
#ifdef RT_MEM_STATS/*記錄當前使用內存和最大使用內存*/used_mem += mem->next - ((rt_uint8_t *)mem - heap_ptr);if (max_mem < used_mem) max_mem = used_mem;
#endif}/* 設置新節點標志字 set memory block magic */mem->magic = HEAP_MAGIC;
#ifdef RT_USING_MEMTRACE/*內存監視,記錄線程名稱*/if (rt_thread_self())rt_mem_setname(mem, rt_thread_self()->name);elsert_mem_setname(mem, "NONE");
#endif/*lfree指向的節點已被分配,調整空閑鏈表指針*/if (mem == lfree){/* 指向下一個未被分配的內存塊 */while (lfree->used && lfree != heap_end)lfree = (struct heap_mem *)&heap_ptr[lfree->next];/*斷言*/RT_ASSERT(((lfree == heap_end) || (!lfree->used)));}/*釋放信號*/rt_sem_release(&heap_sem);/*斷言*/RT_ASSERT((rt_ubase_t)mem + SIZEOF_STRUCT_MEM + size <= (rt_ubase_t)heap_end);RT_ASSERT((rt_ubase_t)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM) % RT_ALIGN_SIZE == 0);RT_ASSERT((((rt_ubase_t)mem) & (RT_ALIGN_SIZE - 1)) == 0);/*調試代碼*/RT_DEBUG_LOG(RT_DEBUG_MEM,("allocate memory at 0x%x, size: %d\n",(rt_ubase_t)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM),(rt_ubase_t)(mem->next - ((rt_uint8_t *)mem - heap_ptr))));RT_OBJECT_HOOK_CALL(rt_malloc_hook,(((void *)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM)), size));/* 返回塊地址 */return (rt_uint8_t *)mem + SIZEOF_STRUCT_MEM;}}rt_sem_release(&heap_sem);return RT_NULL;
}
void *rt_calloc(rt_size_t count, rt_size_t size)
{void *p;/* 申請內存,大小size */p = rt_malloc(count * size);/* 將申請的內存初始化為0 */if (p)rt_memset(p, 0, count * size);return p;
}
3.2 內存塊伸縮
3.2.1 相關接口
????????接口rt_realloc可以將已申請的內存大小進行調整或新申請一塊內存。可以類比于C庫的realloc接口。????????
/*動態內存長度伸縮,rmem--內存塊首地址,newsize--內存塊新長度,申請成功后返回內存塊首地址,否則返回NULL*/
void *rt_realloc(void *rmem, rt_size_t newsize)
3.2.2 原理分析
? ? ? ? 使用rt_realloc分配或調整內存塊分以下幾種情況,newsize先進行對齊:
(1)newsize對齊后為0,直接釋放內存塊。
(2)內存塊rmem未被分配,直接按照newsize對齊后大小分配內存,等同于rt_malloc(newsize)。
(3)rmem已被分配,當newsize對齊后小于等于原內存塊大小時,可以對原內存塊進行部分釋放:若newsize與原內存塊差不足以劃分一個新內存塊時,維持原內存結構不變。若足以劃分一個新內存塊時,則劃分出新的空閑內存塊進行釋放。
(4)rmem已被分配,當newsize對齊后大于原內存塊大小時,則根據newsize重新申請內存,并將原內存的數據復制到新內存塊,并釋放原內存塊。
3.2.3 示例分析
3.2.4 源碼分析
void *void *rt_realloc(void *rmem, rt_size_t newsize)
{rt_size_t size;rt_size_t ptr, ptr2;struct heap_mem *mem, *mem2;void *nmem;RT_DEBUG_NOT_IN_INTERRUPT;/*新尺寸對齊,默認4字節 */newsize = RT_ALIGN(newsize, RT_ALIGN_SIZE);/*新尺寸過大,返回失敗*/if (newsize > mem_size_aligned){RT_DEBUG_LOG(RT_DEBUG_MEM, ("realloc: out of memory\n"));return RT_NULL;}/*新尺寸為0,釋放內存塊*/else if (newsize == 0){rt_free(rmem);return RT_NULL;}/* rmem 未被分配,直接按照newsize大小分配內存 */if (rmem == RT_NULL)return rt_malloc(newsize);/*獲取信號權限*/rt_sem_take(&heap_sem, RT_WAITING_FOREVER);/*內存塊容錯*/if ((rt_uint8_t *)rmem < (rt_uint8_t *)heap_ptr ||(rt_uint8_t *)rmem >= (rt_uint8_t *)heap_end){/* 釋放信號 */rt_sem_release(&heap_sem);return rmem;}/*獲取當前內存塊節點*/mem = (struct heap_mem *)((rt_uint8_t *)rmem - SIZEOF_STRUCT_MEM);/*新舊大小相同,直接釋放信號后返回原內存塊首地址*/ptr = (rt_uint8_t *)mem - heap_ptr;size = mem->next - ptr - SIZEOF_STRUCT_MEM;if (size == newsize){/*釋放信號*/rt_sem_release(&heap_sem);return rmem;}/*新長度小于原長度,并且滿足劃分新塊的條件*/if (newsize + SIZEOF_STRUCT_MEM + MIN_SIZE < size){/* 劃分新內存塊 */
#ifdef RT_MEM_STATSused_mem -= (size - newsize);
#endif/*設置新內存塊節點*/ptr2 = ptr + SIZEOF_STRUCT_MEM + newsize;mem2 = (struct heap_mem *)&heap_ptr[ptr2];mem2->magic = HEAP_MAGIC;mem2->used = 0;mem2->next = mem->next;mem2->prev = ptr;/*設置線程名稱*/
#ifdef RT_USING_MEMTRACErt_mem_setname(mem2, " ");
#endifmem->next = ptr2;if (mem2->next != mem_size_aligned + SIZEOF_STRUCT_MEM){((struct heap_mem *)&heap_ptr[mem2->next])->prev = ptr2;}/*調整空閑內存塊指針*/if (mem2 < lfree){/* the splited struct is now the lowest */lfree = mem2;}/**/plug_holes(mem2);/*釋放信號*/rt_sem_release(&heap_sem);return rmem;}/*新長度大于原長度*//*釋放信號*/ rt_sem_release(&heap_sem);/* 重新分配內存 */nmem = rt_malloc(newsize);if (nmem != RT_NULL) /* check memory */{/*原內存區數據復制到新內存區,然后釋放原內存*/rt_memcpy(nmem, rmem, size < newsize ? size : newsize);rt_free(rmem);}return nmem;
}
3.3 內存快釋放
3.3.1 相關接口
/** 釋放內存塊,rmem指向內存塊首地址*/
void rt_free(void *rmem)
3.3.2 原理分析
????????使用rt_free釋放內存塊:
(1)設置內存塊節點為空閑狀態。
(2)如果下個相鄰內存塊為空閑狀態,則合并內存塊,將下個內存塊的節點從鏈表刪除。
(3)如果上個相鄰內存塊為空閑狀態,則合并內存塊,將當前內存塊的節點從鏈表刪除。
3.3.3 示例分析
3.3.4 源碼分析
/** 釋放內存塊*/
void rt_free(void *rmem)
{struct heap_mem *mem;if (rmem == RT_NULL)return;RT_DEBUG_NOT_IN_INTERRUPT;/*斷言*/RT_ASSERT((((rt_ubase_t)rmem) & (RT_ALIGN_SIZE - 1)) == 0);RT_ASSERT((rt_uint8_t *)rmem >= (rt_uint8_t *)heap_ptr &&(rt_uint8_t *)rmem < (rt_uint8_t *)heap_end);/*執行鉤子*/RT_OBJECT_HOOK_CALL(rt_free_hook, (rmem));/*地址檢查*/if ((rt_uint8_t *)rmem < (rt_uint8_t *)heap_ptr ||(rt_uint8_t *)rmem >= (rt_uint8_t *)heap_end){RT_DEBUG_LOG(RT_DEBUG_MEM, ("illegal memory\n"));return;}/* 獲取當前內存塊節點*/mem = (struct heap_mem *)((rt_uint8_t *)rmem-SIZEOF_STRUCT_MEM);/*調試代碼*/RT_DEBUG_LOG(RT_DEBUG_MEM,("release memory 0x%x, size: %d\n",(rt_ubase_t)rmem,(rt_ubase_t)(mem->next - ((rt_uint8_t *)mem - heap_ptr))));/* 獲取內存信號權限 */rt_sem_take(&heap_sem, RT_WAITING_FOREVER);/* 檢查內存塊節點*/if (!mem->used || mem->magic != HEAP_MAGIC){rt_kprintf("to free a bad data block:\n");rt_kprintf("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);/*修改節點(修改為未使用)*/mem->used = 0;mem->magic = HEAP_MAGIC;
#ifdef RT_USING_MEMTRACE/*復歸線程名稱為空格*/rt_mem_setname(mem, " ");
#endif/*調整空閑頭部lfree,指向地址最低的空閑控制塊*/if (mem < lfree){lfree = mem;}#ifdef RT_MEM_STATS/*計算已使用內存*/used_mem -= (mem->next - ((rt_uint8_t *)mem - heap_ptr));
#endif/* 合并內存塊,前后內存塊空閑判斷*/plug_holes(mem);/*釋放信號*/rt_sem_release(&heap_sem);
}/* 合并內存塊,前后內存塊空閑判斷*/
static void plug_holes(struct heap_mem *mem)
{struct heap_mem *nmem;struct heap_mem *pmem;/*斷言*/RT_ASSERT((rt_uint8_t *)mem >= heap_ptr);RT_ASSERT((rt_uint8_t *)mem < (rt_uint8_t *)heap_end);RT_ASSERT(mem->used == 0);/* 判斷下一個內存塊,如果是空閑塊,則合并 */nmem = (struct heap_mem *)&heap_ptr[mem->next];if (mem != nmem &&nmem->used == 0 &&(rt_uint8_t *)nmem != (rt_uint8_t *)heap_end){/*糾正空閑節點lfree,lfree指向地址最低的空閑內存塊*/ if (lfree == nmem){lfree = mem;}/*刪除下一個內存塊節點*/mem->next = nmem->next;((struct heap_mem *)&heap_ptr[nmem->next])->prev = (rt_uint8_t *)mem - heap_ptr;}/* 判斷上一個內存塊,如果是空閑塊,則合并 */pmem = (struct heap_mem *)&heap_ptr[mem->prev];if (pmem != mem && pmem->used == 0){/* 糾正空閑節點lfree,lfree指向地址最低的空閑內存塊 */if (lfree == mem){lfree = pmem;}/*刪除當前節點*/pmem->next = mem->next;((struct heap_mem *)&heap_ptr[mem->next])->prev = (rt_uint8_t *)pmem - heap_ptr;}
}