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