FreeRTOS 内存管理
堆:heap,就是一块空闲的内存,需要提供管理函数
malloc:从堆里划出一块空间给程序使用
free:用完后,再把它标记为 空闲 的,可以再次使用
栈:stack,函数调用时局部变量保存在栈中,当前程序的环境也是保存在栈中,可以从堆中分配一块空间用作栈。
FreeRTOS中内存管理的接口函数为:pvPortMalloc 、vPortFree,对应于C库的malloc、free。 文件在FreeRTOS/Source/portable/MemMang
void *pvPortMalloc( size_t xWantedSize );
xWantedSize
: 要分配的内存块的大小(以字节为单位)。
如果分配成功,返回指向分配的内存块的指针。如果分配失败(例如内存不足),返回NULL
。
void vPortFree( void *pv )
vPortFree
用于释放之前使用pvPortMalloc
分配的内存块。它将内存块返回到FreeRTOS堆,以便将来重新使用。
pv
: 指向要释放的内存块的指针。这个指针必须是之前通过调用pvPortMalloc
获得的。传递
在FreeRTOS中,有几种不同的堆管理方案,它们提供了从简单到复杂的内存管理功能。下面详细介绍了各个堆管理方案的特性、优缺点和适用场景:
堆管理方案 | 文件 | 特性 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|---|
Heap 1 | heap_1.c | - 固定大小内存块。 - 只支持分配,不支持释放。 | - 实现简单,代码量小。 - 分配速度快。 | - 不支持释放内存,容易造成内存浪费。 | - 简单应用。 - 确定内存需求的嵌入式系统。 |
Heap 2 | heap_2.c | - 动态分配和释放。 - 不合并相邻的空闲块。 | - 实现简单。 - 适合于需要简单分配和释放的场景。 | - 不合并空闲块,可能会导致内存碎片。 | - 小型应用。 - 碎片问题不严重的场景。 |
Heap 3 | heap_3.c | - 封装了标准的 malloc 和 free 。 | - 直接使用标准库的内存管理功能。 | - 依赖于标准库的实现,不一定适合所有嵌入式系统。 | - 使用标准库的环境。 - 兼容性要求高的系统。 |
Heap 4 | heap_4.c | - 动态分配和释放。 - 合并相邻的空闲块。 | - 减少了内存碎片。 - 适合复杂应用。 | - 实现较复杂,代码量大。 - 分配和释放速度较慢。 | - 需要频繁分配和释放内存的场景。 |
Heap 5 | heap_5.c | - 支持多个非连续的内存区域。 - 动态分配和释放。 | - 支持更大灵活性。 - 合并相邻的空闲块,减少碎片。 | - 实现复杂,适用范围广但可能需要更多的配置。 | - 大型应用。 - 有多个内存区域的复杂系统。 |
相关函数介绍
size_t xPortGetFreeHeapSize( void );
用于返回系统当前剩余的可用堆内存的大小(以字节为单位)。可以用来优化内存的使用情况
size_t xPortGetMinimumEverFreeHeapSize( void );
用于返回系统自启动以来,堆内存的最低空闲量(以字节为单位)。只有heap_4、heap_5支持此函数
Malloc 钩子函数 pvPortMalloc函数内部实现
void * pvPortMalloc( size_t xWantedSize )vPortDefineHeapRegions
{
......
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}
malloc
失败的钩子函数是一种机制,用于处理内存分配失败的情况。当系统中的内存不足以满足pvPortMalloc
(或malloc
)请求的分配时,该钩子函数会被调用。它的主要作用是提供一种自定义的处理方式,以应对内存分配失败的情况。
如何使用钩子函数
在FreeRTOS中,可以通过配置和实现 vApplicationMallocFailedHook
钩子函数来处理内存分配失败的情况。以下是具体的配置和实现步骤:
-
配置FreeRTOS: 在FreeRTOS的配置文件
FreeRTOSConfig.h
中,启用configUSE_MALLOC_FAILED_HOOK
宏:#define configUSE_MALLOC_FAILED_HOOK 1
-
实现钩子函数: 在你的应用程序代码中,实现
vApplicationMallocFailedHook
函数。这个函数没有返回值和参数,自定义的处理代码放在这个函数中。例如:void vApplicationMallocFailedHook(void) { // 记录错误日志 printf("Memory allocation failed!\n"); // 重启系统或其他恢复操作 // vTaskDelete(NULL); // 假设删除当前任务以释放资源 // NVIC_SystemReset(); // 假设重启系统 }
FreeRTOS 任务管理
FreeRTOS中,我们可以创建多个任务(task),任务也可以称为线程(thread)。
任务创建与删除
FreeRTOS 中使用xTaskCreate
用来创建任务的函数。它允许用户定义任务的行为、名称、栈大小、优先级、任务句柄等。在创建任务时,开发人员需要提供任务的入口函数、任务名称、栈大小、传递给任务的参数、任务优先级和任务句柄。以下是对 xTaskCreate
函数的详细介绍。
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数
const char * const pcName, // 任务的名字
const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节
void * const pvParameters, // 调用任务函数时传入的参数
UBaseType_t uxPriority, // 优先级
TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务
TaskFunction_t pxTaskCode
:指向任务函数的指针。该任务函数是任务的入口点,它的原型为void vTaskFunction(void *pvParameters)
,即必须接受一个void *
类型的参数,并且没有返回值,永不退出。
const char * const pcName
:任务的名字,主要用于调试目的。不同任务名称应该尽量唯一,以方便识别和调试。
const configSTACK_DEPTH_TYPE usStackDepth
:栈大小类型,通常为uint16_t
。任务栈的大小,以字(word)为单位。例如,如果传入 100,表示栈的大小为 100 word,即 400 字节。
void * const pvParameters
:指向常量的指针,传递给任务函数的参数。在任务函数中可以通过该参数访问任务创建时的上下文信息。
UBaseType_t uxPriority
:(优先级类型,通常为uint32_t
)任务的优先级,数值越高优先级越高。任务的优先级范围为 0~(configMAX_PRIORITIES – 1)。优先级决定了任务的调度顺序和响应时间。
- 类型:
UBaseType_t
(优先级类型,通常为uint32_t
)- 描述:任务的优先级,数值越高优先级越高。优先级决定了任务的调度顺序和响应时间。
TaskHandle_t * const pxCreatedTask
:保存 xTaskCreate 的输出结果,即任务的句柄(task handle)。任务句柄用于后续操作,如删除任务、挂起任务等。可以传入NULL
,如果不需要以后操作该任务。
使用静态分配内存的函数如下:
TaskHandle_t xTaskCreateStatic (
TaskFunction_t pxTaskCode, // 函数指针, 任务函数
const char * const pcName, // 任务的名字
const uint32_t ulStackDepth, // 栈大小,单位为word,10表示40字节
void * const pvParameters, // 调用任务函数时传入的参数
UBaseType_t uxPriority, // 优先级
StackType_t * const puxStackBuffer, // 静态分配的栈,就是一个buffer
StaticTask_t * const pxTaskBuffer // 静态分配的任务结构体的指针,用它来操作这个任务
);
相比于使用动态分配内存创建任务的函数,最后2个参数不一样
StackType_t * const puxStackBuffer
: 静态分配的栈内存,比如可以传入一个数组, 它的大小是usStackDepth*4。
StaticTask_t * const pxTaskBuffer:
静态分配的StaticTask_t结构体的指针,指向静态分配的任务控制块(TCB)。需要分配并初始化一个StaticTask_t
结构体,并将该结构体的指针传递给该参数。
返回值 成功:返回任务句柄; 失败:NULL
任务的删除
void vTaskDelete( TaskHandle_t xTaskToDelete );
pvTaskCode
:任务句柄,使用xTaskCreate创建任务时可以得到一个句柄。 也可传入NULL,这表示删除自己。
任务暂停状态(Suspended)
FreeRTOS中的任务通过vTaskSuspend函数可以进入暂停状态。
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
xTaskToSuspend
表示要暂停的任务,如果为NULL,表示暂停自己。
要退出暂停状态,只能由别的任务来操作:vTaskResume,xTaskResumeFromISR(中断函数调用)
任务就绪状态(Ready)
这个任务随时可以运行:只是有其他高优先级的任务在运行还。这时,它就处于就绪态(Ready)。
状态转换图
空闲任务及其钩子函数
在 FreeRTOS 中,空闲任务(Idle Task) 是一个特殊的任务,它的优先级最低,负责执行系统中的后台工作。空闲任务永远不会被删除,它会在没有其他任务需要运行时执行。空闲任务通常用于系统的后台清理工作,例如回收被删除任务的资源。为了实现特定功能,可以通过配置空闲任务钩子函数来定制空闲任务的行为。
空闲任务(Idle Task)
功能和特性
-
最低优先级:空闲任务的优先级最低,只有当没有其他任务需要运行时,空闲任务才会被调度。
-
资源回收:默认情况下,空闲任务会回收被删除任务的栈内存和任务控制块(TCB),从而释放系统资源。
-
不可删除:空闲任务是由系统自动创建的任务,用户不能删除它。
-
后台工作:用户可以利用空闲任务执行一些后台工作,如系统监控、内存检查、低优先级的后台任务等。
-
系统启动后自动运行:一旦系统启动,空闲任务会一直存在并运行,它是系统任务调度的一部分,确保 CPU 不会闲置。
空闲任务钩子函数(Idle Hook Function)
空闲任务钩子函数是一个用户自定义的函数,它会在每次空闲任务运行时被调用。通过配置空闲任务钩子函数,可以在系统空闲时执行特定的操作。空闲任务钩子函数必须是快速且非阻塞的,以免影响系统的正常调度。
配置空闲任务钩子函数
要使用空闲任务钩子函数,需要在 FreeRTOSConfig.h 文件中定义以下宏:
#define configUSE_IDLE_HOOK 1
定义这个宏后,开发者需要实现一个 vApplicationIdleHook
函数,FreeRTOS 会在空闲任务运行时调用该函数。
void vApplicationIdleHook(void);
使用场景
-
系统监控:可以在空闲任务钩子函数中检查系统资源使用情况,如内存使用、CPU 使用率等,以便在系统空闲时进行资源管理和监控。
-
后台任务:可以在空闲任务钩子函数中执行一些低优先级的后台任务,如日志记录、数据采集等。
-
功耗管理:在空闲任务钩子函数中,可以调用进入低功耗模式的代码,从而在系统空闲时降低功耗。
-
内存管理:可以在空闲任务钩子函数中检查内存泄漏、回收内存等,以保持系统的稳定性和健壮性。
-
系统维护:可以在空闲任务钩子函数中执行系统维护任务,如清理缓存、更新状态等。
注意事项
-
非阻塞:空闲任务钩子函数必须是非阻塞的,因为它会在每次空闲任务运行时调用。如果钩子函数中包含阻塞代码,会影响系统的任务调度和性能。
-
快速执行:钩子函数的执行时间应该尽可能短,以免延迟其他任务的调度。
-
不可调用 FreeRTOS API:在空闲任务钩子函数中,不能调用可能导致任务切换的 FreeRTOS API,如阻塞函数
vTaskDelay
、xQueueReceive
等。 -
低优先级任务:钩子函数中的操作应尽量是低优先级的后台工作,不应包含关键任务或时间敏感的操作。
果钩子函数中包含阻塞代码,会影响系统的任务调度和性能。 -
快速执行:钩子函数的执行时间应该尽可能短,以免延迟其他任务的调度。
-
不可调用 FreeRTOS API:在空闲任务钩子函数中,不能调用可能导致任务切换的 FreeRTOS API,如阻塞函数
vTaskDelay
、xQueueReceive
等。 -
低优先级任务:钩子函数中的操作应尽量是低优先级的后台工作,不应包含关键任务或时间敏感的操作。