文章目錄
- 教程目錄
- 2.1 為什么要自己實現內存管理
-
2.2 FreeRTOS的5種內存管理方法
- 2.2.1 Heap_1
- 2.2.2 Heap_2
- 2.2.3 Heap_3
- 2.2.4 Heap_4
- 2.2.5 Heap_5
-
2.3 Heap相關的函數
- 2.3.1 pvPortMalloc/vPortFree
- 2.3.2 xPortGetFreeHeapSize
- 2.3.3 xPortGetMinimumEverFreeHeapSize
- 2.3.4 malloc失敗的鉤子函數
?
需要獲取更好閱讀體驗的同學,請訪問我專門設立的站點查看,地址:http://rtos.100ask.net/
教程目錄
本教程連載中,篇章會比較多,為方便同學們閱讀,點擊這里可以查看文章的 目錄列表,目錄列表頁面地址:https://blog.csdn.net/thisway_diy/article/details/121399484
2.1 為什么要自己實現內存管理
后續的章節涉及這些內核對象:task、queue、semaphores和event group等。為了讓FreeRTOS更容易使用,這些內核對象一般都是動態分配:用到時分配,不使用時釋放。使用內存的動態管理功能,簡化了程序設計:不再需要小心翼翼地提前規劃各類對象,簡化API函數的涉及,甚至可以減少內存的使用。
內存的動態管理是C程序的知識范疇,并不屬于FreeRTOS的知識范疇,但是它跟FreeRTOS關系是如此緊密,所以我們先講解它。
在C語言的庫函數中,有mallc、free等函數,但是在FreeRTOS中,它們不適用:
- 不適合用在資源緊缺的嵌入式系統中
- 這些函數的實現過于復雜、占據的代碼空間太大
- 并非線程安全的(thread-safe)
- 運行有不確定性:每次調用這些函數時花費的時間可能都不相同
- 內存碎片化
- 使用不同的編譯器時,需要進行復雜的配置
- 有時候難以調試
注意:我們經常"堆棧"混合著說,其實它們不是同一個東西:
-
堆,heap,就是一塊空閑的內存,需要提供管理函數
- malloc:從堆里劃出一塊空間給程序使用
- free:用完后,再把它標記為"空閑"的,可以再次使用
-
棧,stack,函數調用時局部變量保存在棧中,當前程序的環境也是保存在棧中
- 可以從堆中分配一塊空間用作棧
2.2 FreeRTOS的5種內存管理方法
FreeRTOS中內存管理的接口函數為:pvPortMalloc 、vPortFree,對應于C庫的malloc、free。
文件在FreeRTOS/Source/portable/MemMang
下,它也是放在portable
目錄下,表示你可以提供自己的函數。
源碼中默認提供了5個文件,對應內存管理的5種方法。
參考文章:FreeRTOS說明書吐血整理【適合新手+入門】
文件 | 優點 | 缺點 |
---|---|---|
heap_1.c | 分配簡單,時間確定 | 只分配、不回收 |
heap_2.c | 動態分配、最佳匹配 | 碎片、時間不定 |
heap_3.c | 調用標準庫函數 | 速度慢、時間不定 |
heap_4.c | 相鄰空閑內存可合并 | 可解決碎片問題、時間不定 |
heap_5.c | 在heap_4基礎上支持分隔的內存塊 | 可解決碎片問題、時間不定 |
2.2.1 Heap_1
它只實現了pvPortMalloc,沒有實現vPortFree。
如果你的程序不需要刪除內核對象,那么可以使用heap_1:
- 實現最簡單
- 沒有碎片問題
- 一些要求非常嚴格的系統里,不允許使用動態內存,就可以使用heap_1
它的實現原理很簡單,首先定義一個大數組:
/* Allocate the memory for the heap. */
#if ( configAPPLICATION_ALLOCATED_HEAP == 1 )
/* The application writer has already defined the array used for the RTOS
* heap - probably so it can be placed in a special segment or address. */
extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */
然后,對于pvPortMalloc調用時,從這個數組中分配空間。
FreeRTOS在創建任務時,需要2個內核對象:task control block(TCB)、stack。
使用heap_1時,內存分配過程如下圖所示:
- A:創建任務之前整個數組都是空閑的
- B:創建第1個任務之后,藍色區域被分配出去了
- C:創建3個任務之后的數組使用情況
2.2.2 Heap_2
Heap_2之所以還保留,只是為了兼容以前的代碼。新設計中不再推薦使用Heap_2。建議使用Heap_4來替代Heap_2,更加高效。
Heap_2也是在數組上分配內存,跟Heap_1不一樣的地方在于:
- Heap_2使用最佳匹配算法(best fit)來分配內存
- 它支持vPortFree
最佳匹配算法:
- 假設heap有3塊空閑內存:5字節、25字節、100字節
- pvPortMalloc想申請20字節
- 找出最小的、能滿足pvPortMalloc的內存:25字節
-
把它劃分為20字節、5字節
- 返回這20字節的地址
- 剩下的5字節仍然是空閑狀態,留給后續的pvPortMalloc使用
與Heap_4相比,Heap_2不會合并相鄰的空閑內存,所以Heap_2會導致嚴重的"碎片化"問題。
但是,如果申請、分配內存時大小總是相同的,這類場景下Heap_2沒有碎片化的問題。所以它適合這種場景:頻繁地創建、刪除任務,但是任務的棧大小都是相同的(創建任務時,需要分配TCB和棧,TCB總是一樣的)。
雖然不再推薦使用heap_2,但是它的效率還是遠高于malloc、free。
使用heap_2時,內存分配過程如下圖所示:
- A:創建了3個任務
- B:刪除了一個任務,空閑內存有3部分:頂層的、被刪除任務的TCB空間、被刪除任務的Stack空間
- C:創建了一個新任務,因為TCB、棧大小跟前面被刪除任務的TCB、棧大小一致,所以剛好分配到原來的內存
2.2.3 Heap_3
Heap_3使用標準C庫里的malloc、free函數,所以堆大小由鏈接器的配置決定,配置項configTOTAL_HEAP_SIZE不再起作用。
C庫里的malloc、free函數并非線程安全的,Heap_3中先暫停FreeRTOS的調度器,再去調用這些函數,使用這種方法實現了線程安全。
2.2.4 Heap_4
跟Heap_1、Heap_2一樣,Heap_4也是使用大數組來分配內存。
Heap_4使用首次適應算法(first fit)來分配內存。它還會把相鄰的空閑內存合并為一個更大的空閑內存,這有助于較少內存的碎片問題。
首次適應算法:
- 假設堆中有3塊空閑內存:5字節、200字節、100字節
- pvPortMalloc想申請20字節
- 找出第1個能滿足pvPortMalloc的內存:200字節
-
把它劃分為20字節、180字節
- 返回這20字節的地址
- 剩下的180字節仍然是空閑狀態,留給后續的pvPortMalloc使用
Heap_4會把相鄰空閑內存合并為一個大的空閑內存,可以較少內存的碎片化問題。適用于這種場景:頻繁地分配、釋放不同大小的內存。
Heap_4的使用過程舉例如下:
- A:創建了3個任務
-
B:刪除了一個任務,空閑內存有2部分:
- 頂層的
- 被刪除任務的TCB空間、被刪除任務的Stack空間合并起來的
- C:分配了一個Queue,從第1個空閑塊中分配空間
- D:分配了一個User數據,從Queue之后的空閑塊中分配
- E:釋放的Queue,User前后都有一塊空閑內存
- F:釋放了User數據,User前后的內存、User本身占據的內存,合并為一個大的空閑內存
Heap_4執行的時間是不確定的,但是它的效率高于標準庫的malloc、free。
2.2.5 Heap_5
Heap_5分配內存、釋放內存的算法跟Heap_4是一樣的。
相比于Heap_4,Heap_5并不局限于管理一個大數組:它可以管理多塊、分隔開的內存。
在嵌入式系統中,內存的地址可能并不連續,這種場景下可以使用Heap_5。
既然內存是分隔開的,那么就需要進行初始化:確定這些內存塊在哪、多大:
- 在使用pvPortMalloc之前,必須先指定內存塊的信息
- 使用vPortDefineHeapRegions來指定這些信息
怎么指定一塊內存?使用如下結構體:
typedef struct HeapRegion
{
uint8_t * pucStartAddress; // 起始地址
size_t xSizeInBytes; // 大小
} HeapRegion_t;
怎么指定多塊內存?使用一個HeapRegion_t數組,在這個數組中,低地址在前、高地址在后。
比如:
HeapRegion_t xHeapRegions[] =
{
{ ( uint8_t * ) 0x80000000UL, 0x10000 }, // 起始地址0x80000000,大小0x10000
{ ( uint8_t * ) 0x90000000UL, 0xa0000 }, // 起始地址0x90000000,大小0xa0000
{ NULL, 0 } // 表示數組結束
};
vPortDefineHeapRegions函數原型如下:
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );
把xHeapRegions數組傳給vPortDefineHeapRegions函數,即可初始化Heap_5。
2.3 Heap相關的函數
2.3.1 pvPortMalloc/vPortFree
函數原型:
void * pvPortMalloc( size_t xWantedSize ); // 分配內存,如果分配內存不成功,則返回值為NULL。
void vPortFree( void * pv ); // 釋放內存
作用:分配內存、釋放內存。
如果分配內存不成功,則返回值為NULL。
2.3.2 xPortGetFreeHeapSize
函數原型:
size_t xPortGetFreeHeapSize( void );
當前還有多少空閑內存,這函數可以用來優化內存的使用情況。比如當所有內核對象都分配好后,執行此函數返回2000,那么configTOTAL_HEAP_SIZE就可減小2000。
注意:在heap_3中無法使用。
2.3.3 xPortGetMinimumEverFreeHeapSize
函數原型:
size_t xPortGetMinimumEverFreeHeapSize( void );
返回:程序運行過程中,空閑內存容量的最小值。
注意:只有heap_4、heap_5支持此函數。
2.3.4 malloc失敗的鉤子函數
在pvPortMalloc函數內部:
void * pvPortMalloc( size_t xWantedSize )
{
......
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}
所以,如果想使用這個鉤子函數:
- 在FreeRTOSConfig.h中,把configUSE_MALLOC_FAILED_HOOK定義為1
- 提供vApplicationMallocFailedHook函數
- pvPortMalloc失敗時,才會調用此函數?
評論
查看更多