FreeRTOS 中的 heap 4 内存管理,可以算是 heap 2 的增强版本,在 《FreeRTOS --(3)内存管理 heap2》中,我们可以看到,每次内存分配后都会产生一个内存块,多次分配后,会产生很多内存碎片,在较为复杂的场景(需要经常动态分配和释放场景)下,几乎是无法胜任;

所以就有了 heap 4,它相比 heap 2 来说,提供了相邻空闲的内存块合并的功能,一定程度上减少了内存碎片,使得释放了的内存能够再度合并称为较为大的内存块,以供有大内存块的分配场景使用;

1、内存大小

和 heap 2 一样,用于内存管理的内存大小来自于一个大数组,数组的下标就是整个需要被管理的内存的大小,这个是和具体芯片所支持的 RAM 大小相关:

configTOTAL_HEAP_SIZE

被管理的内存定义为:

static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

ucHeap 就是管理的对象;

这些基本的结构都是和之前的 heap2 保持一致,不再多说;

2、对齐

对齐的部分也是和 heap 2 一致,不在多说,更多的参考:《FreeRTOS --(3)内存管理 heap2》的对齐章节;

3、内存块

内存块的定义依然是:

typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK *pxNextFreeBlock; /*<< The next free block in the list. */
size_t xBlockSize; /*<< The size of the free block. */
} BlockLink_t;

没有任何差别,依然有 xStart 和 xEnd 来描述两个 marker,和 heap 2 几乎一样《FreeRTOS --(3)内存管理 heap2》;这里不再多说;

4、内存初始化

和 heap 2 一样,内存初始化使用 prvHeapInit,在第一次调用内存分配的时候,检查是否有初始化,否则进行 prvHeapInit 的调用,初始化相关的结构:

static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE; /* Ensure the heap starts on a correctly aligned boundary. */
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 is used to hold a pointer to the first item in the list of free
blocks. The void cast is used to prevent compiler warnings. */
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
xStart.xBlockSize = ( size_t ) 0; /* pxEnd is used to mark the end of the list of free blocks and is inserted
at the end of the heap space. */
uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
uxAddress -= xHeapStructSize;
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
pxEnd = ( void * ) uxAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL; /* To start with there is a single free block that is sized to take up the
entire heap space, minus the space taken by pxEnd. */
pxFirstFreeBlock = ( void * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
pxFirstFreeBlock->pxNextFreeBlock = pxEnd; /* Only one block exists - and it covers the entire usable heap space. */
xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; /* Work out the position of the top bit in a size_t variable. */
xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}

从编码风格的角度上来说,看上去感觉和 heap 2 不是同一个人写的(同样的逻辑,不同的表达),不过没关系,含义都是一样的;

依然是初始化了 xStart 和 xEnd 两个 marker;做了一些对齐处理后,将能够分配的所有的空间一并挂到了 xStart->pxNextFreeBlock 链表;

和 heap 2 不一样的是,heap 4 定义了一个标记,以表示内存是否有被使用,这里定义了 xBlockAllocatedBit;如果是 32bit CPU 的话,这个 xBlockAllocatedBit = 0x01<<31;32 bit 的最高位,这显然是安全的;

5、内存分配

