前言

本章主要讲解内部存储空间(RAM)的管理。

详细分析heap5方案。

参考:

C标准库的内存管理

C标准库的内存管理用到的API是malloc()free(),但是不建议在RTOS中直接调用,因为:

  1. C标准库的内存管理实现可能比较大,不适合小型嵌入式RAM不足的设备。
  2. 可能会产生内存碎片,对于安全性要求高的嵌入式设备不适合。
  3. 这两个函数会使得链接器配置得复杂。
  4. 待补充。

freertos内存管理接口

freertos的内存管理和内核实现是相互独立的,内核规定内存管理接口,而接口内容却是可由外部自由实现。

但是freertos官方也提供了几种内存分配算法:heap1、heap2、heap3、heap4、heap5。

所以,需要内存管理的有合适的算法可以单独使用freertos提供内存分配算法到自己的设备或系统中。

内存堆大小由宏configTOTAL_HEAP_SIZE决定。(heap3方案除外)

/*
* Map to the memory management routines required for the port.
*/
void * pvPortMalloc( size_t xSize ) PRIVILEGED_FUNCTION; //内存申请函数
void vPortFree( void * pv ) PRIVILEGED_FUNCTION; //内存释放函数
void vPortInitialiseBlocks( void ) PRIVILEGED_FUNCTION; //初始化内存堆函数
size_t xPortGetFreeHeapSize( void ) PRIVILEGED_FUNCTION; //获取当前未分配的内存堆大小
size_t xPortGetMinimumEverFreeHeapSize( void ) PRIVILEGED_FUNCTION; //获取未分配的内存堆历史最小值

freertos五种内存管理

简单介绍。

heap1

特点:

  1. 只能申请不能释放。
  2. 不会产生内存碎片。
  3. 函数的执行时间是确定的。因为可直接查到空闲空间地址和大小。

应用:这种方案一般用在安全性要求较高的系统中。用于从不删除任务、队列、信号量、互斥量等的应用程序。

实现:

使用xNextFreeByte来定位下一个空闲的内存堆位置。

因为freertos系统堆是一个大数组,所以,内存空间是连续的。

所以xNextFreeByte值在heap1方案中实际保存的是已经被分配的内存大小,下次申请时跳过这些已申请的,剩下就是空闲空间。

pucAlignedHeap是一个指向对齐后的内存堆起始地址。

用户提供的系统堆内存的起始地址不一定是对齐的内存地址,需要纠正,纠正后的系统堆内存起始地址保存在pucAlignedHeap

static size_t xNextFreeByte = ( size_t ) 0;
static uint8_t *pucAlignedHeap = NULL;

API注意:

  1. vPortInitialiseBlocks()仅仅将静态局部变量xNextFreeByte设置为0,表示内存没有被申请。
  2. xPortGetFreeHeapSize()并不是释放内存,因为heap1方案不支持释放,所以该API是获取当前未分配的内存堆大小。

heap2

特点:

  1. 支持动态申请和释放。
  2. 采用最佳匹配算法,即是会遍历链表,选择出最小符合用户申请的内存块交给用户。
  3. 链表管理,但是不支持拼接相邻空闲块。所以容易产生内存碎片。
  4. 申请时间具有不确定性,因为检索空闲块需要检索链表,空闲块多、内存碎片多时,检索会久点。但是效率比标准C库中的malloc函数高得多。

应用:不建议用于内存分配和释放是随机大小的应用程序。

实现:

heap2方案的内存管理链表:

typedef struct A_BLOCK_LINK {
struct A_BLOCK_LINK *pxNextFreeBlock;
size_t xBlockSize;
} BlockLink_t;

pxNextFreeBlock:是指向下一个空闲内存块的指针。

xBlockSize:记录当前内存块大小。(内存管理函链表结构体)

heap3

特点:

  1. 需要链接器设置一个堆,malloc()和free()函数由编译器提供。
  2. 具有不确定性。
  3. 很可能增大RTOS内核的代码大小。
  4. configTOTAL_HEAP_SIZE不起作用,因为堆空间由编译器决定提供的。一般在启动文件里设置。

实现:

