内存管理的基本概念

什么是内存?

在计算机系统中变量、中间数据一般是存储在系统空间的,只有在实际使用才将这个数据从存储空间加载到中央处理器内部来进行运算。通常存储空间分为两种,一种内部存储空间,一种是外部存储空间,内部存储空间访问比较快,它能够按照变量的地址来随机地进行访问,就是RAM,叫做随机存储器,而外部存储空间保存的内容,相对来说比较固定的,即使掉电也不会丢失,可以把它理解为硬盘。本节讲解的是内部存储空间--RAM随机存储器的管理,当然SDRAM(配套板子上有,但这里不讲)

FreeRTOS的内存管理是怎样的?

其实FreeRTOS将内存和内核分开,内核包括了一些IPC通信机制,但是对于内存管理FreeRTOS没有太多的要求,他只规定了内存管理函数的接口,就是内存给内核提供了统一的接口,具体是怎么实现的,内核并不关心,比如它的内核是IPC,但是内存有很多种方案,无论你用了多少种方案,你只要给我统一的一个接口就可以了,那么在嵌入式设计中,内存分配是应该根据我们所设计的一个系统来决定选择哪种内存分配算法的,对于可靠性要求非常高的系统应该选择一些静态的分配,比如说只需要申请不需要释放,假如我们又有申请又有释放的话,就需要提供一种动态分配的内存,我申请了内存在我不需要的时候我把它释放掉。

FreeRTOS的内存管理模块主要就是内存的初始化、分配以及释放。

为什么不用C库的内存管理?

其实在电脑中我们用malloc和free这些是C库提供的,是没有问题的,但是在嵌入式系统中,它有很大的弊端,比如说它分配的时间是不稳定的,并且代码的实现也占据了很大的内存空间,在电脑中我们又很大的内存比如说8G,或者说FLASH很大,但是在嵌入式中,内存是很小的,用不起C库的消耗,同时他们是不安全的,比如说一个线程来分配一个内存,另一个线程也来分配吗?不可能的,所以FreeRTOS提供的内存管理是当前任务在分配,其它任务就不能来打扰我,也就是说这个任务来分配的时候,另一个任务来打断你,你就会分配失败,同时他可能会产生内存碎片。而且这两个函数会使得链接器的配置非常复杂如果C库申请内存允许你申请的方向是往栈的方向生长,他会覆盖一些栈的内容,这是一个非常重要的缺陷。

什么是内存碎片?

在嵌入式系统设计的时候,由于有实时性的要求,很少去使用虚拟内存,像Windows中会使用该机制,所有的内存都是由用户参与配置的,就是用户必须知道内存是多少,分配是多少,直接操作的是物理内存,所有分配的内存不可以超过系统所有物理内存的总和,而系统的所有东西都是用户参与管理的,所以内存很重要。同时在嵌入式系统中,对内存的分配时间也很严格,分配时间如果过长将影响实时性,一般的内存管理算法是根据存储的数据长度来一块块去寻找与它相接近的内存块,然后将数据存储在里面,而寻找这样的内存块需要的时间是不确定的,这对于实时操作系统来说是不可接受的,因此需要一个更好的内存管理算法,让它可以在可预测的时间内完成,在嵌入式系统中内存是非常珍贵的,嵌入式中64K的内存相对于电脑中8GB的内存是小巫见大巫,用了一块就少一块,随着内存的申请和释放,就会产生一些内存碎片。由于连续的分配和释放导致内存出现不连续的情况,而某个时刻需要分配很大的内存空间,但此时没有足够大的连续的内存空间,所以就会分配失败,因此这就是一个内存碎片。内存碎片的危害是非常大的,假如此时有一个碎片化的内存,很小且不连续,有时候分配2K也找不到连续的内存,即使当前内存还剩60K或者30K,但是找不到2K的空间也会申请失败。

因此不同的嵌入式系统对不同的配置有不同的要求,FreeRTOS提供了5种内存分配管理算法(heap_1.c~heap_5.c),根据不一样的应用选择不一样的分配算法!

静态内存分配有两个缺陷:

一是很容易造成大的内存空间浪费;二是少数情况下如果定义的数组不够大,就会引起下标越界错误

