freeRTOS中heap_1/2/3/4/5.c的对比


参考链接: link.

什么是堆

c语言中有两个概念经常一起被提及,堆和栈。
栈一般是系统调用,用于函数调用中保存现场(局部变量等)。不难想象,函数调用中主函数往往是最先执行,调用其他函数,然后最后返回。而最深层次的函数(内部不调用其他任何函数的函数)往往是执行完就离开,所以使用栈管理函数调用最为合适。
堆则一般是由程序员调用并管理。最直观的,一般malloc函数和free函数都是操作的这个空间,不过freeRTOS中一般则是使用freeRTOS中定义的函数(pvPortMalloc和vPortFree代替malloc和free函数)。

heap_1/2/4.c共有部分

freeRTOS是怎么代替c语言中的malloc和free函数的呢,可以看以下代码,代码的作用是申请一个很大的数组。

#if( configAPPLICATION_ALLOCATED_HEAP == 1 )
	extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
	static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

这里我的理解是,freeRTOS层使用c语言申请内存的方式申请一个很大的数组(此处configTOTAL_HEAP_SIZE 的数值是15360),极端情况下,我们把可以申请到的所有空间全部申请下来。然后用户层调用的(freeRTOS的)pvPortMalloc和vPortFree函数则是在此数组中操作,操作的函数原型自然是freeRTOS函数层中定义的。

heap_1.c

/*
 * The simplest possible implementation of pvPortMalloc().  Note that this
 * implementation does NOT allow allocated memory to be freed again.
 */

以上是heap_1.c开头处的一段注释,此文件最大的特点就是只负责申请内存,不负责内存的释放。所以这里我们只看pvPortMalloc函数。
在实际分配内存之前,函数要先进行一些操作,其中...代表省略的代码(下同)。

void *pvPortMalloc( size_t xWantedSize )
{
   
	...
	#if( portBYTE_ALIGNMENT != 1 )
	{
   
		if( xWantedSize & portBYTE_ALIGNMENT_MASK )
		{
   
			/* Byte alignment required. */
			xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
		}
	}
	#endif
	...

以上代码的作用是将要申请的内存区间扩充到比xWantedSize 大的最小的portBYTE_ALIGNMENT的倍数。此处xWantedSize & portBYTE_ALIGNMENT_MASK执行的操作应该等同于xWantedSize % portBYTE_ALIGNMENT,至于为什么要这么写我猜测是因为位运算要更快一些。
之后还要对申请到的数组进行同样的操作:

pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & \
							  ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );

此处将原先申请到的数组的首地址调整到portBYTE_ALIGNMENT的倍数,不然之前的操作就没有意义了。试想,如果首地址不是portBYTE_ALIGNMENT的整数倍,之后即使按照portBYTE_ALIGNMENT的整数倍申请地址,申请到地址也不是portBYTE_ALIGNMENT的整数倍。
之后才是实际的内存分配的内容:

		//确保1,申请的地址没有超过内存中数组的最大值
		//2,地址没有溢出
		if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
			( ( xNextFreeByte + xWantedSize ) > xNextFreeByte )	)
		{
   
			//返回当前申请到的地址
			pvReturn = pucAlignedHeap + xNextFreeByte;
			xNextFreeByte += xWantedSize;
		}

如果定义了宏configUSE_MALLOC_FAILED_HOOK,内存分配失败时会调用vApplicationMallocFailedHook函数

	#if( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
   
		if( pvReturn == NULL )
		{
   
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
	}
	#endif

至此,heap_1.c中pvPortMalloc函数基本结束。

heap_2.c

/*
 * A sample implementation of pvPortMalloc() and vPortFree() that permits
 * allocated blocks to be freed, but does not combine adjacent free blocks
 * into a single larger block (and so will fragment memory).  See heap_4.c for
 * an equivalent that does combine adjacent blocks into single larger blocks.
 */

相比于heap_1.c中“管杀不管埋”的内存分配,heap_2.c不仅可以申请内存,还可以释放内存。美中不足的是,它不会合并相邻的空闲内存块。不过注释中也说明这一点会在heap_4.c中改进。
heap_2.c使用链表表示内存中的占用(空闲)情况,链表节点通过指针和一个size_t类型的数据确定了整个堆区的空闲内存的分配情况,链表项里的指针指向的是下一个链表项结点。那么如何确定空闲内存的地址呢:用户申请内存后,内存中实际分配的地址有两部分,第一部分是这个链表项,紧挨着链表项的才是用户申请的地址,所以链表项地址加上链表项结点的大小就是用户申请分配的地址。

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