heap3方案只是简单的封装了标准C库中的malloc()和free()函数。

重新封装后的malloc()和free()函数具有保护功能,采用的封装方式是操作内存前挂起调度器、完成后再恢复调度器。

heap4

和heap2方案类似,且支持相邻空闲块拼接,降低内存块碎片化几率。

特点:

  1. 支持动态申请和释放。
  2. 按地址升序,优先返回第一个满足size需求的空闲块。
  3. 链表管理,支持相邻空闲块拼接。
  4. 申请时间具有不确定性,因为检索空闲块需要检索链表,但是效率比标准C库中的malloc函数高得多。

应用:

  • 可用于重复删除任务、队列、信号量、互斥量等的应用程序。
  • 可用于分配和释放随机字节内存的应用程序。

heap5

heap_5.c方案在实现动态内存分配时与heap4.c方案一样,采用最佳匹配算法和合并算法。

并且允许内存堆跨越多个非连续的内存区。

如可以在片内RAM中定义一个内存堆,还可以在外部SDRAM再定义一个或多个内存堆,这些内存都归系统管理。

heap1、heap2、heap4的堆空间都是有个大数组,拓展下,支持非连续的内存堆,可以使用多个大数组啊。

特点:

  1. 支持动态申请和释放。
  2. 按地址升序,优先返回第一个满足size需求的空闲块。
  3. 链表管理,支持相邻空闲块拼接。
  4. 支持内存堆跨越多个非连续的内存区。
  5. 申请时间具有不确定性,因为检索空闲块需要检索链表,但是效率比标准C库中的malloc函数高得多。

实现:

各块内存堆管理结构体:

typedef struct HeapRegion
{
uint8_t *pucStartAddress; // 内存堆起始地址
size_t xSizeInBytes; // 内存块大小
} HeapRegion_t;

初始化后的内存如下:

heap5内存管理实现细节

实现的内存管理特点如下:

  1. 支持动态申请和动态释放。
  2. 按地址升序,优先返回第一个满足size需求的空闲块。
  3. 支持相邻空闲块拼接。
  4. 支持多个非连续堆空间。
  5. 注意:在创建内核组件前,先调用vPortDefineHeapRegions()完成系统堆初始化。

相关接口

/* Map to the memory management routines required for the port. */

/* 先初始化多个非连续堆空间 */
void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions ) PRIVILEGED_FUNCTION;
/* 内存申请函数 */
void * pvPortMalloc( size_t xSize ) PRIVILEGED_FUNCTION;
/* 内存释放函数 */
void vPortFree( void * pv ) PRIVILEGED_FUNCTION; void vPortInitialiseBlocks( void ) PRIVILEGED_FUNCTION; // 初始化内存堆函数
size_t xPortGetFreeHeapSize( void ) PRIVILEGED_FUNCTION; // 获取当前未分配的内存堆大小
size_t xPortGetMinimumEverFreeHeapSize( void ) PRIVILEGED_FUNCTION; // 获取未分配的内存堆历史最小值
void vPortGetHeapStats( HeapStats_t *xHeapStats ); // 提供堆状态信息

相关参数

xFreeBytesRemaining:表示当前系统中未分配的内存堆大小。

xMinimumEverFreeBytesRemaining:表示未分配内存堆空间历史最小的内存值。为了了解最坏情况下内存堆的使用情况。

xBlockAllocatedBit:系统最高位比特位1。如在32位系统下,该变量值位0x80000000。

  • 通常用法:当当前内存块被占用时,当前内存块size记录值会与该变量按位或,标志当前内存块被占用。

xStart:内存管理块链表头节点,用于指向第一个内存块。

pxEnd:内存管理块链表尾节点指针,用于表示后面没有合法内存空间。

xNumberOfSuccessfulAllocations:申请成功的次数。

xHeapStructSize:某个内存块的管理结构的size,该size也是(向上)字节对齐的。

heapMINIMUM_BLOCK_SIZE:内存块size下限。(内存块管理区+数据区),在heap5方案中是两倍的xHeapStructSize,即是数据区最小也要有一个xHeapStructSize大小。

数据结构

