Memory Management
名词解释:
heap segmentation
:RAM
中没有一块完整的内存空间以满足内存申请需求,但是总的剩余空间大于需求的情况。这就表现了分配算法的重要性。
Stack & Heap
stack
(栈),有时也称为 frame
(帧)。一帧里存放了一个子过程(subroutine,其实也就是函数) 的信息。这些信息包括函数的返回地址和传入参数。当函数中再调用函数时,这些信息会放入堆栈中,而函数返回时,这些信息出栈并恢复到寄存器中。
函数中每定义一个变量,就会将其存储到栈中。函数返回时,释放整个栈空间。栈的优点在于,分配的整个栈空间都可供函数使用,无需再动态分配或者释放空间。
总结:
- the stack grows and shrinks as functions push and pop local variables
- there is no need to manage the memory yourself, variables are allocated and freed automatically
- the stack has size limits
- stack variables only exist while the function that created them, is running
Heap
是动态分配的空间,由操作系统管理*(不再跟栈一样单独为函数管理)*。堆中的空间在程序执行时可以分配,释放,改变大小。这些操作通过 malloc
和 free
实现。
Stack:
- very fast access
- don’t have to explicitly de-allocate variables
- space is managed efficiently by CPU, memory will not become fragmented
- local variables only
- limit on stack size (OS-dependent)
- variables cannot be resized
Heap:
- variables can be accessed globally
- no limit on memory size
- (relatively) slower access
- no guaranteed efficient use of space, memory may become fragmented over time as blocks of memory are allocated, then freed
- you must manage memory (you’re in charge of allocating and freeing variables)
- variables can be resized using
realloc()
Dynamic Memory Allocation
FreeRTOS
将内存分配实现成 protable layer
,可以自定义内存分配算法以实现不同的场景要求。需要申请内存时,调用 pvPortMalloc()
与 vPortFree()
。两者对应于 C 中的 malloc()
与 free()
。
FreeRTOS 实现了5种分配方式,分别对应 heap_1.c
, heap_2.c
, heap_3.c
, heap_4.c
, heap_5.c
。
Heap_1
针对的场景是系统初始化,分配的内存会一直持续到程序结束。Heap1
仅实现了 pvPortMalloc()
函数,未实现 vPortFree()
函数。(根本就没考虑释放) 。Heap_1
堆的大小由 FreeRTOSConfig.h
中 configTOTAL_HEAP_SIZE
决定。图示如下:
大的矩形框是堆,configTOTAL_HEAP_SIZE
决定大小。每一个任务由 TCB
和 Stack
(栈)组成。创建一个任务就在堆中为其分配一块空间。
这里一个任务应该就是一个进程(函数),所以是用栈。
Heap_2
heap_2
与 heap_1
相同,也是将大的空间切分为小的块供程序使用。不同之处在于:
heap_2
允许空间释放heap_2
实现了分配算法,而不是直接分配。
heap_2
不会将分配后剩余的空间整合起来,对于 fragmentation
更敏感。其分配过程如下:
Heap_3
直接使用了 C 的 malloc
和 free
。此时 configTOTAL_HEAP_SIZE
失效,不再决定堆的大小。heap_3
的优势在于 thread-safe
和 scheduler suspension
。
Heap_4
和 heap_1
、heap_2
原理相同:将整块的堆划分为小块供调度使用。heap_4
会把剩余的小块整合起来,形成大的内存块。用于处理内存请求大小不同的情况。图示:
Heap_5
在 Heap_4
基础上,可以定义多个 RAM
块大小。因此,在调用 pvPortMalloc()
分配内存之前,需要初始化——vPortDefineHeapRegions() ()
。 Example:
// declaration of vPortDefineHeapRegions
void vPortDefineHeapRegions(const HeapRegion_t* const pxHeapRegions);
// declaration of HeapRegion_t
typedef struct HeapRegion
{
// start address of a block of memory that will be part of the heap
uint_8* pucStartAddress;
// size of the block
size_t xSizeInBytes;
}
/* Define the start address and size of the three RAM regions. */
#define RAM1_SIZE ( 65 * 1024 )
static uint_8 ucHeap[RAM1_SIZE];
#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[] =
{
{ ucHeap, 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. */
}
这里在 RAM1 中使用数组声明一块空间是将最后绝对地址确定交给连接器。因为在 RAM1 中还有其余变量,这会占据一部分空间,所以不能直接用 RAM1 的首地址。
比如 STM32 从
0x2000 0000
开始是内部 RAM,0x6000 0000
开始是外部 RAM。在link script
(连接文件 )可以只声明内部 RAM 大小和空间。然后用heap_5
声明外部 RAM 空间,供操作系统分配使用。
总结
heap_1
:只分配,不释放,适用于始终存在的情况heap_2
:可以释放,但不会整合剩余的内存块,适用于申请内存大小相同,相近的情况heap_3
:实现了 C 的malloc
和free
函数heap_4
:可以释放,整合剩余的内存块,适用于申请内存大小不同的情况heap_5
:自定义 RAM 块大小和位置,用于分配。
使用函数总结
- pvPortMalloc()
- vPortFree()
- vPortDefineHeapRegions()
- xPortGetFreeHeapSize()
- xPortGetMinimumEverFreeHeapSize()
- void vApplicationMallocFailedHook( void )
这是分配内存失败时的回调函数,需要将configUSE_MALLOC_FAILED_HOOK
设置为1。
参考文章
- Mastering the FreeRTOS ™Real Time Kernel