FreeRTOS的堆内存管理
为什么要进行内存管理?
FreeRTOS以下简称OS,是一个实时操作系统,在运行会时创建许多不同的线程。对于每一个线程的创建,OS需要进行管理,要保留线程的TCB线程控制块和Stack存储运行的上下文。OS是由C语言写的,标准C库的内存分配有malloc
和free
函数,直接使用这两个函数是不安全的——在分配内存的过程中发生了线程抢占丢失上下文,因此需要在分配内存时临时关断线程调度。OS把基于标准C库的malloc
和free
进行了封装,取名时heap_3
,还有其它五种分配方法,他们的命名都是heap_x
的格式。对于内存管理,学过操作系统这门课的小伙伴,知道有最佳适应算法,首次适应算法等,这些都能在OS中找到足迹。
OS的五种分配的优缺点以及如何选择
heap_1 - 最简单的堆分配,只分配不释放
在某些专用的使用场景下,任务只会被创建,不用释放。在这种情况下,内存只会在应用要使用实时功能时才会被内核动态分配,内存只分配不清除,在整个应用生命中一直维持。
值得注意的是,虽然内核是动态分配的,但是除了heap_3
之外,其它四种方法都要提前静态声明大段的内存空间,也就是说无论你用不用,内存都已经先被占用了。
下面是heap_4.c
的代码片段,其中configTOTAL_HEAP_SIZE
是FreeRTOSConfig.h
头文件中的内容,用来指定开辟heap的大小,在使用heap_3
时,是无效的。
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 20 * 1024 ) )
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
heap_1
简单暴力,速度快。没有内存释放,自然不会带来内存碎片的问题。
heap_2 - 最佳适应算法(不推荐使用)
官方指引中评价是,heap_2的兼容性好,但是不建议用来做新的产品。
简单介绍一下什么是最佳适应算法。
假设提前静态声明的数组中,由三段未分配的区域(A,B,C(地址从小到大)),A区大小是20,B区大小是50,C区大小是30。
现在要分配一个大小是25的空间,按照最佳适应算法
,他将分配到C区,理由是最佳适应算法每次挑选大小能装得下并且最小的区域。
而如果是首次适应算法
,他将分配到B区,理由是首次适应算法
每次挑选大小能装得下的地址低的区域。
在该最佳适应算法的例子中,分配到了C区,因此C区分配完后,还有大小为5的空间,带来的问题是内存分配的外碎片,这些空间几乎不能利用,而用这个算法,可能会出现许许多多的小外碎片。
针对外碎片的问题,如果分配的内存大小都是一致的,那么就不存在外碎片的问题,(但是可能产生内碎片)。
heap_3 - C标准库的方法
C标准库的方法就是malloc
和free
,它们的实现由链接器(linker)定义。为了保证线程的安全,heap_3
在分配内存时会临时的关闭线程调度。C标准库不用开辟打断的静态空间,但是代价是运行速度是五个方法中最慢的。
heap_4 - 首次适应算法
这应该是OS中使用最多的内存分配算法。
首次适应算法不会产生许多的小外碎片,因此进行内存的相邻合并就有其意义。注意heap_2
中不会进行相邻内存的合并。
操作系统的内存分配通常需要用内部RAM,以提高操运行的速度。在OS适配的环境,默认设置就是内部RAM,不需要手动更改。
heap_5 - 首次适应算法2 - 零碎的RAM
与heap_4
的分配内存逻辑是完全相同的,区别在于,heap_5
用于多个的RAM,不能用单一的静态数组囊括需要的内存空间,需要多维数组,来描述:
typedef struct HeapRegion
{
/* The start address of a block of memory that will be part of the heap.*/
uint8_t *pucStartAddress;
/* The size of the block of memory in bytes. */
size_t xSizeInBytes;
} HeapRegion_t;
/* Define the start address and size of the three RAM regions. */
#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 );
/* Add application code here. */
}
在使用时,需要先用vPortDefineHeapRegions
方法告诉操作系统,我要定义的内存范围。
参考资料
Mastering the FreeRTOS Real Time Kernel -Ch2.Heap Memory Management