各非连续堆块管理数据结构

各大块堆空间管理数据结构类型HeapRegion_t

  • pucStartAddress:该块堆空间内存起始地址。
  • xSizeInBytes:该块堆空间大小。
typedef struct HeapRegion
{
uint8_t * pucStartAddress;
size_t xSizeInBytes;
} HeapRegion_t;

例如,如果定义两个非连续堆空间:

  • 第一个内存块大小为0x10000字节,起始地址为0x80000000;
  • 第二个内存块大小为0xa0000字节,起始地址为0x90000000。
  • 按照地址顺序放入到数组中,地址小的在前。
  • 该数据结构供给堆空间初始化API vPortDefineHeapRegions()使用。
const HeapRegion_t xHeapRegions[] = {
{ ( uint8_t * ) 0x80000000UL, 0x10000 },
{ ( uint8_t * ) 0x90000000UL, 0xa0000 },
{ NULL, 0 } /* 数组结尾 */
};

内存块管理数据结构

内存块管理数据结构类型BlockLink_t

  • pxNextFreeBlock:是指向下一个空闲内存块的指针。

  • xBlockSize:记录当前内存块大小。(内存管理函链表结构体)

    • 需要特别注意的是,这个内存块大小不仅仅代表当前内存块可用空间,还表示当前内存块是否空闲:该变量最高位为0时,表示空闲,为1时,表示被占用。(参考:heapBITS_PER_BYTE
  • 就是单向链表。

typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK * pxNextFreeBlock; /* 下一个空闲内存块的指针 */
size_t xBlockSize; /* 当前内存块大小 */
} BlockLink_t

主要数据

除了数据结构类型外,管理数据结构还需要一些变量来实现。

链表头xStart

  • 内存块链表头。
static BlockLink_t xStart;

链表尾指针pxEnd

  • 内存块链表尾。
static BlockLink_t *pxEnd = NULL;

初始化堆空间:vPortDefineHeapRegions()

函数原型:

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );
  • pxHeapRegions:传入保存非连续堆空间地址的数据结构地址。

    • HeapRegion_t:各非连续块堆空间管理数据结构类型。

数据校验

初始时内存块链表尾指针为空,表示没有被初始化过,没有被使用过才能重置这些堆块。

configASSERT( pxEnd == NULL );

如果有堆块被初始化过了,内存块链表尾指针不应该为空。

同时,新的堆块地址在旧块后。

/* 初始化非首个堆块 */
if(xDefinedRegions != 0)
{
/* 如果初始化过堆块,肯定有尾节点的 */
configASSERT( pxEnd != NULL ); /* 新的堆块起始地址要在前面堆块尾地址后 */
configASSERT( xAddress > ( size_t ) pxEnd );
}

地址对齐

用户传入的堆块空间始末地址不一定符合地址对齐的,在初始化时需要裁剪,使堆块地址向内对齐。

首地址向上对齐:

if( ( xAddress & portBYTE_ALIGNMENT_MASK ) != 0 ) /* 如果还没对齐,需要实现向上对齐 */
{
/* 先向上漂移到下一个对齐空间 */
xAddress += ( portBYTE_ALIGNMENT - 1 );
/* 去除余数,即是往当前对齐空间下对齐 */
xAddress &= ~portBYTE_ALIGNMENT_MASK; /* 更新对齐后,可用的实际空间。即是减去对齐丢弃的空间 */
xTotalRegionSize -= xAddress - ( size_t ) pxHeapRegion->pucStartAddress;
} /* 对齐后的堆块起始地址 */
xAlignedHeap = xAddress;

同时,在堆块尾部需要预留出尾部链表节点的空间,该空间起始地址也需要符合地址对齐。

头节点:xStart

内存管理块链表的头节点未xStart,该节点只是一个哨兵,不含数据,只指向第一个堆块。

该节点的实际空间并不在堆块内,而内存管理块节点和xpEnd指向的尾节点的空间都是存在堆块内部。

内存管理块

内存管理块数据结构内容参考前面。

刚刚初始化的堆块内存,一整块都是空闲空间,但是这些空间需要内存管理块数据结构来管理。

