FreeRTOS源码学习(一)内存管理heap_1、heap_3

文章目录

  • [0 前言](#0 前言)
    • [0.1 前言](#0.1 前言)
    • [0.2 内存分配实现](#0.2 内存分配实现)
    • [0.3 参考资料](#0.3 参考资料)
  • [1 heap_1.c](#1 heap_1.c)
    • [1.1 应用场景](#1.1 应用场景)
    • [1.2 源码分析](#1.2 源码分析)
      • [1.2.1 内存堆](#1.2.1 内存堆)
      • [1.2.2 pvPortMalloc](#1.2.2 pvPortMalloc)
      • [1.2.3 pvPortFree](#1.2.3 pvPortFree)
      • [1.2.4 vPortInitialiseBlocks](#1.2.4 vPortInitialiseBlocks)
      • [1.2.5 xPortGetFreeHeapSize](#1.2.5 xPortGetFreeHeapSize)
  • [2 heap_3.c](#2 heap_3.c)
    • [2.1 应用场景](#2.1 应用场景)
    • [2.2 源码分析](#2.2 源码分析)
      • [2.2.1 pvPortMalloc](#2.2.1 pvPortMalloc)
      • [2.2.2 vPortFree](#2.2.2 vPortFree)
  • [3 结语](#3 结语)

0 前言

0.1 前言

源码分析已经有很多大佬做了,写这篇文章除了巩固自己的学习过程,也想尝试多分析一下FreeRTOS源码的细节之处,比如出于什么考虑使用了这种实现方式

FreeRTOS的内存分配算法有5种,在本文学习heap_1heap_3,是最简单的两个内存分配算法

后面会一起分析heap_2heap_4heap_5

0.2 内存分配实现

FreeRTOS 包含五个内存分配实现示例,分别是 heap_1.cheap_2.cheap_3.cheap_4.cheap_5.c, 位于 Source / Portable / MemMang 目录下

每次一个项目中, 只应包含其中一个源文件

heap_1------ 最简单,不允许释放内存
heap_2------ 允许释放内存,但不会合并相邻的空闲块
heap_3------ 简单包装了标准 malloc() 和 free(),以保证线程安全
heap_4------ 合并相邻的空闲块以避免碎片化。包含绝对地址放置选项
heap_5------ 如同 heap_4,能够跨越多个不相邻内存区域的堆

每个函数的接口函数如下:

c 复制代码
void *pvPortMalloc( size_t xWantedSize );
void vPortFree( void *pv );
void vPortInitialiseBlocks( void );
size_t xPortGetFreeHeapSize( void );

0.3 参考资料

FreeRTOS官网 ------ 堆内存管理

1 heap_1.c

1.1 应用场景

  • 应用程序从未删除任务、队列、信号量、互斥锁等。
  • 始终具有确定性(总是需要相同的时间来执行), 不会导致内存碎片化。
  • 从静态分配的数组分配内存, 适合用于不允许真实动态内存分配的应用程序 。

1.2 源码分析

必须使能 configSUPPORT_DYNAMIC_ALLOCATION 宏,开启动态内存分配

c 复制代码
#if( configSUPPORT_DYNAMIC_ALLOCATION == 0 )
	#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0
#endif

1.2.1 内存堆

内存管理算法管理的内存堆是一个数组,当有应用程序需要申请内存时,内存管理算法提取合适大小的内存供应用程序使用。

c 复制代码
#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 */

configAPPLICATION_ALLOCATED_HEAP控制内存堆使用的数组在模块内部,或是在外部定义
ucHeap 为内存堆划分的一段连续的内存空间。前缀 uc代表 unsigned char


c 复制代码
#define configTOTAL_HEAP_SIZE		( ( size_t ) ( 25 * 1024 ) )

configTOTAL_HEAP_SIZE FreeRTOSConfig.h文件中定义,是ucHeap 的字节数,用于表示最多可以分配的内存空间

FreeRTOS使用另一个宏定义configADJUSTED_HEAP_SIZE 来表示实际可分配的内存空间大小

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

可以看出,实际 可分配的内存空间大小就是最多 可以分配的内存空间数减去字节对齐数

字节对齐操作可能会导致损失掉一些内存空间,最多损失字节对齐数,这取决于数组的首地址

FreeRTOS这里直接减去字节对齐可能导致的最大损失的字节数,来作为实际可分配的内存空间大小。虽然可能浪费掉一些内存空间,但这样的实现方式可以提高程序整体的可靠性和运行时效率。


c 复制代码
static size_t xNextFreeByte = ( size_t ) 0;

xNextFreeByteucHeap 数组的下标,表示下一个空闲元素的位置。前缀x代表 FreeRTOS 自定义的基础数据类型。

1.2.2 pvPortMalloc

c 复制代码
void *pvPortMalloc( size_t xWantedSize )
{
	void *pvReturn = NULL;		
	static uint8_t *pucAlignedHeap = NULL;

pvReturn 指向被分配的内存,将作为返回值提供给上层程序

pucAlignedHeap 是用于内存堆管理对齐操作的指针。


c 复制代码
	/* Ensure that blocks are always aligned to the required number of bytes. */
	#if( portBYTE_ALIGNMENT != 1 )
	{
		if( xWantedSize & portBYTE_ALIGNMENT_MASK )
		{
			/* Byte alignment required. */
			xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
		}
	}
	#endif

这里实现了对申请内存空间大小 xWantedSize 的对齐要求,由portBYTE_ALIGNMENT 宏控制

实际上就是向上取portBYTE_ALIGNMENT的倍数

如:xWantedSize 为 101,portBYTE_ALIGNMENT 为 4,则实际申请的内存为 104 字节。

实现内存对齐对于MCU来说有很大的性能提升

  • 提高访问效率:加载一个内存对齐的u32变量仅需一条单周期汇编指令(如LDR)
    如果不是内存对齐,则可能需要调用多个非单周期汇编指令
  • 对齐后可以方便的对指针使用掩码,从而加速一些软件操作(如用掩码实现环形缓冲区)
  • 一些内核(如Cortex-M0)禁止非对齐访问,否则会触发硬件错误
  • 一些外设要求访问地址是对齐的,如有些DMA的传输缓冲区要求是字对齐的

c 复制代码
	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 ) ) );
		}

这段代码的功能为:在第一次调用pvPortMallo()函数时,初始化 pucAlignedHeap指针,使其指向 ucHeap 数组的某个元素,该地址满足字节对齐要求。

这里使用了掩码的方式实现字节对齐要求

  • 如果一个指针是4字节对齐的,4的二进制为100,那么其指针的低2位一定是全0的。

  • 如果想让任意的一个指针4字节对齐,只要清除其低2位,就可以向下取4字节的倍数,实现4字节对齐

  • 只需要一个简单的&操作,就可以实现字节对齐要求。
    在这段代码中:

  • 首先获取了ucHeap[ portBYTE_ALIGNMENT ]的指针,因为在进行字节对齐操作时,不能使对齐后的指针超过ucHeap数组的内存范围,所以不能取ucHeap[0]

  • 然后使用了掩码对其向下向portBYTE_ALIGNMENT 字节取整,获得字节对齐后的指针。

  • 这个指针将作为内存分配的基地址,分配内存空间时从这个地址开始分配。

FreeRTOS在不同场景下实现字节对齐的方式有所差异,届时会特意说明


c 复制代码
		/* 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判断有两个条件,第一个条件保证还有剩余内存空间可以分配,第二个条件用于防止输入一个过大的xWantedSize ,计算结果溢出将导致计算结果显著小于实际值,进而可能错误的导致满足了第一个条件。

函数将返回从字节对齐后的内存分配基指针pucAlignedHeap 向后偏移xNextFreeByte个字节的地址作为返回值,代表该部分内存区域被分配给上层程序。


c 复制代码
	#if( configUSE_MALLOC_FAILED_HOOK == 1 )
	{
		if( pvReturn == NULL )
		{
			extern void vApplicationMallocFailedHook( void );
			vApplicationMallocFailedHook();
		}
	}
	#endif
	
	return pvReturn;
}

在内存分配失败时,如果使能了回调函数,则会触发内存分配失败回调函数。

1.2.3 pvPortFree

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 );
}

1.2.4 vPortInitialiseBlocks

初始化内存空间,就是将用于代表已分配字节数的xNextFreeByte变量清零。

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

1.2.5 xPortGetFreeHeapSize

c 复制代码
size_t xPortGetFreeHeapSize( void )
{
	return ( configADJUSTED_HEAP_SIZE - xNextFreeByte );
}

2 heap_3.c

2.1 应用场景

heap_3就是简单包装了C标准库中的 malloc()free()

  • 用于开发环境快速原型验证
  • 与第三方库兼容性要求高的场景
  • 资源受限但需动态内存的短期任务
  • 移植现有非 RTOS 代码到 FreeRTOS

2.2 源码分析

heap_3只有两个接口函数,没有初始化函数和获取剩余可分配内存空间的函数

由于只是封装了C库的内存分配函数,所以没有什么需要特别说明的

2.2.1 pvPortMalloc

c 复制代码
void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn;

	vTaskSuspendAll();
	{
		pvReturn = malloc( xWantedSize );
		traceMALLOC( pvReturn, xWantedSize );
	}
	( void ) xTaskResumeAll();

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

	return pvReturn;
}

2.2.2 vPortFree

c 复制代码
void vPortFree( void *pv )
{
	if( pv )
	{
		vTaskSuspendAll();
		{
			free( pv );
			traceFREE( pv, 0 );
		}
		( void ) xTaskResumeAll();
	}
}

3 结语

heap_1heap_3很简单,很适合入门FreeRTOS源码的学习。

下篇文章将分析heap_2heap_4heap_5

相关推荐
Y unes19 小时前
《i.MX6ULL LED 驱动实战:内核模块开发与 GPIO 控制》
linux·c语言·驱动开发·vscode·ubuntu·嵌入式
优信电子1 天前
电脑控制DFPlayer Mini MP3播放音乐
单片机·串口·嵌入式·mp3·语音播报·串口语音·mp3播报
北京迅为2 天前
【北京迅为】iTOP-4412精英版使用手册-第六十七章 USB鼠标驱动详解
linux·人工智能·嵌入式·4412
小志biubiu2 天前
linux_缓冲区及简单libc库【Ubuntu】
linux·运维·服务器·c语言·学习·ubuntu·c
不脱发的程序猿2 天前
如何检测和解决I2C通信死锁
stm32·单片机·嵌入式·1024程序员节
宁静致远20212 天前
STM32之TM1638数码管及键盘驱动
stm32·嵌入式
Miuney_MAX2 天前
Jlink 识别不到设备
嵌入式
Dragon_D.3 天前
排序算法大全——插入排序
算法·排序算法·c·学习方法
大聪明-PLUS3 天前
Linux 中的 DNS 工作原理(一):从 getaddrinfo 到 resolv.conf
linux·嵌入式·arm·smarc