内存管理一
内存管理是一个系统基本组成部分,FreeRTOS中大量使用了内存管理,比如创建任务、信号量、队列等会自动从堆中申请内存。用户应用层代码也可以使用FreeRTOS提供的内存管理函数来申请和释放内存
1. 内存管理简介
FreeRTOS创建任务、信号量、队列等的时候有两种内存申请的方法:一种是动态的申请所需的RAM;一种是由用户自行定义所需的RAM(静态申请)
标准C库中的malloc()和free()函数也可以实现动态内存管理,但是其有如下缺陷:
- 在小型嵌入式系统中效率不高
- 占据了相当大的一块代码空间
- 它们几乎都不是安全的
- 具有不确定性,每次执行的时间不同
- 有可能产生内存碎片
- 这两个函数会使得链接器配置得复杂
- 若允许堆空间的生长方向覆盖其他变量占据的内存,会成为 debug 的灾难
内存碎片是指小块的、碎片化的内存。内存碎片是伴随着内存申请和释放而来的,如下图所示
FreeRTOS使用pvPortMalloc()函数来替代malloc()申请内存,使用vPortFree()函数来替代 free()释放内存
不同的嵌入式系统对于内存分配和时间要求不同,内存分配算法可作为系统的可选选项,FreeRTOS使用者就可以使用适合的内存分配方法。FreeRTOS提供了5中内存分配方法,这5种方法分别对应heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c这五个文件。下面将会详见介绍这五种方法的区别
2. heap_1内存分配方法
2.1 分配方法介绍
动态内存分配需要一个内存堆,不管哪种分配方法,FreeRTOS中的内存堆都为uxHeap[],大小为configTOTAL_HEAP_SIZE,其在heap_x.c(x为1~5)中定义
#if (configAPPLICATION_ALLCATED_HEAP == 1)
extern uint8_t ucHeap[configTOTAL_HEAP_SIZE];
#else
static uint8_t ucHeap[configTOTAL_HEAP_SIZE];
|
heap_1内存分配的特点如下:
- 适用于创建好任务、信号量和队列就不会删除的应用
- 具有可确定性,不会导致内存碎片
- 代码实现和内存分配过程简单,内存是从一个静态数字中分配,适合于不需要动态内存分配的应用
2.2 内存申请函数
heap_1的内存申请函数pvPortMalloc()源码如下:
void *pvPortMalloc( size_t xWantedSize ){
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL;
#if( portBYTE_ALIGNMENT != 1 )
{
if( xWantedSize & portBYTE_ALIGNMENT_MASK ){
xWantedSize += (portBYTE_ALIGNMENT-(xWantedSize&portBYTE_ALIGNMENT_MASK));
}
}
#endif
vTaskSuspendAll();
{
if( pucAlignedHeap == NULL ){
pucAlignedHeap = (uint8_t *)(((portPOINTER_SIZE_TYPE) &ucHeap[portBYTE_ALIGNMENT]) & (~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK)));
}
if(((xNextFreeByte + xWantedSize) < configADJUSTED_HEAP_SIZE) &&((xNextFreeByte + xWantedSize) > xNextFreeByte)){
pvReturn = pucAlignedHeap + xNextFreeByte;
xNextFreeByte += xWantedSize;
}
traceMALLOC( pvReturn, xWantedSize );
}
( void ) xTaskResumeAll();
#if( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL ){
extern void vApplicationMallocFailedHook(void);
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}
|
2.3 内存释放函数
heap_1的内存释放函数vPortFree()源码如下:
void vPortFree( void *pv ){
( void ) pv;
configASSERT( pv == NULL );
}
|
3. heap_2内存分配方法
3.1 分配方法介绍
heap_2提供了内存释放函数,但是缺点是不会把释放的内存块合并成大的内存块,因此随着不断的申请释放内存,内存堆就会被分为多个大小不一的内存块,也就是会导致内存碎片 内存块:为了实现内存释放,heap_2引入了内存块概念,每分出去一段内存就是一个内存块,剩下的空闲内存也是一个内存块,内存块大小不定。使用链表结构来管理内存块
typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK *pxNextFreeBlock;
size_t xBlockSize;
} BlockLink_t;
|
heap_2内存分配的特点如下:
- 可使用在可能会重复的删除任务、队列、信号量等的应用中
- 若分配和释放的内存大小是随机的,不建议使用该分配方法
- 具有不可确定性,但仍比标准C中的malloc()和free()效率高
3.2 内存堆初始化函数
内存堆初始化函数prvHeapInit( )源码如下:
static void prvHeapInit( void ){
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
pucAlignedHeap=(uint8_t *)(((portPOINTER_SIZE_TYPE)&ucHeap[portBYTE_ALIGNMENT])\
&(~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK)));
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
xStart.xBlockSize = ( size_t ) 0;
xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
xEnd.pxNextFreeBlock = NULL;
pxFirstFreeBlock = ( void * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}
|
初始化后的内存堆
3.3 内存块插入函数
heap_2允许内存释放,可用使用内存块插入函数将释放的内存添加到内存链表中
#define prvInsertBlockIntoFreeList( pxBlockToInsert ){
BlockLink_t *pxIterator;
size_t xBlockSize;
xBlockSize = pxBlockToInsert->xBlockSize;
for(pxIterator=&xStart;pxIterator->pxNextFreeBlock->xBlockSize<xBlockSize;\
pxIterator = pxIterator->pxNextFreeBlock){
}
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
pxIterator->pxNextFreeBlock = pxBlockToInsert;
}
|
3.4 内存申请函数
内存申请函数源码如下
void *pvPortMalloc( size_t xWantedSize ){
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
static BaseType_t xHeapHasBeenInitialised = pdFALSE;
void *pvReturn = NULL;
vTaskSuspendAll();
{
if( xHeapHasBeenInitialised == pdFALSE ){
prvHeapInit();
xHeapHasBeenInitialised = pdTRUE;
}
if( xWantedSize > 0 ){
xWantedSize += heapSTRUCT_SIZE;
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 ){
xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize&portBYTE_ALIGNMENT_MASK));
}
}
if((xWantedSize > 0) && (xWantedSize < configADJUSTED_HEAP_SIZE)){
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
while((pxBlock->xBlockSize < xWantedSize)&&(pxBlock->pxNextFreeBlock != NULL)){
pxPreviousBlock = pxBlock;
pxBlock = pxBlock->pxNextFreeBlock;
}
if( pxBlock != &xEnd ){
pvReturn=(void *)(((uint8_t *)pxPreviousBlock->pxNextFreeBlock)+heapSTRUCT_SIZE);
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
if((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE){
pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
pxBlock->xBlockSize = xWantedSize;
prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
}
xFreeBytesRemaining -= pxBlock->xBlockSize;
}
}
traceMALLOC( pvReturn, xWantedSize );
}
(void) xTaskResumeAll();
#if( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL ){
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}
|
3.5 内存释放函数
内存释放函数主要是将需要释放的内存所在的内存块添加到空闲内存块链表中
void vPortFree( void *pv ){
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
if( pv != NULL ){
puc -= heapSTRUCT_SIZE;
pxLink = ( void * ) puc;
vTaskSuspendAll();
{
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
xFreeBytesRemaining += pxLink->xBlockSize;
traceFREE( pv, pxLink->xBlockSize );
}
( void ) xTaskResumeAll();
}
}
|
4. heap_3内存分配方法
4.1 分配方法介绍
heap_3是对标准C中的malloc()和free()的简单封装,并做了线程保护
heap_3内存分配的特点如下:
- 需要编译器提供一个内存堆,编译器库要提供malloc()和free()函数
- 通过修改启动文件中的Heap_Size来修改内存堆的大小(STM32中)
- configTOTAL_HEAP_SZIE不起作用
- 具有不确定性,并且会增加代码量
4.2 内存申请函数
内存申请函数
void *pvPortMalloc( size_t xWantedSize ){
void *pvReturn;
vTaskSuspendAll();
{
pvReturn = malloc( xWantedSize );
traceMALLOC( pvReturn, xWantedSize );
}
( void ) xTaskResumeAll();
#if( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL ){
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}
|
4.3 内存释放函数
内存释放函数
void vPortFree( void *pv ){
if( pv ){
vTaskSuspendAll();
{
free( pv );
traceFREE( pv, 0 );
}
( void ) xTaskResumeAll();
}
}
|
5. heap_4内存分配方法
5.1 分配方法介绍
heap_4提供了一个最优的匹配算法,与heap_2不同,heap_4会将内存碎片合并成一个大的可用内存块
heap_4内存分配的特点如下:
- 可用在需要重复创建和删除任务、队列、信号量等的应用中
- 不会像heap_2那样产生严重的内存碎片
- 具有不确定性,但比标准C中的malloc()和free()效率高
5.2 内存堆初始化函数
内存堆初始化函数
static void prvHeapInit( void ){
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;
uxAddress = ( size_t ) ucHeap;
if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 ){
uxAddress += ( portBYTE_ALIGNMENT - 1 );
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
}
pucAlignedHeap = ( uint8_t * ) uxAddress;
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
xStart.xBlockSize = ( size_t ) 0;
uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
uxAddress -= xHeapStructSize;
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
pxEnd = ( void * ) uxAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL;
pxFirstFreeBlock = ( void * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
pxFirstFreeBlock->pxNextFreeBlock = pxEnd;
xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
xBlockAllocatedBit = ((size_t)1) << ((sizeof(size_t)*heapBITS_PER_BYTE)-1);
}
|
完成初始化后的内存堆
5.3 内存块插入函数
内存块插入函数用来将某个内存块插入到空闲内存块链表中,其源码如下
static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert ){
BlockLink_t *pxIterator;
uint8_t *puc;
for(pxIterator=&xStart;pxIterator->pxNextFreeBlock<pxBlockToInsert;pxIterator=pxIterator->pxNextFreeBlock){
}
puc = ( uint8_t * ) pxIterator;
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert ){
pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
pxBlockToInsert = pxIterator;
}
else{
mtCOVERAGE_TEST_MARKER();
}
puc = ( uint8_t * ) pxBlockToInsert;
if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock ){
if( pxIterator->pxNextFreeBlock != pxEnd ){
pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
}
else{
pxBlockToInsert->pxNextFreeBlock = pxEnd;
}
}
else{
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
}
if( pxIterator != pxBlockToInsert ){
pxIterator->pxNextFreeBlock = pxBlockToInsert;
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
|
5.4 内存申请函数
内存申请函数
void *pvPortMalloc( size_t xWantedSize ){
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;
vTaskSuspendAll();
{
if( pxEnd == NULL ){
prvHeapInit();
}
else{
mtCOVERAGE_TEST_MARKER();
}
if( ( xWantedSize & xBlockAllocatedBit ) == 0 ){
if( xWantedSize > 0 ){
xWantedSize += xHeapStructSize;
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 ){
xWantedSize += (portBYTE_ALIGNMENT-(xWantedSize&portBYTE_ALIGNMENT_MASK));
configASSERT((xWantedSize & portBYTE_ALIGNMENT_MASK) == 0);
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
mtCOVERAGE_TEST_MARKER();
}
if((xWantedSize > 0) && (xWantedSize <= xFreeBytesRemaining)){
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
while((pxBlock->xBlockSize < xWantedSize)&&(pxBlock->pxNextFreeBlock != NULL)){
pxPreviousBlock = pxBlock;
pxBlock = pxBlock->pxNextFreeBlock;
}
if( pxBlock != pxEnd ){
pvReturn = (void *)(((uint8_t *)pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize);
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
if((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE){
pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
configASSERT(((( size_t) pxNewBlockLink) & portBYTE_ALIGNMENT_MASK) == 0);
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
pxBlock->xBlockSize = xWantedSize;
prvInsertBlockIntoFreeList( pxNewBlockLink );
}
else{
mtCOVERAGE_TEST_MARKER();
}
xFreeBytesRemaining -= pxBlock->xBlockSize;
if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining ){
xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
}
else{
mtCOVERAGE_TEST_MARKER();
}
pxBlock->xBlockSize |= xBlockAllocatedBit;
pxBlock->pxNextFreeBlock = NULL;
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
mtCOVERAGE_TEST_MARKER();
}
traceMALLOC( pvReturn, xWantedSize );
}
( void ) xTaskResumeAll();
#if( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL ){
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0);
return pvReturn;
}
|
5.5 内存释放函数
内存释放函数
void vPortFree( void *pv ){
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
if( pv != NULL ){
puc -= xHeapStructSize;
pxLink = ( void * ) puc;
configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
configASSERT( pxLink->pxNextFreeBlock == NULL );
if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 ){
if( pxLink->pxNextFreeBlock == NULL ){
pxLink->xBlockSize &= ~xBlockAllocatedBit;
vTaskSuspendAll();
{
xFreeBytesRemaining += pxLink->xBlockSize;
traceFREE( pv, pxLink->xBlockSize );
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
}
( void ) xTaskResumeAll();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
}
|
6. heap_5内存分配方法
6.1 分配方法介绍
heap_5使用与heap_4相同的合并算法,内存管理基本相同。但是heap_5允许内存堆跨越多个不连续的内存段。比如外接了SRAM的STM32,如果使用heap_4的话就只能在内部RAM和外部SRAM中二选一,若使用heap_5则两个都可以一起作为内存堆来使用 使用heap_5的话,需要在调用API函数之前先调用函数 vPortDefineHeapRegions() 来对内存堆做初始化处理,在内存堆初始化完成之前禁止调用内存申请函数,其函数原型如下:
void vPortDefineHeapRegions( (const HeapRegion_t *) xHeapRegions)
typedef struct HeapRegion
{
uint8_t *pucStartAddress;
size_t xSizeInBytes;
}HeapRegion_t;
|
以STM32F103为例,现有两个内存段:内部SRAM和外部SRAM,起始地址分别为:0x20000000、0x68000000,大小分别为:64KB,1MB,那么数组就如下:
HeapRegion_t xHeapRegions[] =
{
{(uint8_t *)0x20000000UL,0x10000},
{(uint8_t *)0x68000000UL,0x100000},
{NULL,0}
}
|
heap_5的内存申请与释放函数和heap_4的基本一样,可参考heap_4的内存申请与释放函数
|