heap5方案与其它方案不一样,需要支持不连续地址,,其实现是在堆块尾部需要预留一个内存管理块节点空间出来,用于对接下一个堆块。

所以初始化完堆块后,其内部结构如下图所示:

完整代码实现

void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions )
{
BlockLink_t * pxFirstFreeBlockInRegion = NULL, * pxPreviousFreeBlock;
size_t xAlignedHeap;
size_t xTotalRegionSize, xTotalHeapSize = 0;
BaseType_t xDefinedRegions = 0;
size_t xAddress;
const HeapRegion_t * pxHeapRegion; /* 没有被初始化过才能往下执行 */
configASSERT( pxEnd == NULL ); /* 获取第一个堆块的地址 */
pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] ); /* 逐块初始化 */
while( pxHeapRegion->xSizeInBytes > 0 )
{
/* 记录当前堆块空间大小 */
xTotalRegionSize = pxHeapRegion->xSizeInBytes; /* 确保各堆块起始地址符合对齐要求 */
xAddress = ( size_t ) pxHeapRegion->pucStartAddress; if( ( xAddress & portBYTE_ALIGNMENT_MASK ) != 0 ) /* 如果还没对齐,需要实现向上对齐 */
{
/* 先向上漂移到下一个对齐空间 */
xAddress += ( portBYTE_ALIGNMENT - 1 );
/* 去除余数,即是往当前对齐空间下对齐 */
xAddress &= ~portBYTE_ALIGNMENT_MASK; /* 更新对齐后,可用的实际空间。即是减去对齐丢弃的空间 */
xTotalRegionSize -= xAddress - ( size_t ) pxHeapRegion->pucStartAddress;
} /* 对齐后的堆块起始地址 */
xAlignedHeap = xAddress; if( xDefinedRegions == 0 ) /* 初始化首个堆块 */
{
/* xStart为哨兵节点,不带数据,只带指向首个堆块 */
xStart.pxNextFreeBlock = ( BlockLink_t * ) xAlignedHeap;
xStart.xBlockSize = ( size_t ) 0;
}
else /* 初始化非首个堆块 */
{
/* 如果初始化过堆块,肯定有尾节点的 */
configASSERT( pxEnd != NULL ); /* 新的堆块起始地址要在前面堆块尾地址后 */
configASSERT( xAddress > ( size_t ) pxEnd );
} /* 备份系统堆块尾部节点 */
pxPreviousFreeBlock = pxEnd; /* 在新堆块末留出尾节点空间,用于表示系统堆块空间结束点 */
xAddress = xAlignedHeap + xTotalRegionSize;
xAddress -= xHeapStructSize;
xAddress &= ~portBYTE_ALIGNMENT_MASK;
pxEnd = ( BlockLink_t * ) xAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL; /* 在当前堆块内前部空间作为当前堆块管理数据结构 */
pxFirstFreeBlockInRegion = ( BlockLink_t * ) xAlignedHeap;
/* 当前堆块当前可给用户使用的空间大小。(对比原有,少了首位对齐字节、少了首位内存块管理数据结构空间) */
pxFirstFreeBlockInRegion->xBlockSize = xAddress - ( size_t ) pxFirstFreeBlockInRegion;
/* 当前堆块的下一个堆块指向尾节点,表示当前为最后一个堆块 */
pxFirstFreeBlockInRegion->pxNextFreeBlock = pxEnd; /* 如果当前不是首个初始化的堆块,就需要拼接到前面初始化的堆块中 */
if( pxPreviousFreeBlock != NULL )
{
/* 前一块堆块尾部节点的下一个堆块指向当前堆块 */
pxPreviousFreeBlock->pxNextFreeBlock = pxFirstFreeBlockInRegion;
} /* 记录总堆块可用空间 */
xTotalHeapSize += pxFirstFreeBlockInRegion->xBlockSize; /* 移到下一个需要初始化的堆块 */
xDefinedRegions++;
pxHeapRegion = &( pxHeapRegions[ xDefinedRegions ] );
} /* 初始化未分配内存堆历史最小值。用于了解最坏情况下,内存堆的使用情况。 */
xMinimumEverFreeBytesRemaining = xTotalHeapSize;
/* 当前系统未分配内存堆大小 */
xFreeBytesRemaining = xTotalHeapSize; /* 系统堆必须有空间可用才能在后面被访问 */
configASSERT( xTotalHeapSize ); /* 系统最高位标记为1。
如32为系统时,该值为0x80000000。
用于标记内存块是否空闲。被占用时,将内存块节点内的内存块大小xBlockSize和该值按位或。 */
xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}

