文章目录
- [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_1和heap_3,是最简单的两个内存分配算法
后面会一起分析heap_2、heap_4和heap_5
0.2 内存分配实现
FreeRTOS 包含五个内存分配实现示例,分别是 heap_1.c、 heap_2.c、heap_3.c、heap_4.c 和 heap_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 参考资料
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;
xNextFreeByte 是 ucHeap 数组的下标,表示下一个空闲元素的位置。前缀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_1和heap_3很简单,很适合入门FreeRTOS源码的学习。
下篇文章将分析heap_2、heap_4和heap_5。