\FreeRTOS\Source\portable\MemMang下提供了5中内存分配机制的实现
一、配置FreeRTOS内存大小
在FreeRTOSConfig.h头文件中宏configTOTAL_HEAP_SIZE用于配置内核可用的RAM大小。
在heap1.c, heap2.c, heap4.c源文件中,分配的内存实际上是用一个静态数组ucHeap来表示。
再具体的实现上,FreeRTOS 内核规定的几个内存管理函数原型。系统内部及用户如果要使用内存,只能通过该函数接口进行申请。因此完全可以有用户自己实现。具体函数接口如下(不同方案稍有区别):
void *pvPortMalloc( size_t xSize )
:内存申请函数void vPortFree( void *pv )
:内存释放函数void vPortInitialiseBlocks( void )
:初始化内存堆函数size_t xPortGetFreeHeapSize( void )
:获取当前未分配的内存堆大小size_t xPortGetMinimumEverFreeHeapSize( void )
:获取未分配的内存堆历史最小值
heap_1.c
heap1.c是FreeRTOS提供的多种内存分配策略中最简单的一种,其使用一个全局静态变量xNextFreeByte来记录未分配使用的内存空间的位置,每分配一次就会往后进行偏移。另外,在这种方式中,已经分配的内存不会再释放。**实际上,大多数的嵌入式系统并不需要动态删除任务、信号量、队列等,而是在初始化的时候一次性创建好,便一直使用,永远不用删除。**所以这个内存管理策略实现简洁、安全可靠,使用的非常广泛。
应用程序调用此函数void *pvPortMalloc(size_t xWantedSize)请求分配内存,需要注意的是函数中有两个地方为满足字节对齐的要求进行调整:
为确保从静态数组ucHeap中分配给应用程序的内存块是8字节对齐,函数会判断xWantedSize是否为8的倍数,如果不是,就会添补字节。
step 1 :初始化
初始化分配一片内存:
step 2 :分配
用户分配之后的内存
由于分配出去的内存空间不需要回收,因此每一次分配空间的时候只需要按需要的内存大小在空闲空间上分割出来就可以了。分割时,首先要检查需要的内存大小有没有超出空闲空间的大小,还要检查假如分配完空间后,其末地址是否溢出。假如没有超出空闲空间大小,出没有发生内存溢出现象,才进行分配,记录新分配空间的首地址到pvReturn。
heap_2.c
Heap_1.c 是FreeRTOS多种内存管理机制中最简单的一种:全局声明一个静态数组ucHeap,然后通过指针偏移记录空间的分配情况,在这种内存机制下无法对内存进行释放。
在heap2.c中,同样使用一个全局静态数组ucHeap来表示内存,heap2.c内存管理机制较heap1.c而言增加了内存释放的功能,通过使用链表对内存进行有效管理。
heap2.c虽然支持内存回收,但是回收内存时不进行相邻空闲块的合并,因此这种策略会导致内存碎片,系统运行久了会出现无法分配过大的连续空间的情况,heap4.c中内存管理机制就是为了弥补这种缺陷而诞生的,它在heap2.c的基础上增加了连续空闲块合并的功能。
由于FreeRTOS用空闲块对内存堆进行管理,于是用这一个结构来形成一条空闲块链表对空闲块进行组织和管理。
step1: 初始化
初始化一片内存
初始化完成后的内存分布:
step 2 :分配
用户申请一片内存空间
step 3 :释放
用户释放一片内存
要是分配出去的空闲块的剩余空间要是比两倍的空闲块头还要大,则将分配出去的这个空闲块分割剩余的空间出来,重新放到空闲块链表中。例如,初始化后只有一个空闲块,这个空闲块大小为17KB。经过调整后的用户申请空间大小为1KB,则FreeRTOS就从这个空闲块靠近块首的地方分割出1KB出来分配出去,剩余的16KB则重新放回空闲块链表里。以便下一次继续分配。
heap_3.c
该内存管理方式是将标准的malloc()和free()进行了简单的封装。只是在封装的时候加入了调度器挂起和恢复操作。前两种方式内存管理是在系统申请的一个大数组中,该种方式是真正在堆中进行的。挂起和恢复使线程安全。
因此,在MCU(STM32…)的启动文件(即汇编阶段)就要设置分配堆的大小。
临界区:
进入临界区即屏蔽中断,但这里不屏蔽所有中断,FreeRTOS有一个临界区中断优先级,在FreeRTOSConfig.h里配置。
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191
关闭中断时设置BASEPRI寄存器为该值,这个寄存器会并屏蔽掉掉优先级比设定值低的,而优先级高的不受影响,如果BASEPRI设为0则不屏蔽任何中断。
假如M3的中断优先级为3位,则191(0b10111111)的前3位有效,即优先级为5,此时优先级为57的中断优先级低于设定值值会被屏蔽,而优先级为04的中断优先级高于设定值不受影响,不受影响的中断称作非临界区中断,不能调用FreeRTOS的API,否则会破会系统数据。
进入临界区时会屏蔽临界区内的所有中断,进入和离开临界区的API需要成对使用。
taskENTER_CRITICAL()
......
taskEXIT_CRITICAL()
heap_4.c
FreeRTOS 中的 heap 4 内存管理,可以算是 heap 2 的增强版本。每次内存分配后都会产生一个内存块,多次分配后,会产生很多内存碎片,在较为复杂的场景(需要经常动态分配和释放场景)下,几乎是无法胜任。所以就有了 heap 4,它相比 heap 2 来说,提供了相邻空闲的内存块合并的功能,一定程度上减少了内存碎片,使得释放了的内存能够再度合并称为较为大的内存块。
Heap_4用了BlockLink_t中xBlockSize的最高一位来标识某个内存块是否处于空闲状态
在实现原理上和 heap 2大多一样,在插入这块进行检查,在 heap 2中,也有这个函数,但是它是按照内存块的小到大进行排序,由于有相邻内存块合并的要求,所以在 heap 4 中,内存块链表的组织形式,是按照他们的地址顺序由小到大组织的。首先通过 xStart 开始,获取第一个空闲块的地址,并比较它的下一个空闲块地址和待插入空闲块地址的大小,以便获得合适的插入位置,获得合适的位置了以后,判断待插入的这个空闲块是否和前一个空闲块相邻,如果相邻的话,就将两个块合并到一起。然后在判断是否和后一个空闲块也相邻,如果相邻,也将他们合并。
多次分配后内存情况:
如果要释放中间那个内存,那么就会触发向上和向下的合并:
heap_5.c
heap5与heap4分配释放算法完全相同,只是heap5支持管理多块不连续的内存,本质是将多块不连续内存用链表串成一整块内存,再用heap4算法来分配释放。若使用heap5则在涉及到分配释放的函数调用时要先调用vPortDefineHeapRegions把多块不连续内存串成一块初始化。
使用:
#define RAM1_START_ADDRESS ( ( uint8_t * ) 0x00010000 )
#define RAM1_SIZE ( 65 * 1024 )
#define RAM2_START_ADDRESS ( ( uint8_t * ) 0x00020000 )
#define RAM2_SIZE ( 32 * 1024 )
#define RAM3_START_ADDRESS ( ( uint8_t * ) 0x00030000 )
#define RAM3_SIZE ( 32 * 1024 )
/* Create an array of HeapRegion_t definitions, with an index for each of the three
RAM regions, and terminating the array with a NULL address. The HeapRegion_t
structures must appear in start address order, with the structure that contains the
lowest start address appearing first. */
const HeapRegion_t xHeapRegions[] =
{
{ RAM1_START_ADDRESS, RAM1_SIZE },
{ RAM2_START_ADDRESS, RAM2_SIZE },
{ RAM3_START_ADDRESS, RAM3_SIZE },
{ NULL, 0 } /* Marks the end of the array. */
};
int main( void ) {
/* Initialize heap_5. */
vPortDefineHeapRegions( xHeapRegions );
}
需要注意的几点是:
1、定义 HeapRegion_t 数组的时候,最后一定要定义成为 NULL 和 0,这样接口才知道这是终点;
2、被定义的 RAM 区域,都会去参与内存管理;
类似:
HeapRegion_t xHeapRegions[] =
{
{ ( uint8_t * ) 0x80000000UL, 0x10000 },
{ ( uint8_t * ) 0x90000000UL, 0xa0000 },
{ NULL, 0 } << Terminates the array.
};
也可以:
const HeapRegion_t xHeapRegions[] =
{
{ ucHeap, RAM1_HEAP_SIZE },
{ RAM2_START_ADDRESS, RAM2_SIZE },
{ RAM3_START_ADDRESS, RAM3_SIZE },
{ NULL, 0 } /* Marks the end of the array. */
};
这样ram1 的地址就安全了。
- 内存越界?
传送门:
【freeRTOS】操作系统之一-任务调度
【freeRTOS】操作系统之二-队列
【freeRTOS】操作系统之三-信号量
【freeRTOS】操作系统之四-事件标志组
【freeRTOS】操作系统之五.-内存管理
【freeRTOS】操作系统之六-低功耗模式
【freeRTOS】操作系统之七-freeRtos移植