内存块插入空闲链表:prvInsertBlockIntoFreeList()

这是一个内部接口函数,用户不会接触到,但是后面的malloc和free的实现会使用到,所以在这里先分析了。

原型

static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert );

检索内存块相邻的方法

插入空闲块是按空闲块地址顺序插入的。

需要找到当前空闲块起始地址比新插入的空闲块起始地址小,且当前空闲块链表指向的下一个空闲块的起始地址比新插入的空闲块起始地址大,新的空闲块就是需要插入到这两中间。

/* 先找到和pxBlockToInsert相邻的前一个空闲块 */
for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
{
/* Nothing to do here, just iterate to the right position. */
}

合并内存块

检查内存块地址连续的方法就是:本块内存起始地址+本块内存大小==下一块内存起始地址

/* 如果前一个空闲块和pxBlockToInsert在地址上是连续的,就和前一块合并 */
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
{
/* ... */
} /* 如果pxBlockToInsert和其下一个空闲块地址连续,就和下一个空闲块合并 */
if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
{
/* ... */
}

完整代码实现

static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert )
{
BlockLink_t * pxIterator;
uint8_t * puc; /* 先找到和pxBlockToInsert相邻的前一个空闲块 */
for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
{
/* Nothing to do here, just iterate to the right position. */
} /* 获取pxBlockToInsert相邻的前一个空闲块的起始地址 */
puc = ( uint8_t * ) pxIterator; /* 如果前一个空闲块和pxBlockToInsert在地址上是连续的,就和前一块合并 */
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
{
/* size先合并 */
pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
/* 内存管理块也统一下 */
pxBlockToInsert = pxIterator;
}
else
{
mtCOVERAGE_TEST_MARKER();
} /* 获取当前需要插入的空闲块。(可能是合并后的,也可能是没有合并的,都是下一个空闲块的前一个空闲块) */
puc = ( uint8_t * ) pxBlockToInsert; /* 如果pxBlockToInsert和其下一个空闲块地址连续,就和下一个空闲块合并 */
if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
{
if( pxIterator->pxNextFreeBlock != pxEnd ) /* 如果pxBlockToInsert的下一个空闲块的下一个空闲节点不是尾节点,便需要合并 */
{
/* 合并size */
pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
/* 更新指向。合并时,pxBlockToInsert指向原有下一个空闲块的下一个空闲块 */
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
}
else /* 如果pxBlockToInsert的下一个空闲块的空闲节点是尾节点 */
{
/* 合并时,不需要扩充size,只需要更新指向即可 */
pxBlockToInsert->pxNextFreeBlock = pxEnd;
}
}
else
{
/* 如果不能合并,就直接插入 */
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
} /* 如果新插入的空闲块没有和前一个空闲块合并,插入新的空闲块后需要更新链表节点指向 */
if( pxIterator != pxBlockToInsert )
{
pxIterator->pxNextFreeBlock = pxBlockToInsert;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}

内存申请:pvPortMalloc()

原型

void * pvPortMalloc( size_t xWantedSize )

xWantedSize:输入用户需要申请的空间大小。

返回:

  • 申请成功后返回用户可用空间的起始地址。
  • 申请失败返回NULL。

参数校验

pxEnd不为空证明该接口对应的堆空间已经被初始化过。

/* 堆块必须被初始化过 */
configASSERT( pxEnd );

xWantedSize该值不能大到顶部标志都被覆盖掉。

/* 需要申请的内存不能大到顶部标志都被覆盖掉。 */
if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
{
/* ... */
}

参数纠正

xWantedSize变量先是用户传入的期望申请的大小,而内部空闲块管理除了给用户使用的数据区外还需要内存块管理区,所以需要添加一个xHeapStructSize

/* 一个内存块除了数据区外,还需要管理区BlockLink_t */
xWantedSize += xHeapStructSize;

还需要字节向上对齐:

  • 向上对齐是size往更大方向对齐。
  • 防止溢出是防止xWantedSize扩大后不能大到覆盖掉顶部标志位。
/* 确保申请的内存大小也符合字节对齐 */
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;
}
}

