在裸机下使用heap4进行内存管理
内存管理方式
freertos目前提供了以下5种内存管理,特点如下
heap1:最简单的内存管理,本质是一个大数组。
heap2.:比1多了内存释放,分配也因为有内存释放原因多了一个最佳适配算法,不过在内存释放后没有空闲块合并的功能,只适合信号量队列任务等大小固定的内存分配,随机大小的内存分配释放会因为内存碎片问题而无法进行。后面heap4是heap2的优化版。
heap3:简单封装标准C库mallco和free,提供线程安全。
heap4:一般来说FREERTOS五种堆管理里这个是最常见使用的了,首次适配算法,heap2的优化版,多了相邻内存碎片合并功能。
heap5:比heap4多了不连续地址多段内存管理,分配释放代码一样。
heap4 解析
内存分配区域:静态全局变量,位于静态区,大小由FreeRTOSConfig.h 文件里的宏定义控制
#define configTOTAL_HEAP_SIZE ((size_t)(10*1024))
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
当然我们也可以用__attribute__ ((section(“.ccmram”)));将其分配到别的ram区
static uint8_t ucHeap[configTOTAL_HEAP_SIZE] __attribute__ ((section(".ccmram")));
如何管理
freertos基本只对空闲块做管理,heap4也一样,空闲块做链表管理,并且链表排序内存地址从小到大管理,上文也说到,是首次适应算法,这么做主要便于内存空闲块合并。
分配:从空闲块找第一个大小合适的空闲块,空闲块大小减去需要的内存扔满足最小空闲块要求,该空闲块把剩余内存拆分出来做新的一个空闲块插入继续按地址排列继续插入内存空闲链表中。
释放:根据释放首地址,释放对应内存块,将内存块按地址插入到空闲块链表里,如果空闲块与相邻内存块地址连续,则合并为一个大空闲内存块
源码解析
定义基本变量,如:堆的大小,8字节对齐,空闲内存块结构体,空闲块历史最小值,内存块是否分配bit等
#define configTOTAL_HEAP_SIZE (10 * 1024)
#ifndef NULL
#define NULL ((void *) 0)
#endif
typedef unsigned int size_t;
typedef unsigned char uint8_t;
#define portBYTE_ALIGNMENT 8
#if portBYTE_ALIGNMENT == 8
#define portBYTE_ALIGNMENT_MASK (0x0007)
#endif
/* Block sizes must not get too small. */
#define heapMINIMUM_BLOCK_SIZE ((size_t) (xHeapStructSize << 1))
/* Assumes 8bit bytes! */
#define heapBITS_PER_BYTE ((size_t) 8)
static uint8_t ucHeap[configTOTAL_HEAP_SIZE];
typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK *pxNextFreeBlock; /*<< 下一个内存块 */
size_t xBlockSize; /*<< 内存块大小 */
} BlockLink_t;
static void prvInsertBlockIntoFreeList(BlockLink_t *pxBlockToInsert);
static void prvHeapInit(void);
static const size_t xHeapStructSize = (sizeof(BlockLink_t) + ((size_t) (portBYTE_ALIGNMENT - 1))) & ~((size_t) portBYTE_ALIGNMENT_MASK);
static BlockLink_t xStart, *pxEnd = NULL;
static size_t xFreeBytesRemaining = 0U;//当前空闲字节
static size_t xMinimumEverFreeBytesRemaining = 0U;//统计堆空闲历史最小值
static size_t xNumberOfSuccessfulAllocations = 0;//成功分配
static size_t xNumberOfSuccessfulFrees = 0;
static size_t xBlockAllocatedBit = 0;//内存块是否分配
初始化空闲块指针和变量
static void prvHeapInit( void ) //堆初始化,不需要用户调用,第一次pvPortMalloc()会执行该函数
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;
/* 8字节对齐,因为AAPCS规则要求堆栈8字节对齐 判断&(0111)是否为0*/
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 存储空闲节点连表首节点*/
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
xStart.xBlockSize = ( size_t ) 0;
/* pxEnd 和xStart不同,pxEND是一个指针指向堆空闲末尾,标记空闲块链表的结束*/
uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
uxAddress -= xHeapStructSize;
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
pxEnd = ( void * ) uxAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL;
/* 第一个空闲块指针,指向堆空间字节对齐后的第一个节点,
并且此空闲块的下一个节点是pxend,至此空闲块成链*/
pxFirstFreeBlock = ( void * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
pxFirstFreeBlock->pxNextFreeBlock = pxEnd;
/* 一个统计堆空闲历史最小值,一个显示当前堆空闲字节*/
xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
/* 代表内存快是否分配的bit */
xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}
申请内存
void *pvPortMalloc(size_t xWantedSize)
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;
// vTaskSuspendAll();//在裸机上使用不需要关闭其他线程
{
if (pxEnd == NULL)//末尾节点为null代表未初始化,第一次分配内存需要先对堆空间初始化
{
prvHeapInit();
}
/* 检查内存块是否分配的bit,高一位代表分配标记位,所有管理的的内存有最大值,申请的不能大于最大值 */
if ((xWantedSize & xBlockAllocatedBit) == 0)
{
/* The wanted size must be increased so it can contain a BlockLink_t
* structure in addition to the requested amount of bytes. */
if ((xWantedSize > 0) && ((xWantedSize + xHeapStructSize) > xWantedSize)) /* Overflow check */
{
xWantedSize += xHeapStructSize;//分配内存是以块的形式分配,每次增加一块
/*控制字节对齐,保证分配内存字节对齐 */
if ((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00)
{
/* 检查是否溢出 */
if ((xWantedSize + (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK)))
> xWantedSize)
{
xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK));
}
else
{
xWantedSize = 0;
}
}
}
else
{
xWantedSize = 0;
}
//剩余内存够才分配
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)
{
/* pxPreviousBlock->pxNextFreeBlock为空闲块地址,真正返回地址的是块节
点信息之后的数组 */
pvReturn = (void *) (((uint8_t *) pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize);
/* 节点从空闲表去除 */
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;
if (xFreeBytesRemaining < xMinimumEverFreeBytesRemaining)
{
xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
}
/*分配块高一位置为标记以分配*/
pxBlock->xBlockSize |= xBlockAllocatedBit;
pxBlock->pxNextFreeBlock = NULL;
xNumberOfSuccessfulAllocations++;
}
}
}
}
// ( void ) xTaskResumeAll();
return pvReturn;
}
内存块释放
void vPortFree(void *pv)
{
uint8_t *puc = (uint8_t *) pv;
BlockLink_t *pxLink;
if (pv != NULL)
{
//内存块释放一个heap结构体
puc -= xHeapStructSize;
pxLink = (void *) puc;
//确保该块已经分配,才能进行释放操作
if ((pxLink->xBlockSize & xBlockAllocatedBit) != 0)
{
if (pxLink->pxNextFreeBlock == NULL)
{
/*取消最高位的已用标志位*/
pxLink->xBlockSize &= ~xBlockAllocatedBit;
// vTaskSuspendAll();//裸机不需要暂停
{
/*插入空闲内存块*/
xFreeBytesRemaining += pxLink->xBlockSize;
prvInsertBlockIntoFreeList(((BlockLink_t *) pxLink));
xNumberOfSuccessfulFrees++;
}
// ( void ) xTaskResumeAll();
}
}
}
}
内存合并
static void prvInsertBlockIntoFreeList(BlockLink_t *pxBlockToInsert) /* PRIVILEGED_FUNCTION */
{
BlockLink_t *pxIterator;
uint8_t *puc;
/* 根据地址遍历,寻找插入点. */
for (pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock)
{
/* Nothing to do here, just iterate to the right position. */
}
/* 判找到插入点后判断是否和前一个空闲块连续,连续则合并 */
puc = (uint8_t *) pxIterator;
if ((puc + pxIterator->xBlockSize) == (uint8_t *) pxBlockToInsert)
{
pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
pxBlockToInsert = pxIterator;
}
/* 判找到插入点后判断是否和后一个空闲块连续,连续则合并 ,pxEnd标记堆空间结束是例外,不允许合并*/
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 (pxIterator != pxBlockToInsert)
{
pxIterator->pxNextFreeBlock = pxBlockToInsert;
}
}