heap_1.c

只能申请内存不能释放内存

该方案的特点是:

1.用于从不删除任务、队列、信号量、互斥量等的应用程序(实际上大多数使用FreeRTOS的应用程序都符合这个条件)

2.函数的执行时间是确定的并且不产生内存碎片

看一下代码怎么实现的:


/*
 * The simplest possible implementation of pvPortMalloc().  Note that this
 * implementation does NOT allow allocated memory to be freed again.
 *
 * 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.
 */
#include <stdlib.h>

/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining
all the API functions to use the MPU wrappers.  That should only be done when
task.h is included from an application file. */
#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE

#include "FreeRTOS.h"
#include "task.h"

#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE

#if( configSUPPORT_DYNAMIC_ALLOCATION == 0 )
	#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0
#endif

/* A few bytes might be lost to byte aligning the heap start address. */
#define configADJUSTED_HEAP_SIZE	( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )

/* Allocate the memory for the 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
	static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
	//系统会定义一个数组来分配内存,这就是一个内存堆,
	//configTOTAL_HEAP_SIZE是((size_t)(36*1024))    //也就是36K RAM
#endif /* configAPPLICATION_ALLOCATED_HEAP */

static size_t xNextFreeByte = ( size_t ) 0;

/*-----------------------------------------------------------*/
//内存申请函数
void *pvPortMalloc( size_t xWantedSize ) //size_t是重定义后的无符号整型,在STM32中占4个字节
{
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL;//用于指向对齐后的内存堆起始地址

	/* 如果内存对齐字节不等于1,也就是说不是1字节对齐,那么就要
		把申请的内存空间大小(xWantedSize)按照要求对齐 */
	#if( portBYTE_ALIGNMENT != 1 )
	{
		//若 xWantedSize 与 0x0007 进行按位与运算结果非零,说明当前大小不是 8 的倍数,需要进行调整 
		if( xWantedSize & portBYTE_ALIGNMENT_MASK ) //portBYTE_ALIGNMENT_MASK是 0x0007
		{
			/* 该公式通过补全差额,将 xWantedSize 向上取整到最近的对齐边界(例如从 10 字节调整为 16 字节)。 */
			xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
		}
	}
	#endif

	vTaskSuspendAll();//挂起调度器,不希望其它的任务打扰
	{
		if( pucAlignedHeap == NULL )
		{
			/* 确保堆从正确对齐的边界开始. */
			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. */
		{
			/* 获取申请的内存空间其实地址并且保存在返回值中 */
			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;//返回申请成功的内存起始地址或者 NULL。
}
/*-----------------------------------------------------------*/
//内存释放函数
void vPortFree( void *pv )//该函数就没做,因为heap1.c内存管理算法不支持释放内存
{
	/* 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 )
{
	/* Only required when static memory is not cleared. */
	xNextFreeByte = ( size_t ) 0;//表示内存没有被申请
}
/*-----------------------------------------------------------*/
//获取未分配的内存堆大小
/*通常用于检查我们设置的内存堆是否合理,通过这个函数可以估计出最坏
情况下需要多大的内存堆,以便合理的节省内存资源。*/
size_t xPortGetFreeHeapSize( void )
{
	return ( configADJUSTED_HEAP_SIZE - xNextFreeByte );
}



首先系统会分配一个数组来做内存堆,内存堆的大小在FreeRTOSConfig.h中配置(configTOTAL_HEAP_SIZE)的,分配了36K的RAM,分配的过程下图辅助理解:

上图怎么理解呢?

在 使 用 内 存 申 请 函 数 之 前 , 需 要 将 管 理 的 内 存 进 行 初 始 化 , 需 要 将 变 量pucAlignedHeap 指向内存域第一个地址对齐处,因为系统管理的内存其实是一个大数组,而编译器为这个数组分配的起始地址是随机的,不一定符合系统的对齐要求,这时候要进行内存地址对齐操作。比如数组 ucHeap 的地址从 0x20000123 处开始,系统按照 8 字节对齐,上图是对齐后系统管理的内存示意图。

在内存对齐完成后, 用户想要申请一个 30 字节大小的内存,那么按照系统对齐的要求,我们会申请到 32 个字节大小的内存空间,即使我们只需要 30 字节的内存,申请完成的示意图:

heap_2.c

最佳匹配算法,与heap_4.c很相像,支持释放内存但是它不能将两个小内存合并成一个大的内存块

特点:

1.可以用在那些反复的删除任务、队列、信号量等内核对象且不担心内存碎片的应用程序(也就是说每次申请内存大小比较固定,如果不固定的话就会产生内存碎片);

2.如果我们的应用程序中的队列、任务、信号量、 等工作在一个不可预料的顺序,这样子也有可能会导致内存碎片。

3.具有不确定性

4.最好不用于那些内存分配和释放是随机大小的应用程序。

整个代码及解释如下:

/*
 * 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.
 *
 * See heap_1.c, heap_3.c and heap_4.c for alternative implementations, and the
 * memory management pages of http://www.FreeRTOS.org for more information.
 */
#include <stdlib.h>

/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining
all the API functions to use the MPU wrappers.  That should only be done when
task.h is included from an application file. */
#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE

#include "FreeRTOS.h"
#include "task.h"

#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE

#if( configSUPPORT_DYNAMIC_ALLOCATION == 0 )
	#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0
#endif

/* A few bytes might be lost to byte aligning the heap start address. */
#define configADJUSTED_HEAP_SIZE	( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )

/*
 * Initialises the heap structures before their first use.
 */
static void prvHeapInit( void );

/* 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
	static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */


/* 采用链表的数据结构记录空闲内存块 */
typedef struct A_BLOCK_LINK
{
	struct A_BLOCK_LINK *pxNextFreeBlock;	/*<< 指向下一个空闲内存块的指针. */
	size_t xBlockSize;						/*<< 记录申请的内存块的大小,包括链表结构体大小 */
} BlockLink_t;


static const uint16_t heapSTRUCT_SIZE	= ( ( sizeof ( BlockLink_t ) + ( portBYTE_ALIGNMENT - 1 ) ) & ~portBYTE_ALIGNMENT_MASK );
#define heapMINIMUM_BLOCK_SIZE	( ( size_t ) ( heapSTRUCT_SIZE * 2 ) )

/* Create a couple of list links to mark the start and end of the list. */
static BlockLink_t xStart, xEnd;

/* Keeps track of the number of free bytes remaining, but says nothing about
fragmentation. */
static size_t xFreeBytesRemaining = configADJUSTED_HEAP_SIZE;

/* STATIC FUNCTIONS ARE DEFINED AS MACROS TO MINIMIZE THE FUNCTION CALL DEPTH. */

/*
 * Insert a block into the list of free blocks - which is ordered by size of
 * the block.  Small blocks at the start of the list and large blocks at the end
 * of the list.
 */
//将节点按大小插入到链表中
#define prvInsertBlockIntoFreeList( pxBlockToInsert )								\
{																					\
BlockLink_t *pxIterator;															\
size_t xBlockSize;																	\
																					\
	xBlockSize = pxBlockToInsert->xBlockSize;										\
																					\
	/* Iterate through the list until a block is found that has a larger size */	\
	/* than the block we are inserting. */											\
	for( pxIterator = &xStart; pxIterator->pxNextFreeBlock->xBlockSize < xBlockSize; pxIterator = pxIterator->pxNextFreeBlock )	\
	{																				\
		/* There is nothing to do here - just iterate to the correct position. */	\
	}																				\
																					\
	/* Update the list to include the block being inserted in the correct */		\
	/* position. */																	\
	pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;					\
	pxIterator->pxNextFreeBlock = pxBlockToInsert;									\
}
/*-----------------------------------------------------------*/

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
static BaseType_t xHeapHasBeenInitialised = pdFALSE;//该参数表示的是堆已经被初始化
void *pvReturn = NULL;

	vTaskSuspendAll();//挂起调度器
	{
		/* 如果这是第一次调用malloc,那么堆将需要初始化以设置空闲块列表。 */
		if( xHeapHasBeenInitialised == pdFALSE )
		{
			prvHeapInit();
			xHeapHasBeenInitialised = pdTRUE;
		}

		
		if( xWantedSize > 0 )
		{
			xWantedSize += heapSTRUCT_SIZE;/* 调整要分配的内存值,需要增加上链表结构体所占的内存空间 */

			/* 如果需要申请的内存大小与系统要求对齐的字节数不一致需要进行字节对齐. */
			if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )
			{
				/* Byte alignment required. */
				xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
			}
		}
		//如果当前的空闲内存满足用户申请的内存大小就进行内存申请操作
		if( ( xWantedSize > 0 ) && ( xWantedSize < configADJUSTED_HEAP_SIZE ) )
		{
			/* 从空闲链表的头部开始找,如果该空闲内存大小大于xWantedSize,就从这块内存中
				抠出一部分内存返回,剩余的内存生成新的BlockLink_t插入到链表中 */
			pxPreviousBlock = &xStart;
			pxBlock = xStart.pxNextFreeBlock;
			//从链表头部开始查找大小符合条件的空闲内存
			while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
			{
				pxPreviousBlock = pxBlock;
				pxBlock = pxBlock->pxNextFreeBlock;
			}

			/* 如果搜索到了链表尾说说明没有找到符合大小空闲内存块.否则进行下一步处理 */
			if( pxBlock != &xEnd )
			{
				//能执行到这里说明已经找到合适的内存块了,找到内存块就返回内存块地址
				//注意的是:这里返回的是内存块+内存块链表结构体空间的偏移地址
				//因为内存块头部需要有一个空闲链表节点
				pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );

				/* 因为这个内存块被用户使用了需要从空闲内存块链表中移除 */
				pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

				/* 再看看被分的这个内存块大小剩余的内存块大小能不能再分一个空闲内存块 */
				if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
				{
					/* This block is to be split into two.  Create a new block
					following the number of bytes requested. The void cast is
					used to prevent byte alignment warnings from the compiler. */
					pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );

					/* Calculate the sizes of two blocks split from the single
					block. */
					pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
					pxBlock->xBlockSize = xWantedSize;

					/* Insert the new block into the list of free blocks. */
					prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
				}
				//跟踪剩余的空闲字节数
				xFreeBytesRemaining -= pxBlock->xBlockSize;
			}
		}

		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll();//恢复调度器
	//钩子函数(如果没有分配成功的话)
	#if( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
		if( pvReturn == NULL )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
	}
	#endif
	//返回申请成功的内存起始地址或者 NULL
	return pvReturn;
}
/*-----------------------------------------------------------*/
//释放内存函数:传入的参数是要释放的内存地址
void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;

	if( pv != NULL )
	{
		/* 根据要释放的内存块找到对应的链表节点 */
		puc -= heapSTRUCT_SIZE;

		/* This unexpected casting is to keep some compilers from issuing
		byte alignment warnings. */
		pxLink = ( void * ) puc;

		vTaskSuspendAll();
		{
			/* Add this block to the list of free blocks. */
			prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
			xFreeBytesRemaining += pxLink->xBlockSize;
			traceFREE( pv, pxLink->xBlockSize );
		}
		( void ) xTaskResumeAll();
	}
}
/*-----------------------------------------------------------*/
//还剩余多少字节没有用
size_t xPortGetFreeHeapSize( void )
{
	return xFreeBytesRemaining;
}
/*-----------------------------------------------------------*/