拆分内存块

如果检索到size符合要求的第一个空闲块,malloc会检查该块除了给用户使用的空间和内存管理块的空间后,剩余的空间是否满足组建新的空闲块,如果满足就进行拆分。

/* 如果当前空闲块满足用户需求后,还剩足够的空间组件另一个空闲块,那就需要拆分 */
if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
{
/* 组建新的空闲块,其起始地址为上一个空闲块地址+xWantedSize偏移就是了 */
pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize ); /* 新的空闲块节点size处理。 */
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
pxBlock->xBlockSize = xWantedSize; /* 新的空闲块插入到空闲链表中 */
prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
}

完整代码实现

void * pvPortMalloc( size_t xWantedSize )
{
BlockLink_t * pxBlock, * pxPreviousBlock, * pxNewBlockLink;
void * pvReturn = NULL; /* 堆块必须被初始化过 */
configASSERT( pxEnd ); vTaskSuspendAll();
{
/* 需要申请的内存不能大到顶部标志都被覆盖掉。 */
if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
{
/* 申请的size大于0,且加上内存管理块后不能溢出 */
if( ( xWantedSize > 0 ) && ( ( xWantedSize + xHeapStructSize ) > xWantedSize ) )
{
/* 一个内存块除了数据区外,还需要管理区BlockLink_t */
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
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
xWantedSize = 0;
} /* 申请的内存占用的size要小于现有空闲size才会进行检索 */
if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
{
/* 从低地址的空闲块开始检索 */
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock; /* 找出能满足xWantedSize大小的空闲块 */
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 )
{
/* 组建新的空闲块,其起始地址为上一个空闲块地址+xWantedSize偏移就是了 */
pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize ); /* 新的空闲块节点size处理。 */
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;
/* 更新全局malloc次数 */
xNumberOfSuccessfulAllocations++;
}
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 /* if ( configUSE_MALLOC_FAILED_HOOK == 1 ) */ return pvReturn;
}

内存释放

原型

void vPortFree( void * pv );

pv:需要释放的内存空间的起始地址。

简要分析

通过传入的地址,偏移后找到该内存块的数据管理结构。

检查传入的地址是否合法,内存块是否符合特征。

在heap5代码中检查内存块是否合法的做法还可以添加一个条件,传入的地址在堆块范围内

检查合法后,把当前内存块回收到空闲链表:

  • 清除被占用位。
  • 把当前空闲块插回空闲链表。

完整代码实现

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 )
{
/* 先消除被占用标志位,这样该变量才是表示当前内存块的size */
pxLink->xBlockSize &= ~xBlockAllocatedBit; vTaskSuspendAll();
{
/* 把当前块回收到内核,更新空闲空间size */
xFreeBytesRemaining += pxLink->xBlockSize;
traceFREE( pv, pxLink->xBlockSize );
/* 把当前块插回空闲链表 */
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
/* 更新记录调用free次数 */
xNumberOfSuccessfulFrees++;
}
( void ) xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}

获取总堆空闲size:xPortGetFreeHeapSize()

size_t xPortGetFreeHeapSize( void )
{
return xFreeBytesRemaining;
}

获取历史申请最小空间值:xPortGetMinimumEverFreeHeapSize()

size_t xPortGetMinimumEverFreeHeapSize( void )
{
return xMinimumEverFreeBytesRemaining;
}

获取堆状态信息

状态信息结构体:

