FreeRTOS内存管理
生活随笔
收集整理的這篇文章主要介紹了
FreeRTOS内存管理
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
堆空間是一個數組,configTOTAL_HEAP_SIZE表示堆空間大小,在FreeRTOSConfig.h中宏定義
/* 由應用程序創建堆區,大小為configTOTAL_HEAP_SIZE */ #if (configAPPLICATION_ALLOCATED_HEAP == 1)extern uint8_t ucHeap[configTOTAL_HEAP_SIZE]; /* 由內核創建堆區,大小為configTOTAL_HEAP_SIZE */ #elsestatic uint8_t ucHeap[configTOTAL_HEAP_SIZE]; #endif?
?
將每一個空閑的內存塊組織成一個結構體,并將所有空閑塊結構體掛接到一個鏈表上
/* 初始化堆區 */ static void prvHeapInit(void) {BlockLink_t *pxFirstFreeBlock;uint8_t *pucAlignedHeap;size_t uxAddress;/* 堆區總大小 */size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;/* 堆區首地址 */uxAddress = (size_t)ucHeap;/* 如果堆區首地址沒有8字節對齊 */if((uxAddress & portBYTE_ALIGNMENT_MASK) != 0){/* 對堆區首地址進行8字節對齊 */uxAddress += (portBYTE_ALIGNMENT - 1);uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK);/* 更新堆區總大小 */xTotalHeapSize -= uxAddress - (size_t)ucHeap;}/* 8字節對齊后的堆區首地址 */pucAlignedHeap = (uint8_t *)uxAddress;/* 將首節點next指針指向堆區首地址(沒分配前堆區是一整個內存塊) */xStart.pxNextFreeBlock = (void *)pucAlignedHeap;/* 首節點只起到頭部作用,不表示內存塊,因此大小設為0 */xStart.xBlockSize = (size_t)0;/* 尾節點被組織成一個大小為0的內存塊,放在整個堆區的末端 */uxAddress = ((size_t)pucAlignedHeap) + xTotalHeapSize;/* 尾節點共占用一個結構體大小,計算出尾節點首地址 */uxAddress -= xHeapStructSize;/* 將尾節點首地址進行8字節對齊 */uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK);/* 將尾節點調整后的首地址賦值給pxEnd */pxEnd = (void *)uxAddress;/* 尾節點內存塊大小為0 */pxEnd->xBlockSize = 0;/* 尾節點next指針指向NULL */pxEnd->pxNextFreeBlock = NULL;/* 初始化時要先將整個堆區組織成第一個內存塊 */pxFirstFreeBlock = (void *)pucAlignedHeap;/* 大小為去掉尾節點后的堆區大小 */pxFirstFreeBlock->xBlockSize = uxAddress - (size_t)pxFirstFreeBlock;/* 沒分配前第一個內存塊指向尾節點 */pxFirstFreeBlock->pxNextFreeBlock = pxEnd;/* 初始化時堆區剩余最小空間時的剩余空閑空間大小為第一個內存塊大小 */xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;/* 初始化時堆區剩余空間總大小為第一個內存塊大小 */xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;/* 計算出內存塊是否已分配標志位在內存塊結構體中xBlockSize中的位置(最高位) */xBlockAllocatedBit = ((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1); }堆內存初始化完成后,組織形式如下圖:
?
?
找到第一個滿足大小的空閑內存塊,進行內存分配。需要注意的是如果內存塊遠遠大于請求分配內存的大小,還需要將該空閑內存塊的剩余部分重新組織,并插入到鏈表中。
/* 功能:請求動態分配內存xWantedSize:請求分配的內存大小返回值:已分配的內存地址,NULL表示分配失敗 */ void *pvPortMalloc(size_t xWantedSize) {BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;void *pvReturn = NULL;/* 臨界區 */vTaskSuspendAll();{/* 首次分配內存的時候堆區未初始化,pxEnd指向NULL,需要進行初始化 */if(pxEnd == NULL){/* 初始化堆區 */prvHeapInit();}else{mtCOVERAGE_TEST_MARKER();}/* 請求分配的內存大小最高位不能為1,因為最高位被用來表示該內存塊是否已分配 *//* 換言之不能申請超過2G字節 */if((xWantedSize & xBlockAllocatedBit) == 0){/* 請求分配的內存大小必須大于0 */if(xWantedSize > 0){/* 要對內存進行管理,必須加上內存塊結構體 */xWantedSize += xHeapStructSize;/* 配的字節(加上結構體后)是否已經字節對齊,如果沒有則要進行對齊 */if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00){/* 進行字節對齊 */xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK));/* 如果對齊不了,說明出現錯誤,進行斷言 */configASSERT((xWantedSize & portBYTE_ALIGNMENT_MASK) == 0);}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}/* 請求分配的大小必須大于0,并且請求分配的大小(加上結構體后)必須剩余空閑內存總大小 */if((xWantedSize > 0) && (xWantedSize <= xFreeBytesRemaining)){/* 遍歷所有可分配的內存塊,檢查是否存在大小合適的內存塊 */pxPreviousBlock = &xStart;pxBlock = xStart.pxNextFreeBlock;while((pxBlock->xBlockSize < xWantedSize) && (pxBlock->pxNextFreeBlock != NULL)){pxPreviousBlock = pxBlock;pxBlock = pxBlock->pxNextFreeBlock;}/* 如果存在,則進行分配 */if(pxBlock != pxEnd){/* 分配成功返回的內存地址 */pvReturn = (void *)(((uint8_t *)pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize);/* 將該內存塊從空閑內存塊鏈表中移除 */pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;/* 如果該內存塊太大,去掉請求的大小(加上結構體后)還夠分配一個內存塊的話,需要將多余的內存組織成新的內存塊,并插入空閑內存塊鏈表 */if((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE){/* 新的內存塊在剩余內存地址處 */pxNewBlockLink = (void *)(((uint8_t *)pxBlock) + xWantedSize);/* 如果新的內存塊不是8字節對齊的話,進行斷言 */configASSERT((((size_t)pxNewBlockLink) & portBYTE_ALIGNMENT_MASK) == 0);/* 新的內存塊大小為該內存塊減去請求大小(加上結構體后) */pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;/* 該內存塊大小更新為請求大小(加上結構體) */pxBlock->xBlockSize = xWantedSize;/* 將新內存塊插入空閑內存塊鏈表(內存塊地址從小到大排列) */prvInsertBlockIntoFreeList(pxNewBlockLink);}else{mtCOVERAGE_TEST_MARKER();}/* 更新剩余空閑內存總大小 */xFreeBytesRemaining -= pxBlock->xBlockSize;/* 更新堆區剩余最小空間時的剩余空閑空間大小 */if(xFreeBytesRemaining < xMinimumEverFreeBytesRemaining){xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;}else{mtCOVERAGE_TEST_MARKER();}/* 將該內存塊最高位置1,表示該內存塊已經被分配 */pxBlock->xBlockSize |= xBlockAllocatedBit;/* 將該內存塊next指針指向NULL */pxBlock->pxNextFreeBlock = NULL;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}/* 設置一個跟蹤點,輸出內存地址和大小 */traceMALLOC(pvReturn, xWantedSize);}/* 退出臨界區 */(void)xTaskResumeAll();/* 如果啟用內存動態分配失敗鉤子函數,則要判斷是否失敗并調用鉤子函數 */#if (configUSE_MALLOC_FAILED_HOOK == 1){/* 判斷是否分配失敗,如果失敗則要調用鉤子函數 */if(pvReturn == NULL){extern void vApplicationMallocFailedHook(void);/* 調用鉤子函數 */vApplicationMallocFailedHook();}else{mtCOVERAGE_TEST_MARKER();}}#endif/* 如果分配空間指針不是8字節對齊,則進行斷言 */configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0);return pvReturn; }釋放內存的時候,就是將該段內存重新組織成空閑內存塊結構體,并重新插入鏈表
/* 功能:釋放動態分配的內存pv:請求釋放的內存地址 */ void vPortFree(void *pv) {uint8_t *puc = (uint8_t *)pv;BlockLink_t *pxLink;/* 請求釋放的地址不能為空 */if(pv != NULL){/* 計算出該內存所在內存塊(結構體 + 內存)的首地址 */puc -= xHeapStructSize;pxLink = (void *)puc;/* 如果該節點并沒有已經被分配,則進行斷言 */configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0);/* 如果該節點next指針沒有指向NULL,則進行斷言 */configASSERT(pxLink->pxNextFreeBlock == NULL);/* 該內存塊必須已經被分配 */if((pxLink->xBlockSize & xBlockAllocatedBit) != 0){/* 該內存塊next指針必須指向NULL */if(pxLink->pxNextFreeBlock == NULL){/* 將該內存塊最高位置0,表示該內存塊未被分配 */pxLink->xBlockSize &= ~xBlockAllocatedBit;/* 進入臨界區 */vTaskSuspendAll();{/* 更新剩余內存塊大小 */xFreeBytesRemaining += pxLink->xBlockSize;/* 設置一個跟蹤點,輸出內存地址和大小 */traceFREE(pv, pxLink->xBlockSize);/* 將新內存塊插入空閑內存塊鏈表(內存塊地址從小到大排列) */prvInsertBlockIntoFreeList(((BlockLink_t *)pxLink));}/* 退出臨界區 */(void)xTaskResumeAll();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}} }經過幾次分配和釋放之后,內存空間排列變得很復雜,如下圖:
?
?
為了防止內存碎片化,在空閑塊重新插入鏈表的時候會將地址相鄰的兩塊內存空間進行合并
/* 將新內存塊插入空閑內存塊鏈表(內存塊地址從小到大排列) */ static void prvInsertBlockIntoFreeList(BlockLink_t *pxBlockToInsert) {BlockLink_t *pxIterator;uint8_t *puc;/* 內存地址從小到大進行排列,尋找合適的插入點 */for(pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock){}/* 如果和前一個內存塊地址相鄰,則要進行合并 */puc = (uint8_t *)pxIterator;if((puc + pxIterator->xBlockSize) == (uint8_t *)pxBlockToInsert){pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;pxBlockToInsert = pxIterator;}else{mtCOVERAGE_TEST_MARKER();}/* 如果和后一個內存塊地址相鄰并且后一個內存塊不是尾節點,則要進行合并 */puc = (uint8_t * )pxBlockToInsert;if((puc + pxBlockToInsert->xBlockSize) == (uint8_t *)pxIterator->pxNextFreeBlock){/* 如果不是尾節點,進行合并 */if(pxIterator->pxNextFreeBlock != pxEnd){pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;}/* 如果是尾節點,則直接指向尾節點 */else{pxBlockToInsert->pxNextFreeBlock = pxEnd;}}/* 如果和后一個內存塊地址不相鄰,則直接指向下一個內存塊 */else{pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;}/* 將該內存塊插入空閑內存塊鏈表 */if(pxIterator != pxBlockToInsert){pxIterator->pxNextFreeBlock = pxBlockToInsert;}else{mtCOVERAGE_TEST_MARKER();} }將原先分配的空間2釋放之后,內存的組織形式,如下圖:
?
?
內存管理還提供了一些其他的接口
/* 功能:獲取堆區剩余內存大小返回值:剩余內存大小 */ size_t xPortGetFreeHeapSize(void) {return xFreeBytesRemaining; } /* 功能:獲取堆區剩余最小空間時的剩余空閑空間大小返回值:堆區剩余最小空間時的剩余空閑空間大小 */ size_t xPortGetMinimumEverFreeHeapSize(void) {return xMinimumEverFreeBytesRemaining; }?
總結
以上是生活随笔為你收集整理的FreeRTOS内存管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: STM32之GPIO原理
- 下一篇: Simulink之器件换流式电压型无源逆