void vPortInitialiseBlocks( void )
{
	/* This just exists to keep the linker quiet. */
}
/*-----------------------------------------------------------*/

static void prvHeapInit( void )//初始化内存堆
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;

	/* 保证pucAlignedHeap按照指定的内存分配 */
	pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );

	/* 空闲内存链表头部初始化 */
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;//指向对齐后的内存起始地址pucAlignedHeap
	xStart.xBlockSize = ( size_t ) 0;//空闲内存块链表的头部是没有可用的内存空间,所以该值为0

	/* 空闲内存链表尾部初始化 */
	xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;//管理内存最大的值
	xEnd.pxNextFreeBlock = NULL;//当遍历空闲链表到这里的时候,表示已经没有可用的内存块了

	/* 首先,有一个单独的空闲块,它的大小可以占用整个堆空间。 */
	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
	pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
	pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}
/*-----------------------------------------------------------*/

heap_2.c算法的原理:

系统会先从内存块空闲链表表头开始遍历,查找符合用户申请大小的内存块,内存块空闲链表是按照内存块大小升序排列,所以最先返回的一块是最符合申请的内存大小(最佳匹配算法的原理)。当找到内存块的时候,返回该内存块偏移heapSTRUCT_SIZE个字节后的地址,因为在每个内存块的前面预留的节点是用于记录内存块的信息,用户不需要也不允许操作这部分内存。在申请内存成功的同时,系统还会判断当前内存是否有剩余(大于一个链表节点所需的内存空间),也就表示剩余的内存块还是存东西的,也要将其利用起来,这时系统会将内存块进行分割,在剩余的内存块头部添加一个内存节点,并且完善该空闲内存块的信息,然后将其内存块大小插入内存块空闲链表中,供下次分配使用。

