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