FreeRTOS 之 动态内存管理(heap_1.c)详解

版权声明:进步始于交流,收获源于分享!转载请保留原文出处,谢谢! https://blog.csdn.net/ZCShouCSDN/article/details/84928881

写在前面

  写这篇文章时,网上已经有了铺天盖地的文章来介绍 FreeRTOS 的动态内存。之所以还去写这篇博文,主要还是记录自己的学习过程。结合源代码一步一步分析一下FreeRTOS究竟是怎么实现的其内存部分。

简介

  FreeRTOS 支持 5 种动态内存管理方案,分别通过文件 heap_1.cheap_2.cheap_3.cheap_4.cheap_5.c 实现。这 5 个文件在 FreeRTOS 源码包中的路径是:FreeRTOS\Source\portable\MemMang。具体如下图:
位置
用户在自己的项目中如果要使用 FreeRTOS ,则必须从中以上5中内存方案中选择一种 。5种方案各有各的优势,分别适用于不同的应用场景。
  再具体的实现上,FreeRTOS 内核规定的几个内存管理函数原型。系统内部及用户如果要使用内存,只能通过该函数接口进行申请。因此完全可以有用户自己实现。具体函数接口如下(不同方案稍有区别):

  • void *pvPortMalloc( size_t xSize ) :内存申请函数
  • void vPortFree( void *pv ) :内存释放函数
  • void vPortInitialiseBlocks( void ) :初始化内存堆函数
  • size_t xPortGetFreeHeapSize( void ):获取当前未分配的内存堆大小
  • size_t xPortGetMinimumEverFreeHeapSize( void ):获取未分配的内存堆历史最小值

heap_1.c

  这种内存分配方式最简单直接,速度快程序简单。适用于分配完内存后不需要回收的场合。只允许管理一个静态的数组ucHeap,内存从静态Ram中由系统分配,不能指定管理外部SRAM,或者管理堆中的内存。下面就结合源码,详细来介绍一下heap_1.c

配置

  在 FreeRTOS 的配置文件(FreeRTOSConfig.h)中,关于内存管理部分的配置项主要有以下几个,要使用FreeRTOS 提供的内存实现策略,则必须进行有效的配置:

  • configSUPPORT_STATIC_ALLOCATION
    设置为1,那么可以使用应用程序编写器提供的RAM创建RTOS对象。设置为0,则只能使用从FreeRTOS堆分配的RAM创建RTOS对象。默认(未定义时)为0
  • configSUPPORT_DYNAMIC_ALLOCATION
    设置为1,则可以使用从FreeRTOS堆中自动分配的RAM创建RTOS对象。设置为0,则只能使用应用程序编写器提供的RAM创建RTOS对象。默认(未定义)为1
  • configTOTAL_HEAP_SIZE
    FreeRTOS堆中可用的RAM总量。该值仅在configSUPPORT_DYNAMIC_ALLOCATION设置为 1 且应用程序使用FreeRTOS源代码下载中提供的示例内存分配方案之一时该定义才有效。 有关详细信息,请参阅内存配置部分。
  • configAPPLICATION_ALLOCATED_HEAP
    默认情况下,FreeRTOS堆由FreeRTOS声明,并由链接器放置在内存中。 将configAPPLICATION_ALLOCATED_HEAP设置为1允许应用程序编写器声明堆,这允许应用程序将堆放置在内存中的任何位置。
    如果使用heap_1.c,heap_2.c或heap_4.c,并且configAPPLICATION_ALLOCATED_HEAP设置为1,那么应用程序编写器必须提供一个具有完全名称和维度的uint8_t数组,如下所示:uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
    该数组将用作FreeRTOS堆。 如何将数组放置在特定的内存位置取决于使用的编译器 - 请参阅您的编译器文档。

  从上面的配置不难看出,FreeRTOS给与了用户极大的权限(配置灵活性),甚至允许完全由用户自己实现内存堆的管理。例如,使用使用静态分配方式(版本9.0.0之后)。
  heap_1.c是这是 5 种内存管理方案中最简单的一个,简单到只能申请内存(没有释放)。heap_1.c方案中,内存堆实际上是一个很大的数组,名为:static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];。源码如下:

/* heap_1.h的内存管理方式,只允许管理一个静态的数组ucHeap,内存从静态Ram中由系统分配 */
#if( configSUPPORT_DYNAMIC_ALLOCATION == 0 )
	#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0