/* 用于从vPortGetHeapStats()传递关于堆的信息. */
typedef struct xHeapStats
{
size_t xAvailableHeapSpaceInBytes; /* The total heap size currently available - this is the sum of all the free blocks, not the largest block that can be allocated. */
size_t xSizeOfLargestFreeBlockInBytes; /* The maximum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
size_t xSizeOfSmallestFreeBlockInBytes; /* The minimum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
size_t xNumberOfFreeBlocks; /* The number of free memory blocks within the heap at the time vPortGetHeapStats() is called. */
size_t xMinimumEverFreeBytesRemaining; /* The minimum amount of total free memory (sum of all free blocks) there has been in the heap since the system booted. */
size_t xNumberOfSuccessfulAllocations; /* The number of calls to pvPortMalloc() that have returned a valid memory block. */
size_t xNumberOfSuccessfulFrees; /* The number of calls to vPortFree() that has successfully freed a block of memory. */
} HeapStats_t;

接口实现:

void vPortGetHeapStats( HeapStats_t * pxHeapStats )
{
BlockLink_t * pxBlock;
size_t xBlocks = 0, xMaxSize = 0, xMinSize = portMAX_DELAY; /* portMAX_DELAY used as a portable way of getting the maximum value. */ vTaskSuspendAll();
{
pxBlock = xStart.pxNextFreeBlock; /* 堆块被初始化过才能进去 */
if( pxBlock != NULL )
{
/* 遍历每一个空闲内存块 */
do
{
/* 记录当前有多少个空闲内存块(也包括非连续堆块末节点的内存块,虽然数据区size为0,但是也记录在内) */
xBlocks++; /* 更新最大块的size */
if( pxBlock->xBlockSize > xMaxSize )
{
xMaxSize = pxBlock->xBlockSize;
} /* 需要注意的是heap5支持多个非连续的堆块,所以非连续堆块末节点值用于指向下一个内存块,其内数据区size为0 */
if( pxBlock->xBlockSize != 0 )
{
/* 更新最小块size */
if( pxBlock->xBlockSize < xMinSize )
{
xMinSize = pxBlock->xBlockSize;
}
} /* 遍历下一个内存块 */
pxBlock = pxBlock->pxNextFreeBlock;
} while( pxBlock != pxEnd );
}
}
( void ) xTaskResumeAll(); /* 收集部分堆状态信息 */
pxHeapStats->xSizeOfLargestFreeBlockInBytes = xMaxSize;
pxHeapStats->xSizeOfSmallestFreeBlockInBytes = xMinSize;
pxHeapStats->xNumberOfFreeBlocks = xBlocks; taskENTER_CRITICAL(); /* 进入临界。维护堆块管理的全局变量的原子性 */
{
/* 收集剩余堆状态信息 */
pxHeapStats->xAvailableHeapSpaceInBytes = xFreeBytesRemaining;
pxHeapStats->xNumberOfSuccessfulAllocations = xNumberOfSuccessfulAllocations;
pxHeapStats->xNumberOfSuccessfulFrees = xNumberOfSuccessfulFrees;
pxHeapStats->xMinimumEverFreeBytesRemaining = xMinimumEverFreeBytesRemaining;
}
taskEXIT_CRITICAL(); /* 退出临界 */
}

