FreeRTOS 內存管理

FreeRTOS 內存管理(Heap Memory Management)

動態內存分配(Dynamic Memory Allocation)

爲了使FreeRTOS簡單易用, 內核對象並不是靜態分配內存空間,而是動態的。FreeRTOS在每個內核對象被創建時分配空間,在對象被刪除時回收空間。
C語言標準庫中的malloc()和free()是用來分配空間的,但是在某些情況下有些問題,比如說:

  • 在小型嵌入式系統上不一定適用
  • 庫佔用內存太多
  • 線程間不一定安全
  • 分配時間不是確定的(影響實時性)
  • 會被內存碎片影響

  • 在FreeRTOS中,用pvPortMalloc()代替malloc(),用vPortFree()代替free(),這兩個函數都是public函數,可以被應用調用。
    FreeRTOS提供5個例程heap_1.c, heap_2.c, heap_3.c, heap_4.c, heap_5.c,他們的代碼可以在FreeRTOS/Source/portable/MemMang目錄下找到。

下面分別介紹這五個方法:

Heap 1

Heap1 僅僅使用了一個基本的pvPortMalloc(),並沒有使用到vPortFree(),因此Heap1適用於只創建任務不刪除的情況。比如說,有些對安全性要求較高的系統禁止使用動態內存分配,以減少不確定性、內存碎片、分配出錯。Heap1是確定性較高的分配方式,不會產生內存碎片。
通過FreeRTOSConfig.h中的宏定義configTOTAL_HEAP_SIZE,可以設置隊列長度。隊列是靜態創建的,設置較大的size會導致較多的內存消耗,即使隊列並未裝滿。
每一個任務從heap中分配一定空間,作爲TCB(Task control block)和Stack, 如下圖所示:
在這裏插入圖片描述

  • A 代表沒有任務創建時的情況,隊列是空的
  • B 此時有一個任務被創建,隊列中存放這個任務的TCB和Stack
  • C 多個任務創建時的情況

Heap 2

與Heap 1不同,Heap 2應用了一個最好的分配算法並且允許內存被回收。相似的是,由於隊列是靜態創建的,所以會消耗較多內存空間。
比如說,現在Heap中包含三塊空閒內存,分別是5bytes, 25bytes, 100bytes。任務調用pvPortMalloc()函數請求分配20bytes的內存空間。這時,pvPortMalloc()判斷應該使用25bytes的空閒區域,那麼它會先分割這25bytes爲20bytes,5bytes,然後返回20bytes空間首指針。剩餘的5bytes在下次調用pvPortMalloc()時依然是空閒可用的。
這段過程可以用下圖表示:
在這裏插入圖片描述
但是,Heap2並不會合併相鄰內存塊,所以他會造成內存碎片化。
Heap2常被用於需要重複創建和刪除相同任務並且任務的內存大小固定的場景。
在這裏插入圖片描述

Heap 3

Heap3 使用C標準庫的malloc(),free()函數,所以他的Heap大小取決於鏈接器配置,與configTOTAL_HEAP_SIZE無關。
Heap3確保了內存分配與回收時線程的安全,他會在內存分配時掛起FreeRTOS調度器。

Heap 4

與Heap1, 2相似,Heap4也是靜態分配隊列,並且通過configTOTAL_HEAP_SIZE定義大小。但是,Heap4會合並相鄰空閒內存空間,防止內存碎片的風險。如圖所示,
在這裏插入圖片描述
Heap4 適用於需要重複分配不同大小內存塊的場景。

Heap4也支持爲隊列指定開始地址,默認情況下,隊列開始地址是鏈接器自動分配的。也可以通過設置configAPPLICATION_ALLOCATED_HEAP宏定義來手動分配。
在這裏插入圖片描述

Heap 5

Heap5允許在多個、不相鄰的內存塊中分配空間。適用於非連續內存塊的場景。
使用Heap_5時,每個內科對象被調用前必須調用vPortDefineHeapRegions()函數來指定內存的開始地址。
在這裏插入圖片描述

HeapRegion_t

每一個分散的內存空間都被描述爲HeapRegion_t類型,他是一個結構體
在這裏插入圖片描述
注意:
最低的開始地址必須是隊列中的第一個結構體,最高的開始地址必須是隊列中的最後一個結構體。
隊列結尾的結構體的pucStartAddress = NULL

舉例:有一個內存A,分配了三個Block:RAM1, RAM2 ,RAM3
在這裏插入圖片描述
在這裏插入圖片描述
這段代碼並不是一個可用的例子,因爲他分配完了所有的RAM,導致沒有空間容納其他的變量。
當程序進行鏈接時,鏈接器會自動給每個變量分配空間。如圖B所示,鏈接器將變量分配到了RAM1中,而RAM1剩餘的部分和RAM2/3交由Heap5進行分配。但是,這樣會產生一個問題:Heap5定義RAM1_START_ADDRESS起始地址爲0x00010000,而這部分會與鏈接器分配的變量空間重疊,導致嚴重錯誤。

爲了避免以上情況的發生,可以採用如下代碼,會更加簡潔並且可維護。
在這裏插入圖片描述
由於鏈接器到底使用了多少空間我們不便得知,若非空間非常有限,我們可以忽略RAM1這塊空間,Heap5只指定RAM2,RAM3來分配。好處是:

  • 沒必要知道鏈接器佔用空間
  • 鏈接器自動分配HeapRegion_t的地址
  • 即使RAM1被鏈接器佔用,也不會導致重疊
  • 如果ucHeap太大,應用不會工作

Heap 使用上的相關函數 (Heap Related Utility Functions)

The xPortGetFreeHeapSize()

這個函數可以用來優化堆棧大小。
比如說,在所有的內核對象都被創建以後,調用這個函數,返回值n就是剩餘的堆棧大小。這樣我們就可以將configTOTAL_HEAP_SIZE的數值減小n個
在這裏插入圖片描述

The xPortGetMinimumEverFreeHeapSize()

這個函數返回的是,應用從開始運行到當前時刻,發生過的最小的未分配空間的字節大小。這個函數只有當啓用Heap4/5纔有效。
比如說,返回200,代表從應用開始運行到當前,這其中某一時刻僅剩餘200字節的空間了。這個能幫助我們及時調整堆棧大小,避免空間用盡。
在這裏插入圖片描述
在這裏插入圖片描述

Malloc Failed Hook Functions 內存分配失敗的鉤子函數

這個函數需要配置宏定義configUSE_MALLOC_FAILED_HOOK = 1開啓。並且需要定義函數在這裏插入圖片描述
開啓HOOK後,當分配失敗時,pvPortMalloc() 返回 NULL並調用鉤子函數

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章