1、内存管理简介
(1)创建任务、队列、信号量等对象时,两种内存创建方法:
动态内存创建:自动地从FreeRTOS管理的内存堆中申请创建对象所需的内存,并且在对象删除后,可将这块内存释放回FreeRTOS管理的内存堆。
静态内存创建:需用户提供各种内存空间,并且使用静态方式占用的内存空间一般固定下来了,即使任务、队列等被删除后,这些被占用的内存空间一般没有其他用途。
动态方式管理内存比静态方式管理内存更加灵活。
(2)标准C语言库也提供了函数malloc()和函数free()实现内存的申请和释放,但其存在如下缺点:
- 占用大量的代码空间不适合用在资源紧缺的嵌入式系统中
- 没有线程安全的相关机制
- 运行有不确定性,每次调用这些函数时花费的时间
- 可能都不相同,内存碎片化
针对以上缺点,FreeRTOS提供了多种动态内存管理算法,可用于不同的嵌入式系统。
2、FreeRTOS五种内存管理算法
FreeRTOS提供了5种动态内存管理算法,分别为: heap_1、heap_2、heap_3、heap_4、heap_5,其中使用较多的为heap_4内存管理算法。
2.1 heap_1内存管理算法
heap_1只实现了pvPortMalloc,没有实现vPortFree。也就是说,它只能申请内存,无法释放内存。如果你的工程,创建好的任务、队列、信号量等都不需要被删除,那么可以使用heap_1内存管理算法heap_1的实现最为简单,管理的内存堆是一个数组,在申请内存的时候,heap_1内存管理算法只是简单地从数组中分出合适大小的内存,内存堆数组的定义如下所示:
/* 定义一个大数组作为 FreeRTOS 管理的内存堆 */
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
2.2 heap_2内存管理算法
- 相比于heap_1内存管理算法, heap_2内存管理算法使用最适应算法,并且支持释放内存
- heap_2内存管理算法并不能将相邻的空闲内存块合并成一个大的空闲内存块,不可避免地会产生内存碎片
最适应算法假设heap有3块空闲内存(按内存块大小由小到大排序)︰5字节、25字节、50字节,现在新创建一个任务需要申请20字节的内存:
找出最小的、能满足pvPortMalloc的内存:25字节
把它划分为20字节、5字节,返回这20字节的地址,剩下的5字节仍然是空闲状态,留给后续的pvPortMalloc使用。。
2.3 heap_4内存管理算法
heap_4内存管理算法使用了首次适应算法,也支持内存的申请与释放,并且能够将空闲且相邻的内存进行合并,从而减少内存碎片的现象。
首次适应算法: 假设heap有3块空闲内存(按内存块地址由低到高排序)︰5字节、50字节、25字节,现在新创建一个任务需要申请20字节的内存
- 找出第一个能满足pvPortMalloc的内存:50字节
- 把它划分为20字节、30字节;返回这20字节的地址,剩下30字节仍然是空闲状态,留给后续的pvPortMalloc使用。
2.4 heap_5内存管理算法
heap_5内存管理算法是在 heap_4内存管理算法的基础上实现的,但是heap_5内存管理算法在heap_4内存管理算法的基础上实现了管理多个非连续内存区域的能力,heap_5内存管理算法默认并没有定义内存堆,需要用户手动指定内存区域的信息,对其进行初始化。
适用场景:在嵌入式系统中,那些内存的地址并不连续的场景。
指定一块内存:
typedef struct HeapRegion{
uint8_t * pucStartAddress; /* 内存区域的起始地址 */
size_t xSizeInBytes; /* 内存区域的大小,单位:字节 */
}HeapRegion_t;
指定多块不连续内存:
Const HeapRegion_t xHeapRegions[] ={
{ (uint8_t *)0x80000000, 0x10000 }, /* 内存区域 1 */
{ (uint8_t *)0x90000000, 0xA0000 }, /* 内存区域 2 */
{ NULL, 0 } /* 数组终止标志 */
};
vPortDefineHeapRegions(xHeapRegions);
3、内存管理相关API函数
- 申请内存——void * pvPortMalloc( size_t xWantedSize )
xWantedSize:申请的内存大小,以字节为单位;
返回值:返回一个指针,指向已分配大小的内存。如果申请内存失败,则返回NULL。
- 释放内存——void vPortFree( void *pv )
*pv:指针指向一个要释放内存的内存块;
- 获取当前空闲内存的大小——size_t xPortGetFreeHeapSize( void );
返回值:返回当前剩余的空闲内存大小
在一段内存没有被释放之前绝对不能再调用一次函数pvPortMalloc()为其再次分配内存,否则会导致内存泄露
4、API函数分析
4.1 相关变量介绍
(1)定义一个大数组作为FreeRTOS管理的内存堆:
/* 定义一个大数组作为 FreeRTOS 管理的内存堆 */
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];//一般是10K
(2)内存块结构体介绍
typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK* pxNextFreeBlock; /* 指向下一个内存块 */
size_t xBlockSize; /* 最高位表示内存块是否已经被分配 * 其余位表示内存块的大小 */
} BlockLink_t;