空闲内存块的初始化示意图:

申请两次内存成功的内存示意图如下:

内存释放函数的原理:

只需要向函数中传入要释放的内存地址,那么系统就会自动向前索引到对应的链表节点,并且取出这块内存块的信息,将这个节点插入到空闲内存块链表中,即将这个内存块归还给系统。

释放一个内存块的示意图:

heap_3.c

封装了标准C库的malloc()和free()

heap_4.c(主要使用这个)

采用最佳匹配算法以及合并算法

空闲内存块以单链表的形式连接起来的,xStart表示链表头,pxEnd指向内存堆空间最后位置。

空闲链表不是以内存块大小进行排序的,而是以内存块起始大小排序的,内存地址小的在前,地址大的在后。因为heap_4.c方案还有一个合并算法,在释放内存的时候,假如两个相邻的空闲内存块在地址上是连续的,那么就可以合并为一个内存块

特点:

1.可用于重复删除任务、队列、信号量、互斥量等的应用程序

2.可用于分配和释放随机字节内存的应用程序

3.具有不确定性,但是效率比标准C库中的Malloc函数高的多

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

xMinimumEverFreeBytesRemaining:表示未分配内存堆空间历史最小的内存值

整个代码及解释如下:

/*
 * 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 http://www.FreeRTOS.org for more information.
 */