内存分配,还是使用的 pvPortMalloc 接口,正确分配,返回可用的内存地址,出错返回 NULL:

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL; vTaskSuspendAll();
{
/* If this is the first call to malloc then the heap will require
initialisation to setup the list of free blocks. */
if( pxEnd == NULL )
{
prvHeapInit();
}
else
{
mtCOVERAGE_TEST_MARKER();
} /* Check the requested block size is not so large that the top bit is
set. The top bit of the block size member of the BlockLink_t structure
is used to determine who owns the block - the application or the
kernel, so it must be free. */
if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
{
/* The wanted size is increased so it can contain a BlockLink_t
structure in addition to the requested amount of bytes. */
if( xWantedSize > 0 )
{
xWantedSize += xHeapStructSize; /* Ensure that blocks are always aligned to the required number
of bytes. */
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
{
/* Byte alignment required. */
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 ) )
{
/* Traverse the list from the start (lowest address) block until
one of adequate size is found. */
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
{
pxPreviousBlock = pxBlock;
pxBlock = pxBlock->pxNextFreeBlock;
} /* If the end marker was reached then a block of adequate size
was not found. */
if( pxBlock != pxEnd )
{
/* Return the memory space pointed to - jumping over the
BlockLink_t structure at its start. */
pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize ); /* This block is being returned for use so must be taken out
of the list of free blocks. */
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; /* If the block is larger than required it can be split into
two. */
if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
{
/* This block is to be split into two. Create a new
block following the number of bytes requested. The void
cast is used to prevent byte alignment warnings from the
compiler. */
pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 ); /* Calculate the sizes of two blocks split from the
single block. */
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
pxBlock->xBlockSize = xWantedSize; /* Insert the new block into the list of free blocks. */
prvInsertBlockIntoFreeList( pxNewBlockLink );
}
else
{
mtCOVERAGE_TEST_MARKER();
} xFreeBytesRemaining -= pxBlock->xBlockSize; if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
{
xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
}
else
{
mtCOVERAGE_TEST_MARKER();
} /* The block is being returned - it is allocated and owned
by the application and has no "next" block. */
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;
}

内存分配部分的代码和之前的 heap 2 的代码几乎完全一样,这里不做过多的解释;

6、内存释放

内存释放是 vPortFree 接口:

void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink; if( pv != NULL )
{
/* The memory being freed will have an BlockLink_t structure immediately
before it. */
puc -= xHeapStructSize; /* This casting is to keep the compiler from issuing warnings. */
pxLink = ( void * ) puc; /* Check the block is actually allocated. */
configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
configASSERT( pxLink->pxNextFreeBlock == NULL ); if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
{
if( pxLink->pxNextFreeBlock == NULL )
{
/* The block is being returned to the heap - it is no longer
allocated. */
pxLink->xBlockSize &= ~xBlockAllocatedBit; vTaskSuspendAll();
{
/* Add this block to the list of free blocks. */
xFreeBytesRemaining += pxLink->xBlockSize;
traceFREE( pv, pxLink->xBlockSize );
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
}
( void ) xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}

释放的时候,将之前的内存通过调用 prvInsertBlockIntoFreeList 插入到 Free List 中,内存块的合并就体现在这个函数;

6.1、合并

当释放内存的时候调用到了 prvInsertBlockIntoFreeList,它实现了相邻内存块的合并工作,这也是 heap 4 与 heap 2 最大不同的地方:

static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
{
BlockLink_t *pxIterator;
uint8_t *puc; /* Iterate through the list until a block is found that has a higher address
than the block being inserted. */
for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
{
/* Nothing to do here, just iterate to the right position. */
} /* Do the block being inserted, and the block it is being inserted after
make a contiguous block of memory? */
puc = ( uint8_t * ) pxIterator;
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
{
pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
pxBlockToInsert = pxIterator;
}
else
{
mtCOVERAGE_TEST_MARKER();
} /* Do the block being inserted, and the block it is being inserted before
make a contiguous block of memory? */
puc = ( uint8_t * ) pxBlockToInsert;
if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
{
if( pxIterator->pxNextFreeBlock != pxEnd )
{
/* Form one big block from the two blocks. */
pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
}
else
{
pxBlockToInsert->pxNextFreeBlock = pxEnd;
}
}
else
{
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
} /* If the block being inserted plugged a gab, so was merged with the block
before and the block after, then it's pxNextFreeBlock pointer will have
already been set, and should not be set here as that would make it point
to itself. */
if( pxIterator != pxBlockToInsert )
{
pxIterator->pxNextFreeBlock = pxBlockToInsert;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}

在 heap 2中,也有这个函数,但是它是按照内存块的小到大进行排序,由于有相邻内存块合并的要求,所以在 heap 4 中,内存块链表的组织形式,是按照他们的地址顺序由小到大组织的;

首先通过 xStart 开始,获取第一个空闲块的地址,并比较它的下一个空闲块地址和待插入空闲块地址的大小,以便获得合适的插入位置;

获得合适的位置了以后,判断待插入的这个空闲块是否和前一个空闲块相邻,如果相邻的话,就将两个块合并到一起:

/* Do the block being inserted, and the block it is being inserted after
make a contiguous block of memory? */
puc = ( uint8_t * ) pxIterator;
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
{
pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
pxBlockToInsert = pxIterator;
}
else
{
mtCOVERAGE_TEST_MARKER();
}

然后在判断是否和后一个空闲块也相邻,如果相邻,也将他们合并,当然这里需要判断是否是紧挨着 xEnd:

/* Do the block being inserted, and the block it is being inserted before
make a contiguous block of memory? */
puc = ( uint8_t * ) pxBlockToInsert;
if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
{
if( pxIterator->pxNextFreeBlock != pxEnd )
{
/* Form one big block from the two blocks. */
pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
}
else
{
pxBlockToInsert->pxNextFreeBlock = pxEnd;
}
}
else
{
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
}

打个比方:

多次分配和释放后,内存布局如下所示:

如果要释放中间那个内存,那么就会触发向上和向下的合并:

官方针对这个 heap 4 的官图为:

最新文章

  1. EXCEL 对比数据是否重复
  2. CMD打包文件,解压文件
  3. POJ3659 [usaco2008jan_gold]电话网络
  4. 二叉查找树(binary search tree)详解
  5. FTP上传类
  6. Sqli-labs less 39
  7. 基础知识 - Golang 中的正则表达式
  8. 【HDOJ】3367 Pseudoforest
  9. HDU 3571 N-dimensional Sphere
  10. 如何将linux用在开发环境中的
  11. 高性能nosql ledisdb设计与实现 (2):replication
  12. LDA和PCA
  13. ResDrawableImgUtil【根据图片名称获取resID值或者Bitmap对象】
  14. https进行配置以及http跳转到https配置
  15. ssh项目问题01,为创建数据库抛出的异常
  16. Laravel Cache 缓存钉钉微应用的 Access Token
  17. U盘安装CentOS 7卡住在 mounting configuration file system
  18. 【Hive学习之二】Hive SQL
  19. Cascade Classifier Training 没有基础也会目标检测啦
  20. [转载]必须Mark!最佳HTML5应用开发工具推荐

热门文章

  1. Oracle入门基础(四)一一多行函数
  2. 什么是 Daemon 线程?它有什么意义?
  3. redis支持哪些数据类型?redis命令大全
  4. java-web中的Filter&amp;Listener
  5. Linux分区问题
  6. 学习FastDfs(四)
  7. 爬虫-ip代理
  8. css技术之用最高和最宽的限制“max-height和max-width”做图片同比例缩放,达到图片不变形目的,做出批量打印图片功能,页面打印“window.print()”
  9. MOS管驱动电路,看这里就啥都懂了
  10. 002.MEMS应用在开关电源上,实现大功率超小型化