FreeRTOS 作為一個嵌入式實時操作系統,其運行的環境一般資源有限,特別是其內存資源,可能只有幾 M,甚至是幾十 KB。針對不同的應用場景,FreeRTOS 源碼中提供了 5 種內存管理方案。本文就來聊聊這些內存管理方案以及相關的優化措施。
內存管理相關的代碼在 lib/FreeRTOS/portable/MemMang 目錄下,從 heap_1.c 到 heap_5.c,文件相互獨立,并提供統一接口:
void *pvPortMalloc( size_t xWantedSize );
void vPortFree( void *pv );
而 heap 的定義,可以是一塊靜態分配的數組作為 heap,也可以是指定的一塊區域作為 heap,本文就以靜態數組為例進行講解。
#if( configAPPLICATION_ALLOCATED_HEAP == 1)
extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif
實際上,內存釋放接口并不一定都有效,因為 heap_1 方案是不支持內存釋放的。在 heap_1 中調用 pvPortMalloc() 的時候,其具體實現如下所示:
pucAligendHeap: 對齊后的 heap 起始地址。
xNextFreeByte: 記錄下一次分配內存的偏移值,實際就是已分配內存的總大小。
pvReturn = pucAlignedHeap + xNextFreeByte;
xNextFreeByte += xWantedSize;
每次分配的時候,返回 pucAligendHeap + xNextFreeByte 的地址,然后更新 xNextFreeByte。當然了,整個分配過程中,還有一些對齊操作。由于分配后的內存不能進行釋放,所以這種分配方案適用于不需要頻繁申請釋放內存的場景。
相對于 heap_1 方案,在 heap_2 中增加了內存釋放的函數。其實現是在每一次分配的內存中增加了一些描述信息(也就是多分配一個結構體大小的內存,用于保存描述信息),這樣在釋放的時候,就可以根據這些信息回收內存。
typedef struct A_BLOCK_LINK {
struct A_BLOCK_LINK *pxNextFreeBlock;
size_t xBlockSize;
} BlockLink_t;
static const uint16_t heapSTRUCT_SIZE = (( sizeof( BlockLink_t ) + ( portBYTE_ALIGNMENT - 1 )) & ~portBYTE_ALIGNMENT_MASK );
xWantedSize += heapSTRUCT_SIZE; /*每次分配的時候自動加上描述結構體的大小*/
描述信息結構體中包含了一個鏈表指針,用于在釋放的時候,將該內存加入到空閑鏈表中,每次分配內存實際就是從空閑鏈表上進行分配(初始狀態,整個鏈表除了頭尾,就一個成員,就是整個空閑的 heap 塊)。xBlockSize 用于描述分配內存的大小。
其中,xStart 和 xEnd 是空閑鏈的頭尾節點。初始狀態時,整個 heap 就是一個空閑塊。分配的時候,從頭遍歷空閑鏈,找到第一個滿足大小的塊,也就是最先匹配原則(first fit),從中分裂出所需的大小,然后將剩余的插入到空閑鏈中。而釋放就是將其插入到空閑鏈中。
需要注意的是,插入空閑鏈中,都是升序排列的,也就是說在分配的時候,最先滿足的塊也是最優的塊(best fit),可以減少碎片的產生。從實現來看,heap_2 雖然添加了釋放內存函數,但其在插入到空閑鏈的時候,沒有對相鄰的塊進行合并,所以 heap_2 適用于操作固定大小內存的場景。
heap_3 的方案沒有基于 heap_2 進行優化,而是直接使用 libc 庫中 malloc() / free() 接口,所以這里就不多做介紹。
事實上,相鄰塊合并功能是在 heap_4 中引入的。在將空閑塊插入到鏈表的時候,會判斷是否有空閑塊是相鄰的,如果相鄰就合并成一個更大的空閑塊,就能減少碎片的產生,進而更適用于一般的內存分配和釋放場景。注意 heap_4 在插入空閑鏈的時候,不再是升序排列,而是根據地址大小進行排列,這樣便于判斷量表中前后兩個塊是否相鄰。
heap_5 相對于 heap_4 方案并沒有進行算法上的優化,它添加了一個接口可以指定某個內存塊作為 heap。
void vPortDefineHeapRegions( const HeapRegion_t * const pRegions );
在前幾種內存管理方案中,除了 heap_3,內存塊都是靜態分配的。而在 heap_5 中 heap 不再是靜態定義的全局變量,而是需要顯示指定的一塊內存區域作為 heap。這樣的好處就是,heap 的來源更加靈活,可以是和運行空間不連續的一塊內存,也可以將多個不連續的內存塊作為 heap 進行管理。
以上就介紹了 FreeRTOS 中原生的 5 種內存管理方案。可以看出,后面的內存管理方案對前面的管理方案是兼容的,比如 heap_5 可以替代 heap_1, heap_2 和 heap_4 方案。這里可能就有個疑問了,為什么不直接用 heap_5 方案呢?更豐富的功能,意味著復雜的一些實現,在一些簡單的場景中,heap_5 分配速度可能沒有 heap_1 或者 heap_2 來得快,所以 heap_1,heap_2 等方案也有其存在的意義。
在實際場景中,比如隨機的分配和釋放,而且分配的大小也不一致,這個時候,一般會選擇 heap_4。heap_4 引入了內存合并功能,可以減少內存碎片,但和 heap_2 相比,也把最優匹配的原則去掉了。如下圖所示:
當要分配一個 32Bytes 的內存(經過對齊等處理后的大小),按照 heap_4 的分配方案,最先匹配原則,會從 56Bytes 大小的塊中分配一個32Bytes 出去,而不是從第二個空閑塊,剛剛好是 32Bytes 的塊中分配。這樣的分配方法就會產生碎片。碎片多了就會導致空閑內存看似很多,但大的內存塊已經沒有了。
優化的辦法也很簡單,就是遍歷整個空閑鏈表,找到最優的一個塊,其修改方法就是在原來 first fit 的基礎上,遍歷剩余的鏈表:
/* first fit */
while ((pxBlock- >xBlockSize < xWantedSize) && (pxBlock- >pxNextFreeBlock != NULL))
{
pxPreviousBlock = pxBlock;
pxBlock = pxBlock- >pxNextFreeBlock;
}
/* best fit */
BlockLink_t *pxTmp = pxBlock;
BlockLink_t *pxPreTmp = pxPreviousBlock;while (pxTmp != pxEnd){
if ((pxTmp- >xBlockSize >= xWantedSize) && (pxTmp- >xBlockSize < pxBlock- >xBlockSize))
{
pxBlock = pxTmp;
pxPreviousBlock = pxPreTmp;
}
pxPreTmp = pxTmp;
pxTmp = pxTmp- >pxNextFreeBlock;
}
最優匹配的引入,不是一定就比原來的方案效果要好,因為遍歷整個空閑鏈表,會導致分配內存的時間變長。這在某些對實時要求較高的環境中就不適應了,比如在一些網絡環境中可能會因為超時而導致功能不正常。
其實分配快,內存碎片少的方法有很多,只是他們的實現成本會有所不同,一個算法不可能適用所有的場景,分配方案中也沒有哪個比哪個一定更好,找到適合應用場景的就是好的算法。這也許就是 FreeRTOS 中保留了多種分配方案的原因吧。
-
FreeRTOS
+關注
關注
12文章
484瀏覽量
62229 -
內存管理
+關注
關注
0文章
168瀏覽量
14160 -
嵌入式實時操作系統
+關注
關注
1文章
127瀏覽量
7854
發布評論請先 登錄
相關推薦
評論