#endif

/* 首地址按 portBYTE_ALIGNMENT 对齐后内存容量的大小 */
#define configADJUSTED_HEAP_SIZE	( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )

/* Allocate the memory for the heap. */
#if( configAPPLICATION_ALLOCATED_HEAP == 1 )
	/* 用户自定义静态内存的位置,名称必须为ucHeap. */
	extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
	/* 如果 没有启用用户自定义,则使用默认的 */
	static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */

/* Index into the ucHeap array. 记录已经分配的内存大小(主要用来定位下一个空闲的内存堆位置) */
static size_t xNextFreeByte = ( size_t ) 0;

具体的说明,见上面的注释即可。

内存对齐

  在 portmacro.h (Source/Portable/ + 对应编译器 + 平台 目录下) 的常量 portBYTE_ALIGNMENT 定义了字节对齐,对应的这个变量决定了portable.h 中的一个常量 portBYTE_ALIGNMENT_MASK, 对应关系如下:

portBYTE_ALIGNMENT portBYTE_ALIGNMENT_MASK
32(表示以32个字节对齐) 0x001f
16(表示以16个字节对齐) 0x000f
8(表示以8个字节对齐) 0x0007
4(表示以4个字节对齐) 0x0003
2(表示以2个字节对齐) 0x0001
1(表示以1个字节对齐) 0x0000

至于为什么会考虑内存对齐呢?这个在LwIP的博文中有详细的说明,再次不在赘述!

void *pvPortMalloc( size_t xWantedSize )

这个函数是由 FreeRTOS 规定的内存分配函数。系统内部及用户如果要使用内存,只能通过该函数接口进行申请。

void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn = NULL;
/* 按 portBYTE_ALIGNMENT 对齐后内存首地址。!!!注意这是个静态变量,在第一次使用时会被初始化!!! */
static uint8_t *pucAlignedHeap = NULL;	

	/* 确保指定的静态内存块是 按 portBYTE_ALIGNMENT 对齐的(如果指定的对齐为1字节,就没必要处理了). */
	#if( portBYTE_ALIGNMENT != 1 )
	{
		if( xWantedSize & portBYTE_ALIGNMENT_MASK )	/* 其实就是xWantedSize % portBYTE_ALIGNMENT */
		{
			/* Byte alignment required.将 xWantedSize 按照 portBYTE_ALIGNMENT 强制对齐 */
			xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
		}
	}
	#endif

	vTaskSuspendAll();	/* 挂起所有任务,防止重入 */
	{
		if( pucAlignedHeap == NULL )	/* 静态变量,仅在第一次初始化 */
		{
			/* Ensure the heap starts on a correctly aligned boundary. 这个实现对齐的方式可以参考,很溜! */
			pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
		}

		/* Check there is enough room left for the allocation. 检查剩余空间是否足够,且 没有溢出 */
		if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
			( ( xNextFreeByte + xWantedSize ) > xNextFreeByte )	)/* Check for overflow. */
		{
			/* Return the next free byte then increment the index past this
			block. */
			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;
}

malloc

void vPortFree( void *pv )

  这个函数是由 FreeRTOS 规定的内存释放函数。系统内部及用户如果要释放内存,只能通过该函数接口进行申请。有下面的实现可知,heap_1.c方案,内存一旦申请便无法释放!

void vPortFree( void *pv )
{
	/* Memory cannot be freed using this scheme.  See heap_2.c, heap_3.c and
	heap_4.c for alternative implementations, and the memory management pages of
	http://www.FreeRTOS.org for more information. */
	( void ) pv;

	/* Force an assert as it is invalid to call this function. */
	configASSERT( pv == NULL );
}

void vPortInitialiseBlocks( void )

  这个函数是由 FreeRTOS 规定的内存管理初始化函数。

void vPortInitialiseBlocks( void )
{
	/* Only required when static memory is not cleared. */
	xNextFreeByte = ( size_t ) 0;
}

size_t xPortGetFreeHeapSize( void )

  这个函数是由 FreeRTOS 规定的获取动态内存的剩余大小的函数。唯一需要注意的就是,其大小是在对齐之后的!

size_t xPortGetFreeHeapSize( void )
{
	return ( configADJUSTED_HEAP_SIZE - xNextFreeByte );
}

猜你喜欢

转载自blog.csdn.net/ZCShouCSDN/article/details/84928881