#include <stdlib.h>

/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining
all the API functions to use the MPU wrappers.  That should only be done when
task.h is included from an application file. */
#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE

#include "FreeRTOS.h"
#include "task.h"

#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE

#if( configSUPPORT_DYNAMIC_ALLOCATION == 0 )
	#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0
#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 )

/* 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
	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 );

/*
 * Called automatically to setup the required heap structures the first time
 * pvPortMalloc() is called.
 */
static void prvHeapInit( void );

/*-----------------------------------------------------------*/

/* 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. */
static BlockLink_t xStart, *pxEnd = NULL;

/* Keeps track of the number of free bytes remaining, but says nothing about
fragmentation. */
static size_t xFreeBytesRemaining = 0U;//表示当前系统中未分配的内存堆大小
static size_t xMinimumEverFreeBytesRemaining = 0U;//表示未分配内存堆空间历史最小的内存值

/* 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. */
static size_t xBlockAllocatedBit = 0;//标识内存块是否为空闲内存块 如果最高位置一则已被分配,置零则内存块为空闲

/*-----------------------------------------------------------*/

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;

	vTaskSuspendAll();//挂起所有任务也就是挂起调度器
	{
		/* 如果第一次调用内存分配函数,先调用内存堆初始化函数 */
		if( pxEnd == NULL )
		{
			prvHeapInit();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		/* 这里xWantedSize的大小有要求,要求最高位为0。
			因为后面 BlockLink_t 结构体中的 xBlockSize 的最高位需要使用
			这个成员的最高位被用来标识这个块是否空闲。因此要申请的块大小不能使用这个位. */
		if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
		{
			/* 调整要分配的内存值,需要增加上链表结构体所占的内存空间 */
			if( xWantedSize > 0 )
			{
				xWantedSize += xHeapStructSize;

				/* 进行内存对齐 */
				if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
				{
					/* Byte alignment required. */
					xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
					configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
			//如果当前的空闲内存足够满足用户申请的内存大小,就进行内存申请操作
			if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
			{
				/* 从起始(最低地址)块遍历列表,直到找到一个大小合适的. */
				pxPreviousBlock = &xStart;
				pxBlock = xStart.pxNextFreeBlock;
				//从链表头部开始查找大小符合条件的空余内存
				while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
				{
					pxPreviousBlock = pxBlock;
					pxBlock = pxBlock->pxNextFreeBlock;
				}

				/* If the end marker was reached then a block of adequate size
				was	not found. */
				if( pxBlock != pxEnd )
				{
					/* Return the memory space pointed to - jumping over the
					BlockLink_t structure at its start. */
					pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );

					/* This block is being returned for use so must be taken out
					of the list of free blocks. */
					pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;

					/* If the block is larger than required it can be split into
					two. */
					if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
					{
						/* This block is to be split into two.  Create a new
						block following the number of bytes requested. The void
						cast is used to prevent byte alignment warnings from the
						compiler. */
						pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
						configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );

						/* Calculate the sizes of two blocks split from the
						single block. */
						pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
						pxBlock->xBlockSize = xWantedSize;

						/* Insert the new block into the list of free blocks. */
						prvInsertBlockIntoFreeList( pxNewBlockLink );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					xFreeBytesRemaining -= pxBlock->xBlockSize;
					//如果当前内存大小小于历史最小记录,更新历史最小内存记录
					if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
					{
						xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					/* 注意这里的 xBlockSize 的最高位被设置为 1,标记内存已经被申请使用*/
					pxBlock->xBlockSize |= xBlockAllocatedBit;
					pxBlock->pxNextFreeBlock = NULL;
				}
				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

	configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );
	return pvReturn;
}
/*-----------------------------------------------------------*/

void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;

	if( pv != NULL )
	{
		/* 偏移得到节点地址,. */
		puc -= xHeapStructSize;

		/* This casting is to keep the compiler from issuing warnings. */
		pxLink = ( void * ) puc;

		/* 断言. */
		configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 );
		configASSERT( pxLink->pxNextFreeBlock == NULL );
		/* 判断一下内存块是否已经是被分配使用的,如果是就释放该内存块 */
		if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
		{
			if( pxLink->pxNextFreeBlock == NULL )
			{
				/* 将内存块标识为空闲 */
				pxLink->xBlockSize &= ~xBlockAllocatedBit;

				vTaskSuspendAll();
				{
					/* 更新系统当前空闲内存的大小,添加到内存块空闲链表中 */
					xFreeBytesRemaining += pxLink->xBlockSize;
					traceFREE( pv, pxLink->xBlockSize );
					prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
				}
				( void ) xTaskResumeAll();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
}
/*-----------------------------------------------------------*/

size_t xPortGetFreeHeapSize( void )
{
	return xFreeBytesRemaining;
}
/*-----------------------------------------------------------*/

size_t xPortGetMinimumEverFreeHeapSize( void )
{
	return xMinimumEverFreeBytesRemaining;
}
/*-----------------------------------------------------------*/

void vPortInitialiseBlocks( void )
{
	/* This just exists to keep the linker quiet. */
}
/*-----------------------------------------------------------*/

static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;

	/* 进行内存对齐操作 */
	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;//xTotalHeapSize表示系统管理的总内存大小
	}

	pucAlignedHeap = ( uint8_t * ) uxAddress;

	/* 初始化链表头部 */
	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
	xStart.xBlockSize = ( size_t ) 0;

	/* 初始化pxEnd,计算pxEnd的位置,它的值为内存尾部偏移一个
		BlockLink_t 结构体大小,偏移出来的这个BlockLink_t就是pxEnd*/
	uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
	uxAddress -= xHeapStructSize;
	uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
	pxEnd = ( void * ) uxAddress;
	pxEnd->xBlockSize = 0;
	pxEnd->pxNextFreeBlock = NULL;

	/* 将当前所有内存插入空闲内存块链表中。
		不同的是链表的尾部不是静态的,而是放在了内存的最后。 */
	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
	pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
	pxFirstFreeBlock->pxNextFreeBlock = pxEnd;

	/* 更新统计变量. */
	xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
	xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;

	/* xBlockAllocatedBit比较特殊,这里被设置为最高位为1,其它位为0的一个size_t大小的值.
		这样任意一个size_t大小的值和xBlockAllocatedBit进行按位或,如果该值最高位为1
		那么结果为1,否则为0,FreeRTOS利用这一特性标计一个内存块是否是空闲的 */
	xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}
/*-----------------------------------------------------------*/

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;
	if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
	{
		pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
		pxBlockToInsert = pxIterator;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	/* 判断 pxBlockToInsert 是否和后面的空闲内存相邻 */
	puc = ( uint8_t * ) pxBlockToInsert;
	if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
	{
		/* 与之相邻的下一个内存块不是链表尾节点 */
		if( pxIterator->pxNextFreeBlock != pxEnd )
		{
			/* 将后面的内存合入 pxBlockToInsert,
				并用 pxBlockToInsert 代替该内存在链表中的位置 */
			pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
			pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
		}
		else
		{
			/* 如果 pxBlockToInsert 的下一个内存块是 pxEnd, 那就不能合并,
				将内存块节点的成员变量 pxNextFreeBlock 指向 pxEnd。*/
			pxBlockToInsert->pxNextFreeBlock = pxEnd;
		}
	}
	else
	{
		//后面不相邻,那么只能插入链表了
		pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
	}

	/* 判断下前面是否已经合并了,如果合并了,就不用再更新链表了 */
	if( pxIterator != pxBlockToInsert )
	{
		pxIterator->pxNextFreeBlock = pxBlockToInsert;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

分配函数的原理如下:

从链表头 xStart 开始遍历查找合适的内存块,如果某个空闲内存块的大小能容得下用户要申请的内存,则将这块内存取出用户需要内存空间大小的部分返回给用户,剩下的内存块组成一个新的空闲块,按照空闲内存块起始地址大小顺序插入到空闲块链表中,内存地址小的在前,内存地址大的在后。在插入到空闲内存块链表的过程中,系统还会执行合并算法将地址相邻的内存块进行合并:判断这个空闲内存块是相邻的空闲内存块合并成一个大内存块,如果可以则合并。

priHeapInit()完成的示意图:

申请内存常见的情况:

申请3次内存完成的示意图:

内存释放函数的原理:

根据传入要释放的内存块地址,偏移之后找到链表节点,然后将这个内存块插入到空闲内存块链表中,在内存块插入过程中会执行合并算法,最后是将这个内存块标志为“空闲” (内存块节点的 xBlockSize 成员变量最高位清 0)、再更新未分配的内存堆大小即可
按照内存释放的过程,当我们释放一个内存时,如果与它相邻的内存块都不是空闲的,那么该内存块并不会合并,只会被添加到空闲内存块链表中,见下图:

如果某个时间段释放了另一个内存块,发现该内存块前面有一个空闲内存块与它在地址上是连续的,那么这两个内存块会合并成一个大的内存块,并插入空闲内存块链表中,见下图:

heap_5.c(与heap_4.c一样,不过它支持两块内存)

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

调用vPortDefineHeapRegions()函数来实现系统管理的内存初始化

HeapRegion_t结构体

调用vPortDefineHeapRegions()函数初始化内存堆

/* 在内存中为内存堆分配两个内存块,
第一个内存块大小为0x10000字节,起始地址为0x80000000,
第一个内存块大小为0xa0000字节,起始地址为0x90000000。
起始地址为0x80000000的内存块起始地址更低,因此放到了数组的第一个位置 */
const HeapRegion_t xHeapRegions[] =
{
    { ( uint8_t * ) 0x80000000UL, 0x10000 }, 
	{ ( uint8_t * ) 0x90000000UL, 0xa0000 },
	{ NULL, 0 }  //数组结尾
 };

/* 向函数vPortDefineHeapRegions() 传递形参 */
vPortDefineHeapRegions(xHeapRegions);

vPortDefineHeapRegions()


实验

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