【freertos】008-内存管理的更多相关文章

  1. 轻量级操作系统FreeRTOS的内存管理机制(一)

    本文由嵌入式企鹅圈原创团队成员朱衡德(Hunter_Zhu)供稿. 近几年来,FreeRTOS在嵌入式操作系统排行榜中一直位居前列,作为开源的嵌入式操作系统之一,它支持许多不同架构的处理器以及多种编译 ...

  2. FreeRTOS 动态内存管理

    以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节为大家讲解 FreeRTOS 动态内存管理,动态内存管理是 FreeRTOS 非常重要的一项功能,前面 ...

  3. FreeRTOS的内存管理

    FreeRTOS提供了几个内存堆管理方案,有复杂的也有简单的.其中最简单的管理策略也能满足很多应用的要求,比如对安全要求高的应用,这些应用根本不允许动态内存分配的. FreeRTOS也允许你自己实现内 ...

  4. freertos之内存管理

    任务.信号量.邮箱才调度器开始调度之前就应该创建,所以它不可能像裸奔程序那样的函数调用能确定需要多少内存资源,RTOS提供了3种内存管理的方法: 1 方法一:确定性好适合于任务.信号量.队列都不被删除 ...

  5. FreeRTOS内存管理

    简介 Freertos的内存管理分别在heap_1.c,heap_2.c,heap_3.c,heap_4.c,heap_5.c个文件中,选择合适的一种应用于嵌入式项目中即可. 本文的图片中 红色部分B ...

  6. FreeRTOS --(3)内存管理 heap2

    在<FreeRTOS --(2)内存管理 heap1>知道 heap 1 的内存管理其实只是简单的实现了内存对齐的分配策略,heap 2 的实现策略相比 heap 1 稍微复杂一点,不仅仅 ...

  7. FreeRTOS --(6)内存管理 heap5

    转载自https://blog.csdn.net/zhoutaopower/article/details/106748308 FreeRTOS 中的 heap 5 内存管理,相对于 heap 4&l ...

  8. FreeRTOS --(5)内存管理 heap4

    FreeRTOS 中的 heap 4 内存管理,可以算是 heap 2 的增强版本,在 <FreeRTOS --(3)内存管理 heap2>中,我们可以看到,每次内存分配后都会产生一个内存 ...

  9. FreeRTOS --(2)内存管理 heap1

    转载自https://blog.csdn.net/zhoutaopower/article/details/106631237 FreeRTOS 提供了5种内存堆管理方案,分别对应heap1/heap ...

  10. FreeRTOS--堆内存管理

    因为项目需要,最近开始学习FreeRTOS,一开始有些紧张,因为两个星期之前对于FreeRTOS的熟悉度几乎为零,经过对FreeRTOS官网的例子程序的摸索,和项目中问题的解决,遇到了很多熟悉的身影, ...

随机推荐

  1. PCB产业链、材料、工艺流程详解(1)

    PCB知识大全 1.什么是pcb,用来干什么? PCB( Printed Circuit Board),中文名称为印制电路板,又称印刷线路板,是重要的电子部件,是电子元器件的支撑体,是电子元器件电气连 ...

  2. AD18布线技巧

    3. 快乐打孔模式(颜色配置)PCB 设计完成后,补回流孔,需要打开多层,软件设置如下: 设置方法: 转载原文链接未知

  3. 谷歌地图 API 开发之信息窗口

    信息窗口 简介 InfoWindow 在地图上方给定位置的弹出窗口中显示内容(通常为文本或图像).信息窗口具有一个内容区域和一个锥形柄.柄顶部与地图上的某指定位置相连. 通常,您会将信息窗口附加到标记 ...

  4. 玩别人玩剩下的:canvas大雪纷飞

    canvas大雪纷飞 前言:正好业务触及到canvas,看完api顺手写个雪花效果,因为之前看到过很多次这个,主要看思路,想象力好的可以慢慢去创作属于自己的canvas效果 思路: 利用画圆arc() ...

  5. x64 番外篇——保护模式相关

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  6. android的布局xml文件如何添加注释?

    xml布局文件如图添加注释后报错,错误内容如下: 上网查阅xml添加注释的语法规则: XML 中的注释 在 XML 中编写注释的语法与 HTML 的语法很相似: <!--This is a co ...

  7. 根据官方文档使用Visual Studio Code创建代码组件的一些总结

    1.安装组件Visual Studio Code Download Visual Studio Code - Mac, Linux, Windows 2.安装Node.js Download | No ...

  8. intel 82599网卡(ixgbe系列)术语表

    Intel® 82599 10 GbE Controller Datasheet 15.0 Glossary and Acronyms 术语表 缩写 英文解释 中文解释 1 KB A value of ...

  9. Machine Learning 学习笔记 01 Typora、配置OSS、导论

    Typora 安装与使用. Typora插件. OSS图床配置. 机器学习导论. 机器学习的基本思路. 机器学习实操的7个步骤

  10. OpenHarmony 3.1 Release版本发布

    OpenHarmony 3.1 Release 版本概述 当前版本在OpenHarmony 3.1 Beta的基础上,更新支持以下能力: 标准系统基础能力增强 本地基础音视频播放能力.视频硬编解码.相 ...