prvHeapInit函数

第一次调用pvPortMalloc函数时,会先执行prvHeapInit函数,目的是初始化上面的链表。

static void prvHeapInit( void )
{
   
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;

	//同heap_1.c中一样,将数组的首位对齐到portPOINTER_SIZE_TYPE 的整数倍
	pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );

	//用于指向链表的首地址(对齐过后的)
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
	xStart.xBlockSize = ( size_t ) 0;

	//标记链表的结尾
	xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
	xEnd.pxNextFreeBlock = NULL;

	//刚开始,只有一个空闲的内存块,其大小是整个内存空间
	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
	pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
	pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}

pvPortMalloc函数

同heap_1.c,每次申请内存都要将要申请的内存大小拓展到portBYTE_ALIGNMENT的倍数,不同的是,这里的申请的内存除了用户申请使用的之外,还要额外添加A_BLOCK_LINK需要的内存。毕竟每次申请内存都要创建一个链表结点做记录,C语言层面能被申请的内存都被申请完了(ucHeap[ configTOTAL_HEAP_SIZE ]),自然要负责将这一部分的内存也申请下来。举个例子,假设A_BLOCK_LINK需要的内存大小是12,用户申请50个内存,那么至少要分配50+12=62个内存,这还不算内存对齐操作之后的部分,代码如下:

		if( xWantedSize > 0 )
		{
   
			xWantedSize += heapSTRUCT_SIZE;

			/* Ensure that blocks are always aligned to the required number of bytes. */
			if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )
			{
   
				/* Byte alignment required. */
				xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
			}
		}

以上都是对xWantedSize进行操作,操作完成后,从链表的第一项开始查找空闲内存比xWantedSize大的项:

pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
{
   
	pxPreviousBlock = pxBlock;
	pxBlock = pxBlock->pxNextFreeBlock;
}

如果pxBlock不是xEnd,代表找到了满足要求的链表项:

//返回地址,由于申请时连带申请了A_BLOCK_LINK链表项,所以地址要加heapSTRUCT_SIZE ,返回的是用户实际申请的地址
pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );

//由于当前列表项的内存空间被占用,不是free状态,所以将此链表项删除
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

//如果原先空闲内存大于要分配的内存,分配完内存后原内存分为两个部分
//第一部分是分配出去的,第二的部分是依旧空闲的内存
if( ( pxBlock->xBlockSize - xWantedSize ) > heap
### FreeRTOS 堆区管理及配置 #### 三种内存分配方式概述 FreeRTOS 支持多种不同的堆管理方案,允许开发者根据具体应用场景选择最合适的内存管理模式。默认情况下,FreeRTOS 提供了几种不同版本的 `heap_x.c` 文件来实现动态内存分配功能[^1]。 #### 动态内存分配机制 当应用程序调用诸如 `xTaskCreate()` 或者其他用于创建内核对象(如队列、信号量等)的功能函数时,FreeRTOS 就会在运行期间通过 `pvPortMalloc()` 函数从指定的堆区内部分配所需大小的一块连续存储空间给新创建的对象使用。一旦该对象不再被需要并销毁之后,则会自动释放之前所占有的那片区域回到可用池里等待下一次再利用[^3]。 #### 不同 heap 实现的特点对比 - **heap_2.c**: 这一版本可以执行基本的分配与回收动作,不过它并不处理因频繁申请/释放而导致可能出现的小片段化问题,在某些特定条件下可能会遇到无法获取足够大的连续区块的情况[^4]。 - **heap_4.c**: 使用更先进的首次适应算法来进行最佳匹配查找,并且能够有效地减少由于多次分割造成的内部碎片累积现象。每当有新的请求进来时,程序总是尝试寻找最小能满足需求的那一段未使用的区间;而在归还的时候也会检查前后是否有相连的部分以便及时合并成更大的整体以备将来更大规模的需求之需[^5]. #### 如何设置自定义堆尺寸 对于基于 STM32F429 平台开发的应用而言,可以在项目源码目录下的 `FreeRTOSConfig.h` 中修改如下宏定义来自定义最大可分配总量: ```c #define configTOTAL_HEAP_SIZE ((size_t) ( 32 * 1024 )) ``` 这里将整个系统的堆容量设定为了32KB,当然实际数值应当依据具体的硬件资源情况以及预期负载特性灵活调整[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值