Grey
全部学习内容汇总: GitHub - GreyZhang/g_FreeRTOS: learning notes about FreeRTOS.
1866_FreeRTOS的存储管理方案heap_4分析
对FreeRTOS的heap_4进行分析拆解,按照文学式编程的方式重新组织成个人笔记。
主题由来介绍
free以及malloc这样的存储释放以及申请分配机制是很多算法设计实现的基础。 而嵌入式软件中这方面的使用总是有一些局限性,因此能够看到很多种不同的实施方案。 之前在使用FreeRTOS的时候注意到过这个OS中提供了多种方案的实施选择,而其中的 heap_4的方案有着很多有点也是较为推荐的一种实现方案。因此,这里结合源代码来 分析整理一下。整理的过程中我会把这份代码撕裂成碎片,最终理解后进行重组。这样, 后续的这份笔记以及FreeRTOS的这个实现方案就可以纳入到我的工具箱中使用。
要点细节分析
首先写了自己的 Copyright,同时说明一下FreeRTOS中支持的几种heap。关于使用建议,目前来看较为建议的是heap_4.c。 这个在下面提到的链接中可以看到说明。
文件的头部描述
/*
* FreeRTOS Kernel V10.4.3 LTS Patch 2
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* https://www.FreeRTOS.org
* https://github.com/FreeRTOS
*
*/
/*
* A sample implementation of pvPortMalloc() and vPortFree() that combines
* (coalescences) adjacent memory blocks as they are freed, and in so doing
* limits memory fragmentation.
*
* See heap_1.c, heap_2.c and heap_3.c for alternative implementations, and the
* memory management pages of https://www.FreeRTOS.org for more information.
*/
以上是官网相关的文档的说明,可以能够看出几个不同方案的差异点。
处理包含的头文件
判断使用本文件的合理性
如果系统配置中不支持动态的存储分配,那么就不应该使用这个文件。如果违反了应该报错。
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 0 )
#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0
#endif
相关参数配置
最小块的大小设置原因:
假设每一个字节有8bit,这个似乎是比较常规的一个定义。
/* 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 )
heap存储的分配
有两种分配方式,要么是在应用中定义,要么是在这个文件中定义。从分配机制的研究角度 来说没有什么值得区分的,只需要按照在本文件中定义来理解即可。
/* 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
PRIVILEGED_DATA static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */
空余内存的链表追踪结构定义
链表是一个单向的链表,指向下一个空余的存储块。同时,数据结构标注了这个存储块的大小。 从注释的信息看,其实这个链表在处理的时候应该会有一个根据地址进行排序的过程。
/* Define the linked list structure. This is used to link free blocks in order
* of their memory address. */
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;
内存的释放接口设计
内存释放的同时,需要对剩余内存进行合并处理。如果这一块内容的前面或者后面也是 空余内存,那么应该对其进行合并。
/*-----------------------------------------------------------*/
/*
* Inserts a block of memory that is being freed into the correct position in
* the list of free memory blocks. The block being freed will be merged with
* the block in front it and/or the block behind it if the memory blocks are
* adjacent to each other.
*/
static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert ) PRIVILEGED_FUNCTION;
初始化的处理接口设计
这个会设计成一个隐式调用的方式,在使用相关的API的时候如果发现没有进行过初始 化那么就会对此进行调用。
/*
* Called automatically to setup the required heap structures the first time
* pvPortMalloc() is called.
*/
static void prvHeapInit( void ) PRIVILEGED_FUNCTION;
堆中存储块结构体存储大小判断
这是一个固定值,应该按照链表记录节点的数据结构所占空间大小来分配。然而,得同时满足存储的对齐要求。 这一段代码中的括号比较多,使用emacs等具有括号配对判断的工具可以一下子看清楚处理的结构。其实是两 个表达式进行了&操作。前面计算了链表记录节点所占用的存储空间,之后加了一个对齐值之后减一。接下来 的&操作其实就是一个取整的过程。正好是完成了一个存储对齐补充的过程。
/*-----------------------------------------------------------*/
/* The size of the structure placed at the beginning of each allocated memory
* block must by correctly byte aligned. */
static const size_t xHeapStructSize = ( sizeof( BlockLink_t ) + ( ( size_t ) ( portBYTE_ALIGNMENT - 1 ) ) ) & ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
创建链表的存储位置以及追踪指针
/* Create a couple of list links to mark the start and end of the list. */
PRIVILEGED_DATA static BlockLink_t xStart, * pxEnd = NULL;
存储的分配以及释放回收记录
/* Keeps track of the number of calls to allocate and free memory as well as the
* number of free bytes remaining, but says nothing about fragmentation. */
PRIVILEGED_DATA static size_t xFreeBytesRemaining = 0U;
PRIVILEGED_DATA static size_t xMinimumEverFreeBytesRemaining = 0U;
PRIVILEGED_DATA static size_t xNumberOfSuccessfulAllocations = 0;
PRIVILEGED_DATA static size_t xNumberOfSuccessfulFrees = 0;
获取一个存储块分配的链表结构中存储占用标志
链表结构中有一个代表块大小的成员,这个成员的最高位用来表征当前的存储块是属于 应用程序的还是已经释放的。这个状态在软件中用一个变量来获取。
/* Gets set to the top bit of an size_t type. When this bit in the xBlockSize
* member of an BlockLink_t structure is set then the block belongs to the
* application. When the bit is free the block is still part of the free heap
* space. */
PRIVILEGED_DATA static size_t xBlockAllocatedBit = 0;
实现malloc的接口
传入的参数为申请的内存的字节数目,如果成功则返回一个指向新分配的存储区开始的 指针。这个指针应该是满足系统要求的对齐要求。如果失败,则返回NULL。可以根据配 置选择在分配失败的时候是否调用失败的hook函数。
/*-----------------------------------------------------------*/
void * pvPortMalloc( size_t xWantedSize )
{
/* local_variables_for_malloc */
<
vTaskSuspendAll();
/* malloc_memmory_allocation */
<
( void ) xTaskResumeAll();
/* call_failed_hook_function_when_configed */
<
/* check_malloc_pointer_alignment */
<
return pvReturn;
}
实现free接口
free的实现其实比较简单,只是把对应的存储标记信息修改一下然后加入到free memory 的链表之中。不过,安全起见,增加一些必要的检查。
/*-----------------------------------------------------------*/
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 ) );
xNumberOfSuccessfulFrees++;
}
( void ) xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
初始化以及状态查询接口实现
这部分设计的实现是比较简单的,基本上都是变量的信息查询。而初始化是不必要的, 因为在malloc的实现中借助于链表初始化的状态信息来判断了是否有初始化的必要, 以此实现了初始化的自动调用。
/*-----------------------------------------------------------*/
size_t xPortGetFreeHeapSize( void )
{
return xFreeBytesRemaining;
}
/*-----------------------------------------------------------*/
size_t xPortGetMinimumEverFreeHeapSize( void )
{
return xMinimumEverFreeBytesRemaining;
}
/*-----------------------------------------------------------*/
void vPortInitialiseBlocks( void )
{
/* This just exists to keep the linker quiet. */
}
heap的初始化设计
这个初始化主要是初始化链表以及分配的存储之间的关系。
/*-----------------------------------------------------------*/
static void prvHeapInit( void ) /* PRIVILEGED_FUNCTION */
{
/* local variables needed for initialization */
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;
uint8_t * pucAlignedHeap;
BlockLink_t * pxFirstFreeBlock;
/* check_alignment_of_heap_memory */
<
/* heap_memory_linked_list_init */
<
<
}
向空闲存储链表中插入一个内存块
这是一个很容易实现的操作,首先找到插入点,之后修改链表节点的链接属性即可。 由于存储剩余空间的统计都是在申请或者释放内存的时候处理的,因此这里就是一个纯 粹的链表操作。插入点如何找呢?只需要找到第一个next属性大于被插入地址的位置即可。 这里没有等于的可能性,否则是一种错误。因此,虽然条件判断利用了小于而不是小于 等于,其实找到的应该是next第一个大于需要插入的存储块地址的节点。找到之后,会 面临两类3种情况。分别是可以合并,不能合并。其中,可以合并的情况可能是能与前面 合并或者能与后面合并。这两种全都进行了判断处理,因此可以时间两个方向的合并。 至于无法合并的情况,可能是前后位置全都有被分配出去的内存。这种情况下,只修改 链表中的链接关系即可,不需要调整任何大小信息或者进行节点的合并。
/*-----------------------------------------------------------*/
static void prvInsertBlockIntoFreeList( BlockLink_t * pxBlockToInsert ) /* PRIVILEGED_FUNCTION */
{
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统计功能
这不是这一套机制的主要部分,而是用以使用这一套机制的一个分析工具。
代码构建
/* heap_4_c_lang_header */
<
/* heap_4_included_files */
<
/* check_if_use_is_valid */
<
/* pars_def */
<
/* memory_allocation */
<
/* free_memory_linked_list */
<
/* memory_free_and_merge_declaration */
<
/* auto_init */
<
/* heap_struct_memory_size */
<
/* linked_list_and_ref_pointer */
<
/* mem_allocation_and_free_track_states */
<
/* block_allocated_bit */
<
/* freertos_malloc */
<
/* freertos_free */
<
/* init_funcs_and_check_funcs */
<
/* freertos_heap_init */
<
/* insert_a_block_into_free_list */
<
/* heap_status_statistic */
<
小结