內存管理
? ? ? ? 我們知道每次創建任務、隊列、互斥鎖、軟件定時器、信號量或事件組時,RTOS 內核都需要 RAM , RAM 可以從 RTOS API 對象創建函數內的 RTOS 堆自動動態分配, 或者由應用程序編寫者提供。
????????如果 RTOS 對象是動態創建的,那么標準 C 庫 malloc() 和 free() 函數有時可用于此目的,但是...它們在嵌入式系統上并不總是可用,占用了寶貴的代碼空間,不是線程安全的,而且不是確定性的 (執行函數所需時間將因調用而異),所以更多的時候需要的不是一個替代的內存分配實現。
????????一個嵌入式/實時系統的 RAM 和定時要求可能與另一個非常不同,所以單一的 RAM 分配算法 將永遠只適用于一個應用程序子集。
????????為了避免此問題,FreeRTOS 將內存分配 API 保留在其可移植層。 可移植層在實現核心 RTOS 功能的源文件之外, 允許提供適合于正在開發的實時系統的特定應用程序實現。 當 RTOS 內核需要 RAM 時,它不調用 malloc(),而是調用 pvPortMalloc()。 釋放 RAM 時, RTOS 內核調用 vPortFree(),而不是 free()。
????????FreeRTOS 提供了幾種堆管理方案, 其復雜性和功能各不相同。 你也可以提供自己的堆實現, 甚至同時使用兩個堆實現。 同時使用兩個堆實現 允許將任務堆棧和其他 RTOS 對象放置在 內部 RAM 中,并將應用程序數據放置在較慢的外部 RAM 中。
5種管理方法
????????源碼中默認提供了5個文件,對應內存管理的5種方法。每個提供的實現都包含在單獨的源文件中 (分別是 heap_1.c、 heap_2.c、heap_3.c、heap_4.c 和 heap_5.c), 位于主 RTOS 源代碼下載內容的 Source/Portable/MemMang 目錄下。 可根據需要添加其他實現方式。 每次一個項目中, 只應包含其中一個源文件[這些可移植層函數定義的堆 將由 RTOS 內核使用, 即使使用 RTOS 的應用程序選擇使用自己的堆實現]。
heap_1 —— 最簡單,不允許釋放內存。
heap_2—— 允許釋放內存,但不會合并相鄰的空閑塊。
heap_3 —— 簡單包裝了標準 malloc() 和 free(),以保證線程安全。
heap_4 —— 合并相鄰的空閑塊以避免碎片化。 包含絕對地址放置選項。
heap_5 —— 如同 heap_4,能夠跨越多個不相鄰內存區域的堆。
注意:
????????heap_1 不太有用,因為 FreeRTOS 添加了靜態分配支持。
????????heap_2 現在被視為舊版,因為較新的 heap_4 實現是首選。
heap_1.c
????????heap_1 不太有用,因為 FreeRTOS 添加了靜態分配支持。heap_1 是最簡單的實現方式。 內存一經分配,它不允許內存再被釋放 。 盡管如此,heap_1.c 還是適用于大量嵌入式應用程序。 這是因為許多小型和深度嵌入的應用程序在系統啟動時 創建了所需的所有任務、隊列、信號量等, 并在程序的生命周期內使用所有這些對象 (直到應用程序再次關閉或重新啟動)。 任何內容 都不會被刪除。
????????這個實現只是在要求使用 RAM 時將一個單一的數組細分為更小的塊 。 數組的總大小(堆的總大小)通過 configTOTAL_HEAP_SIZE (定義于 FreeRTOSConfig.h 中)設置 。 提供了 configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h 配置常量, 以允許將堆放置在內存中的特定地址。
????????xPortGetFreeHeapSize() API 函數返回未分配的堆空間總量,允許優化 configTOTAL_HEAP_SIZE 設置。
heap_1 實現
????????如果您的應用程序從未刪除任務、隊列、信號量、互斥鎖等,則可以使用。 (這實際上涵蓋了使用 FreeRTOS 的大多數應用程序)。
????????始終具有確定性(總是需要相同的時間來執行), 不會導致內存碎片化。非常簡單,且從靜態分配的數組分配內存, 這意味著它通常適合用于不允許真實動態內存分配的應用程序 。
使用heap_1時,內存分配過程如下圖所示:
A:創建任務之前整個數組都是空閑的
B:創建第1個任務之后,藍色區域被分配出去了
C:創建3個任務之后的數組使用情況
heap_2.c
????????heap_2 現在被視為舊版,首選 heap_4 。heap_2 使用最佳適應算法,并且與方案 1 不同,它允許釋放先前分配的塊, 它 不 將相鄰的空閑塊組合成一個大塊。 有關不合并空閑塊的實現,請參閱 heap_4.c。
????????可用堆空間的總量通過 configTOTA L_HEAP_SIZE(定義于 FreeRTOSConfig.h 中)設置。 提供了 configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h 配置常量, 以允許將堆放置在內存中的特定地址。xPortGetFreeHeapSize() API 函數返回未分配的堆空間總量, (允許優化 configTOTAL_HEAP_SIZE 設置), 但不提供關于未分配的內存如何被碎片化成小塊的信息 。
????????pvPortCalloc() 函數的簽名與標準庫 calloc 函數相同。它為一個對象數組分配內存, 并將分配的存儲空間中的所有字節初始化為零。如果分配成功, 它會返回指向分配的內存塊中最低字節的指針。如果分配失敗,它會返回一個空指針。
heap_2實現
????????即使應用程序重復刪除任務、隊列、 信號量、互斥鎖等,仍然可用, 但要注意以下關于內存碎片化的信息。如果正在分配和釋放的內存為隨機大小,則不可使用 。
例如:
????????如果應用程序動態地創建和刪除任務, 且分配給正在創建任務的堆棧大小總是相同的 , 那么 heap2.c 可以在大多數情況下使用。 但是, 如果分配給正在創建任務的堆棧的大小不是總相同, 那么可用的空閑內存可能會被碎片化成許多小塊 , 最終導致分配失敗。 在這種情況下,heap_4.c 是更好的選擇。
????????如果應用程序動態地創建和刪除任務, 且隊列存儲區域在每種情況下都是相同的 (隊列存儲區域是隊列項大小乘以隊列長度), 那么 heap_2.c 可以在大多數情況下使用。 但是, 如果在每種情況下的隊列存儲區域不相同, 那么可用的空閑內存可能會被碎片化成許多小塊 , 最終導致分配失敗。 在這種情況下,heap_4.c 是更好的選擇。
????????應用程序直接調用 pvPortMalloc() 和 vPortFree(), 而不是僅通過其他 FreeRTOS API 函數間接調用。
????????如果您應用程序的隊列、任務、信號量、互斥鎖等的順序不可預測, 可能會導致內存碎片化。 這對幾乎所有的應用程序來說都是不可能的, 但應牢記這一點。非確定性,但比大多數標準 C 庫 malloc 實現更有效。
????????heap_2.c 適用于許多必須動態創建對象的小型實時系統 。 請參閱 heap_4 了解類似實現, 該實現將空閑的內存塊組合成單一的大內存塊。
使用heap_2時,內存分配過程如下圖所示:
A:創建了3個任務
B:刪除了一個任務,空閑內存有3部分:頂層的、被刪除任務的TCB空間、被刪除任務的Stack空間
C:創建了一個新任務,因為TCB、棧大小跟前面被刪除任務的TCB、棧大小一致,所以剛好分配到原來的內存
heap_3.c
????????這是標準 C 庫 malloc() 和 free() 函數實現了簡單的包裝器, 在大多數情況下,將與您選擇的編譯器一起提供。 該 包裝器只是使 malloc() 和 free() 函數線程安全。
heap_3實現
????????需要鏈接器設置堆,需要編譯器庫提供 malloc() 和 free() 實現。不具有確定性,能會大大增加 RTOS 內核代碼大小。
????????請注意,使用 heap_3 時,FreeRTOSConfig.h 中的 configTOTAL_HEAP_SIZE 設置無效 。
heap_4.c
?????????此方案使用第一適應算法,并且與方案 2 不同, 它確實將相鄰的空閑內存塊組成單個大內存塊(它確實包含合并算法) 。
????????可用堆空間的總量通過 configTOTAL_HEAP_SIZE (定義于 FreeRTOSConfig.h 中)設置。 提供了 configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h 配置常量, 以允許將堆放置在內存中的特定地址。
????????xPortGetFreeHeapSize() API 函數被調用時返回未分配的堆空間總量, xPortGetMinimumEverFreeHeapSize() API 函數返回 FreeRTOS 應用程序啟動的系統中已存在的最小空閑堆空間量。 這兩個函數都沒有提供關于未分配的 內存如何碎片化為小塊的信息。
????????vPortGetHeapStats() API 函數提供了其他信息。 它填充了一個 heap_t 結構體的成員,如下所示。
/* Prototype of the vPortGetHeapStats() function. */
void vPortGetHeapStats( HeapStats_t *xHeapStats );/* Definition of the Heap_stats_t structure. */typedef struct xHeapStats
{size_t xAvailableHeapSpaceInBytes;????? /* The total heap size currently available - this is the sum of all the free blocks, not the largest block that can be allocated. */size_t xSizeOfLargestFreeBlockInBytes; ??? /* The maximum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */size_t xSizeOfSmallestFreeBlockInBytes; /* The minimum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */size_t xNumberOfFreeBlocks;??????????? /* The number of free memory blocks within the heap at the time vPortGetHeapStats() is called. */size_t xMinimumEverFreeBytesRemaining; /* The minimum amount of total free memory (sum of all free blocks) there has been in the heap since the system booted. */size_t xNumberOfSuccessfulAllocations;?? /* The number of calls to pvPortMalloc() that have returned a valid memory block. */size_t xNumberOfSuccessfulFrees;???? /* The number of calls to vPortFree() that has successfully freed a block of memory. */
} HeapStats_t;
????????pvPortCalloc() 函數的簽名與標準庫 calloc 函數相同。它為一個對象數組分配內存, 并將分配的存儲空間中的所有字節初始化為零。如果分配成功, 它會返回指向分配的內存塊中最低字節的指針。如果分配失敗,它會返回一個空指針。
heap_4使用
????????即使應用程序重復刪除任務、隊列、 信號量、互斥鎖等,仍然可用。
????????與 heap_2 實現相比,導致堆空間嚴重碎片化成多個小塊的可能性更小 (即使正在分配和釋放的內存是隨機大小) 。不具有確定性,但比大多數標準 C 庫 malloc 實現更有效。
????????heap_4.c 對于想在應用程序代碼中直接使用可移植層內存分配方案的應用程序特別有用 (而不是 通過調用 API 函數 pvPortMalloc() 和 vPortFree() 來間接使用)。
????????Heap_4執行的時間是不確定的,但是它的效率高于標準庫的malloc、free。
heap_5.c
?????????此方案使用與 heap_4 相同的第一擬合和內存合并算法, 允許堆跨越多個不相鄰(非連續) 內存區域。Heap_5 通過調用 vPortDefineHeapRegions() 初始化,且不得使用, 直到 vPortDefineHeapRegions() 執行以后。 創建 RTOS 對象(任務、隊列、信號量等)將隱式調用 pvPortMalloc(),因此使用 heap_5 時,在創建任何此類對象之前調用 vPortDefineHeapRegions() 至關重要。
????????vPortDefineHeapRegions() 采用單一參數, 該參數為 HeapRegion_t 結構體數組。 HeapRegion_t 在 portable.h 中定義為
typedef struct HeapRegion
{/* Start address of a block of memory that will be part of the heap.*/uint8_t *pucStartAddress;/* Size of the block of memory. */size_t xSizeInBytes;
} HeapRegion_t;The HeapRegion_t type definition
????????數組使用空位零大小的區域定義終止, 數組中定義的內存區域必須按地址順序, 從低地址到高地址顯示。 以下源代碼片段提供了示例 。 此外, MSVC Win32 模擬器演示也使用 heap_5 ,因此可作參考。
/* Allocate two blocks of RAM for use by the heap.? The first is a block of
0x10000 bytes starting from address 0x80000000, and the second a block of
0xa0000 bytes starting from address 0x90000000.? The block starting at
0x80000000 has the lower start address so appears in the array fist. */const HeapRegion_t xHeapRegions[] =
{{ ( uint8_t * ) 0x80000000UL, 0x10000 },{ ( uint8_t * ) 0x90000000UL, 0xa0000 },{ NULL, 0 } /* Terminates the array. */
};
/* Pass the array into vPortDefineHeapRegions(). */
vPortDefineHeapRegions( xHeapRegions );Initialising heap_5 after defining the memory blocks to be used by the heap
????????xPortGetFreeHeapSize() API 函數被調用時返回未分配的堆空間總量, xPortGetMinimumEverFreeHeapSize() API 函數返回 FreeRTOS 應用程序啟動的系統中已存在的最小空閑堆空間量。 這兩個函數都沒有提供關于未分配的 內存如何碎片化為小塊的信息。
????????pvPortCalloc() 函數的簽名與標準庫 calloc 函數相同。它為一個對象數組分配內存, 并將分配的存儲空間中的所有字節初始化為零。如果分配成功, 它會返回指向分配的內存塊中最低字節的指針。如果分配失敗,它會返回一個空指針。
????????vPortGetHeapStats() API 函數提供了有關堆狀態的其他信息。
Heap相關的函數
pvPortMalloc/vPortFree
函數原型
void * pvPortMalloc( size_t xWantedSize ); // 分配內存,如果分配內存不成功,則返回值為NULL。void vPortFree( void * pv );? // 釋放內存
????????作用:分配內存、釋放內存。如果分配內存不成功,則返回值為NULL。
xPortGetFreeHeapSize
函數原型
size_t xPortGetFreeHeapSize( void );
????????當前還有多少空閑內存,這函數可以用來優化內存的使用情況。比如當所有內核對象都分配好后,執行此函數返回2000,那么configTOTAL_HEAP_SIZE就可減小2000。
????????注意:在heap_3中無法使用。
xPortGetMinimumEverFreeHeapSize
函數原型
size_t xPortGetMinimumEverFreeHeapSize( void );
????????返回:程序運行過程中,空閑內存容量的最小值。
????????注意:只有heap_4、heap_5支持此函數。
malloc失敗的鉤子函數
void * pvPortMalloc( size_t xWantedSize )
{......#if ( configUSE_MALLOC_FAILED_HOOK == 1 ){if( pvReturn == NULL ){extern void vApplicationMallocFailedHook( void );vApplicationMallocFailedHook();}}#endifreturn pvReturn;???????
}
????????在pvPortMalloc函數內部,所以,如果想使用這個鉤子函數:在FreeRTOSConfig.h中,把configUSE_MALLOC_FAILED_HOOK定義為1。提供vApplicationMallocFailedHook函數
????????pvPortMalloc失敗時,才會調用此函數
常用的管理方法
動態內存分配(Dynamic Memory Allocation)
????????FreeRTOS提供了用于動態內存分配的內置函數,如pvPortMalloc和vPortFree。這些函數允許任務在運行時請求和釋放內存。這種方法對于需要靈活管理內存的應用非常有用,但需要小心避免內存泄漏和碎片化。
靜態內存分配(Static Memory Allocation)
????????FreeRTOS允許用戶在編譯時為任務和內核對象(如隊列、信號量等)分配靜態內存。通過在FreeRTOS配置文件中定義合適的宏,可以將任務的堆棧和內核對象的內存靜態分配。
內存池(Memory Pools)
????????內存池是在系統初始化時創建的一塊內存區域,用于存儲固定大小的內存塊。任務可以從內存池中申請內存塊,并在使用完畢后將其返回給內存池。這有助于減少內存碎片化。
堆棧自動增長(Stack Auto-Grow)
????????FreeRTOS允許任務的堆棧在運行時自動增長,以適應任務運行期間的動態需求。這種方法使得在任務執行時不需要預先知道堆棧的最大深度,但也需要謹慎防止堆棧溢出。
靜態API和動態API的混合使用
????????在FreeRTOS中,可以選擇性地使用靜態API或動態API,根據應用需求選擇適當的方式。這樣可以在一些任務中使用靜態分配,而在另一些任務中使用動態分配,以充分發揮各種方法的優勢。
????????FreeRTOS提供了靈活的內存管理機制,可以根據具體的應用場景選擇合適的方法。使用動態內存分配時需要特別注意內存泄漏和碎片化的問題,而靜態內存分配和內存池則可以在一定程度上減少這些問題。