【FreeRTOS 教程 五】FreeRTOS 内存管理细致讲解
目录
一、FreeRTOS 堆内存管理:
二、RTOS源代码内存分配实现:
(1)heap_1.c:
(2)heap_2.c:
(3)heap_3.c:
(4)heap_4.c:
(5)heap_5.c:
三、堆栈使用和堆栈溢出检查:
(1)堆栈使用:
(2)堆栈溢出检测(方法 1):
(3)堆栈溢出检测(方法 2):
(4)堆栈溢出检测(方法 3):
四、静态内存分配 vs 动态内存分配:
(1)使用动态分配的 RAM 创建 RTOS 对象:
(2)使用静态分配的 RAM 创建 RTOS 对象:
五、静态分配的 FreeRTOS 项目注意事项:
(1)configSUPPORT_STATIC_ALLOCATION宏定义常量:
(2)configSUPPORT_DYNAMIC_ALLOCATION宏定义常量:
六、FreeRTOS教程示例代码下载:
一、FreeRTOS 堆内存管理:
每次创建任务、队列、互斥锁、软件定时器、信号量或事件组时,RTOS 内核都需要 RAM。RAM 可以从 RTOS API 对象创建函数内的 RTOS 堆自动动态分配,或者可以由应用程序编写者提供。
如果 RTOS 对象是动态创建的,那么标准 C 库 malloc() 和 free() 函数有时可用于此目的,但是往往存在以下问题:
- 它们在嵌入式系统上并不总是可用,
- 它们占用了宝贵的代码空间,
- 它们不是线程安全的,而且
- 它们不是确定性的(执行函数所需时间将因调用而异)
所以往往需要一个替代的内存分配实现。
一个嵌入式/实时系统的 RAM 和定时要求可能与另一个非常不同,所以单一的分配算法 RAM 将永远只适用于一个应用程序子集。
为了避免此问题,FreeRTOS 将内存分配 API 保留在其可移植层。可移植层在实现核心 RTOS 功能的源文件之外,允许提供适合于正在开发的实时系统的特定应用程序实现。当 RTOS 内核需要 RAM 时,它不调用 malloc(),而是调用 pvPortMalloc()。释放 RAM 时,RTOS 内核调用 vPortFree(),而不是 free()。
FreeRTOS 提供了几种堆管理方案,其复杂性和功能各不相同。开发者也可以提供自己的堆实现,甚至同时使用两个堆实现。同时使用两个堆实现允许将任务堆栈和其他 RTOS 对象放置在内部 RAM 中,并将应用程序数据放置在较慢的外部 RAM 中。
二、RTOS源代码内存分配实现:
FreeRTOS 下载包含五个内存分配实现示例,下面介绍所提供的每个实现方式何时选择可能最合适。
每个提供的实现都包含在单独的源文件中(分别是 heap_1.c、heap_2.c、heap_3.c、heap_4.c 和 heap_5.c),位于主 RTOS 源代码下载内容的 Source/Portable/MemMang 目录下。可根据需要添加其他实现方式。每次一个项目中,只应包含其中一个源文件[这些可移植层函数定义的堆将由 RTOS 内核使用,即使用 RTOS 的应用程序选择使用自己的堆实现]。
以下是介绍:
- heap_1 —— 最简单,不允许释放内存。
- heap_2 —— 允许释放内存,但不会合并相邻的空闲块。
- heap_3 —— 简单包装了标准 malloc() 和 free(),以保证线程安全。
- heap_4 —— 合并相邻的空闲块以避免碎片化。包含绝对地址放置选项。
- heap_5 —— 如同 heap_4,能够跨越多个不相邻内存区域的堆。
注意:
- heap_1 不太有用,因为 FreeRTOS 添加了静态分配支持。
- heap_2 现在被视为旧版,因为较新的 heap_4 实现是首选。
(1)heap_1.c:
heap_1 不太有用,因为 FreeRTOS 添加了静态分配支持。
- heap_1 是最简单的实现方式。内存一经分配,它不允许内存再被释放。尽管如此,heap_1.c 还是适用于大量嵌入式应用程序。这是因为许多小型和深度嵌入的应用程序在系统启动时,创建了所需的所有任务、队列、信号量等,并在程序的生命周期内使用所有这些对象(直到应用程序再次关闭或重新启动)。任何内容都不会被删除。
- 这个实现只是在要求使用 RAM 时将一个单一的数组细分为更小的块。数组的总大小(堆的总大小)通过 configTOTAL_HEAP_SIZE(定义于 FreeRTOSConfig.h 中)设置。提供了 configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h 配置常量,以允许将堆放置在内存中的特定地址。
- xPortGetFreeHeapSize() API 函数返回未分配的堆空间总量,允许优化 configTOTAL_HEAP_SIZE 设置。
heap_1 实现:
- 如果您的应用程序从未删除任务、队列、信号量、互斥锁等,则可以使用。(这实际上涵盖了使用 FreeRTOS 的大多数应用程序)。
- 始终具有确定性(总是需要相同的时间来执行),不会导致内存碎片化。
- 非常简单,且从静态分配的数组分配内存,这意味着它通常适合用于不允许真实动态内存分配的应用程序。
(2)heap_2.c:
heap_2 现在被视为旧版,因为 heap_4 是首选。
- heap_2 使用最佳适应算法,并且与方案 1 不同,它允许释放先前分配的块,它不将相邻的空闲块组合成一个大块。有关不合并空闲块的实现,请参阅 heap_4.c。
- 可用堆空间的总量通过 configTOTAL_HEAP_SIZE(定义于 FreeRTOSConfig.h 中)设置。提供了 configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h 配置常量,以允许将堆放置在内存中的特定地址。
- xPortGetFreeHeapSize() API 函数返回未分配的堆空间总量,(允许优化 configTOTAL_HEAP_SIZE 设置),但不提供关于未分配的内存如何被碎片化成小块的信息。
- pvPortCalloc() 函数的签名与标准库 calloc 函数相同。它为一个对象数组分配内存,并将分配的存储空间中的所有字节初始化为零。如果分配成功,它会返回指向分配的内存块中最低字节的指针。如果分配失败,它会返回一个空指针。
此实现介绍:
即使应用程序重复删除任务、队列、信号量、互斥锁等,仍然可用,但要注意以下关于内存碎片化的信息。
如果正在分配和释放的内存为随机大小,则不应使用。例如:
- 如果应用程序动态地创建和删除任务,且分配给正在创建任务的堆栈大小总是相同的,那么 heap2.c 可以在大多数情况下使用。但是,如果分配给正在创建任务的堆栈的大小不是总相同,那么可用的空间内存可能会被碎片化成许多小块,最终导致分配失败。这种情况下,heap_4.c 是更好的选择。
- 如果应用程序动态地创建和删除任务,且队列存储区域在每种情况下都是相同的(队列存储区域是队列项大小乘以队列长度),那么 heap_2.c 可以在大多数情况下使用。但是,如果在每种情况下的队列存储区域不相同,那么可用的空闲内存可能会被碎片化成许多小块,最终导致分配失败。这种情况下,heap_4.c 是更好的选择。
- 应用程序直接调用 pvPortMalloc() 和 vPortFree(),而不是仅通过其他 FreeRTOS API 函数间接调用。
- 如果您应用程序的队列、任务、信号量、互斥锁等的顺序不可预测,可能会导致内存碎片化。这对几乎所有的应用程序来说都是不可能的,但应牢记这一点。
- 非确定性,但比大多数标准 C 库 malloc 实现更有效。
heap_2 适用于许多必须动态创建对象的小型实时系统。可参考heap_4 了解类似实现,该实现将空间的内存块组合成单一的大内存块。
(3)heap_3.c:
这为标准 C 库 malloc() 和 free() 函数实现了简单的包装器,在大多数情况下,将与您选择的编译器一起提供。该包装器只是使 malloc() 和 free() 函数线程安全。
此实现介绍:
- 需要链接器设置堆,需要编译器库提供 malloc() 和 free() 实现。
- 不具有确定性。
- 可能会大大增加 RTOS 内核代码大小。
请注意,使用 heap_3 时,FreeRTOSConfig.h 中的 configTOTAL_HEAP_SIZE 设置无效。
(4)heap_4.c:
此方案使用第一适应算法,并且与方案 2 不同,它确实将相邻的空闲内存块组成单个大内存块(它确实包含合并算法)。
- 可用堆空间的总量通过 configTOTAL_HEAP_SIZE(定义于 FreeRTOSConfig.h 中)设置。提供了 configAPPLICATION_ALLOCATED_HEAP FreeRTOSConfig.h 配置常量,以允许将堆放置在内存中的特定地址。
- xPortGetFreeHeapSize() API 函数被调用时返回未分配的堆空间总量。
- xPortGetMinimumEverFreeHeapSize() API 函数返回 FreeRTOS 应用程序启动的系统中已存在的最小空闲堆空间量。这两个函数都没有提供关于未分配的内存如何碎片化为小块的信息。
vPortGetHeapStats() API 函数提供了其他信息。它填充了一个 heap_t 结构体的成员,如下所示:
/* vPortGetHeapStats() 函数的原型。*/
void vPortGetHeapStats( HeapStats_t *xHeapStats );
/* Heap_stats_t 结构体的定义。*/
typedef struct xHeapStats
{
size_t xAvailableHeapSpaceInBytes; /* 当前可用的总堆大小 - 这是所有空闲块的总和,而不是可以分配的最大块。 */
size_t xSizeOfLargestFreeBlockInBytes; /* vPortGetHeapStats() 被调用时堆中所有空闲块的最大大小(以字节为单位)。 */
size_t xSizeOfSmallestFreeBlockInBytes; /* vPortGetHeapStats() 被调用时堆中所有空闲块的最小大小(以字节为单位)。 */
size_t xNumberOfFreeBlocks; /* vPortGetHeapStats() 被调用时堆中空闲内存块的数量。 */
size_t xMinimumEverFreeBytesRemaining; /* 自系统启动以来堆中曾经存在的最小总空闲内存量(所有空闲块的总和)。 */
size_t xNumberOfSuccessfulAllocations; /* 调用 pvPortMalloc() 返回有效内存块的次数。 */
size_t xNumberOfSuccessfulFrees; /* 调用 vPortFree() 成功释放内存块的次数。 */
} HeapStats_t;
pvPortCalloc() 函数的签名与标准库 calloc 函数相同。它为一个对象数组分配内存, 并将分配的存储空间中的所有字节初始化为零。如果分配成功, 它会返回指向分配的内存块中最低字节的指针。如果分配失败,它会返回一个空指针。
heap_4:
- 即使应用程序重复删除任务、队列、信号量、互斥锁等等时也可使用
- 与 heap_2 实现相比,导致堆空间严重碎片化成多个小块的可能性小得多 (即使正在分配和释放的内存是随机大小) 。
- 不具有确定性,但比大多数标准 C 库 malloc 实现更有效。
heap_4.c 对于想在应用程序代码中直接使用可移植层内存分配方案的应用程序特别有用 (而不是 通过调用 API 函数 pvPortMalloc() 和 vPortFree() 来间接使用)。
(5)heap_5.c:
此方案使用与 heap_4 相同的第一拟合和内存合并算法, 允许堆跨越多个不相邻(非连续) 内存区域。
- Heap_5 通过调用 vPortDefineHeapRegions() 初始化,且不得使用, 直到 vPortDefineHeapRegions() 执行以后。创建 RTOS 对象(任务、队列、信号量等)将隐式调用 pvPortMalloc(), 因此,使用 heap_5 时,在创建任何此类对象之前调用vPortDefineHeapRegions() 至关重要。
- vPortDefineHeapRegions() 采用单一参数,该参数为 HeapRegion_t 结构体数组。
HeapRegion_t 在 portable.h 中定义为:
typedef struct HeapRegion
{
/* 将成为堆一部分的内存块的起始地址.*/
uint8_t *pucStartAddress;
/*内存块的大小。* /
size_t xSizeInBytes;
} HeapRegion_t;
HeapRegion_t 类型定义:
- 数组使用空位零大小的区域定义终止, 数组中定义的内存区域必须按地址顺序, 从低地址到高地址显示。以下源代码片段提供了示例 。MSVC Win32 模拟器演示也使用 heap_5 ,因此可作参考。
/* 为堆分配两个 RAM 块。第一个是从地址 0x80000000 开始的 0x10000 字节的块,第二个是从地址 0x90000000 开始的 0xa0000 字节的块。起始地址为 0x80000000 的块因为起始地址较低,所以出现在数组中的第一个位置。*/
const HeapRegion_t xHeapRegions[] =
{
{ ( uint8_t * ) 0x80000000UL, 0x10000 },
{ ( uint8_t * ) 0x90000000UL, 0xa0000 },
{ NULL, 0 } /* 结束数组。*/
};
/* 将数组传递给 vPortDefineHeapRegions()。 */
vPortDefineHeapRegions( xHeapRegions );
定义了堆使用的内存块之后初始化 heap_5:
- xPortGetFreeHeapSize() API 函数被调用时返回未分配的堆空间总量, xPortGetMinimumEverFreeHeapSize() API 函数返回 FreeRTOS 应用程序启动的系统中已存在的最小空闲堆空间量。这两个函数都没有提供关于未分配的 内存如何碎片化为小块的信息。
- pvPortCalloc() 函数的签名与标准库 calloc 函数相同。它为一个对象数组分配内存, 并将分配的存储空间中的所有字节初始化为零。如果分配成功, 它会返回指向分配的内存块中最低字节的指针。如果分配失败,它会返回一个空指针。
- vPortGetHeapStats() API 函数提供了有关堆状态的其他信息。
三、堆栈使用和堆栈溢出检查:
(1)堆栈使用:
可参考uxTaskGetStackHighWaterMark() API 函数。
- 每个任务都拥有其独立维护的堆栈。如果 使用 xTaskCreate() 创建任务, 则用作任务堆栈的内存会从 FreeRTOS 堆中自动分配, 并通过传递至 xTaskCreate() API 函数的参数来确定内存大小。
- 如果使用 xTaskCreateStatic() 创建任务,则用作任务堆栈的内存由 应用程序编写者预先分配。堆栈溢出是应用程序不稳定的一个很常见的原因。因此 FreeRTOS 提供两个可选机制,用于协助检测并纠正 出现的堆栈溢出问题。使用的选项通过 configCHECK_FOR_STACK_OVERFLOW 配置常量来配置。
- 请注意,这些选项仅适用于内存映射未分段的架构。 此外,在 RTOS 内核溢出检查开始之前,某些处理器可能会因堆栈损坏而 发生故障或异常。如果 cconfigCHECK_FOR_STACK_OVERFLOW 未设置为 0 , 则应用程序必须提供堆栈溢出钩子函数。钩子函数必须命名为 vApplicationStackOverflowHook(), 并且具备以下原型:
void vApplicationStackOverflowHook( TaskHandle_t xTask,
char *pcTaskName );
- xTask 和 pcTaskName 参数分别将违规任务的句柄和名称传递给该钩子函数。 但请注意,根据溢出的严重程度,这些参数本身可能会损坏,在这种情况下, 可直接检查 pxCurrentTCB 变量。
- 堆栈溢出检查会增加上下文切换的开销,因此建议只在开发或测试阶段使用此检查。
(2)堆栈溢出检测(方法 1):
- 在 RTOS 内核使任务退出运行状态后,堆栈可能达到其最大(最深)值, 因为此时的堆栈会包含任务上下文。此时, RTOS 内核可以检查处理器堆栈指针是否仍处于有效堆栈空间内。如果堆栈指针包含超出有效堆栈范围的值,则将调用堆栈溢出钩子函数。
- 此方法很快,但不能保证可以捕获所有堆栈溢出。将 configCHECK_FOR_STACK_OVERFLOW 设置为 1 即可使用此方法。
(3)堆栈溢出检测(方法 2):
- 任务首次创建时,其堆栈会填充一个已知值。任务退出运行状态时, RTOS 内核可以检查最后 16 个字节是否处于有效堆栈范围内,以确保这些已知值 未被任务或中断活动所覆盖。如果这 16 个字节中的任何一个不再为初始值, 则调用堆栈溢出钩子函数。
- 这种方法比方法 1 效率低,但仍然相当快。它很可能会捕获堆栈溢出, 但仍无法保证能够捕获所有溢出。
- 将 configCHECK_FOR_STACK_OVERFLOW 设置为 2 即可使用此方法。
(4)堆栈溢出检测(方法 3):
- 将 configCHECK_FOR_STACK_OVERFLOW 设置为 3 即可使用此方法。
- 此方法仅适用于选定的端口。如果可用,该方法将启用 ISR 堆栈检查。 检测到 ISR 堆栈溢出时,会触发断言。请注意,在这种情况下不会调用堆栈溢出钩子函数, 因为它只针对任务堆栈,而不是针对 ISR 堆栈。
四、静态内存分配 vs 动态内存分配:
在 FreeRTOS V9.0.0 之前的版本中,下列 RTOS 对象使用的内存是从专用 FreeRTOS 堆中分配的。 从 FreeRTOS V9.0.0 开始,应用程序编写者可以选择自行提供内存, 从而使得以下对象在创建时可以选择不进行动态内存分配:
- 任务
- 软件定时器
- 队列
- 事件组
- 二进制信号量
- 计数信号量
- 递归信号量
- 互斥锁
选择使用静态还是动态内存分配取决于应用程序和应用程序编写者的偏好。两种方法各有优缺点,且可以在同一个 RTOS 应用程序中同时使用。
(1)使用动态分配的 RAM 创建 RTOS 对象:
动态创建 RTOS 对象的优势在于化繁为简,并且有可能减少应用程序的最大 RAM 使用量:
- 创建对象时所需的函数参数较少。
- 内存分配会在 RTOS API 函数内部自动进行。
- 应用程序编写者无需担心自行分配内存。
- 如果 RTOS 对象被删除,它所使用的 RAM 可以被重新利用,从而有可能减少应用程序的最大 RAM 占用。
- RTOS 提供的 API 函数可以返回堆内存使用情况的信息,帮助优化堆的大小。
- 可以根据应用程序的需求选择合适的内存分配方案,例如 heap_1.c 提供简单且确定的分配方式,适用于安全关键应用;heap_4.c 提供碎片保护;heap_5.c 支持将堆分配到多个 RAM 区域;也可以使用应用程序编写者 提供的分配方案。
以下 API 函数在 configSUPPORT_DYNAMIC_ALLOCATION 设置为 1 或未定义时可用,用于通过动态分配的 RAM 创建 RTOS 对象:
- xTaskCreate():用于动态分配的 RAM 创建一个新的任务(线程)。该函数需要指定任务函数、任务堆栈大小、任务参数等信息。
- xQueueCreate():用于动态分配的 RAM 创建一个队列。队列用于任务间通信,可以存储消息或数据项。
- xTimerCreate():用于动态分配的 RAM 创建一个定时器。定时器可以用于延迟任务执行或周期性任务调度。
- xEventGroupCreate():用于动态分配的 RAM 创建一个事件组。事件组用于任务间的同步,可以等待多个事件标志的组合。
- xSemaphoreCreateBinary():用于动态分配的 RAM 创建一个二进制信号量。二进制信号量用于互斥访问共享资源,初始计数为1。
- xSemaphoreCreateCounting():用于动态分配的 RAM 创建一个计数信号量。计数信号量允许多个任务共享资源,初始计数可以大于1。
- xSemaphoreCreateMutex():用于动态分配的 RAM 创建一个互斥信号量。互斥信号量用于确保同一时间只有一个任务可以访问共享资源。
- xSemaphoreCreateRecursiveMutex():用于动态分配的 RAM 创建一个递归互斥信号量。递归互斥信号量允许同一个任务多次获取同一信号量,防止死锁。
(2)使用静态分配的 RAM 创建 RTOS 对象:
使用静态分配的 RAM 创建 RTOS 对象具有以下优势,可为应用程序编写者 提供更大的控制权限:
- RTOS 对象可以放置在特定的内存位置。
- 最大 RAM 占用可以在链接时(而不是在运行时)确定。
- 应用程序编写者无需担心如何合理处理内存分配失败的情形。
- 可在不允许动态内存分配的应用程序中使用 RTOS(尽管 FreeRTOS 提供的分配方案有助于克服 大多数反对意见)。
以下 API 函数在 configSUPPORT_STATIC_ALLOCATION 设置为 1 时可用,允许使用应用程序编写者提供的内存来创建 RTOS 对象。要提供内存,应用程序编写者只需声明适当对象类型的变量,然后将变量的地址传递给 RTOS API 函数即可。提供的 StaticAllocation.c 标准演示/测试任务演示了这些函数的使用方法:
- xTaskCreateStatic():用于在指定的静态内存位置创建一个新的任务(线程)。需要提供任务函数、任务堆栈缓冲区和任务参数等信息。
- xQueueCreateStatic():用于在指定的静态内存位置创建一个队列。队列用于任务间通信,可以存储消息或数据项。
- xTimerCreateStatic():用于在指定的静态内存位置创建一个定时器。定时器可以用于延迟任务执行或周期性任务调度。
- xEventGroupCreateStatic():用于在指定的静态内存位置创建一个事件组。事件组用于任务间的同步,可以等待多个事件标志的组合。
- xSemaphoreCreateBinaryStatic():用于在指定的静态内存位置创建一个二进制信号量。二进制信号量用于互斥访问共享资源,初始计数为1。
- xSemaphoreCreateCountingStatic():用于在指定的静态内存位置创建一个计数信号量。计数信号量允许多个任务共享资源,初始计数可以大于1。
- xSemaphoreCreateMutexStatic():用于在指定的静态内存位置创建一个互斥信号量。互斥信号量用于确保同一时间只有一个任务可以访问共享资源。
- xSemaphoreCreateRecursiveMutexStatic():用于在指定的静态内存位置创建一个递归互斥信号量。递归互斥信号量允许同一个任务多次获取同一信号量,防止死锁。
五、静态分配的 FreeRTOS 项目注意事项:
任务、队列、信号量和软件定时器等 RTOS 对象都可使用自动分配的 RAM 或预分配(静态分配)的 RAM 来创建。
(1)configSUPPORT_STATIC_ALLOCATION宏定义常量:
- 如果 configSUPPORT_STATIC_ALLOCATION设置为 1,则 RTOS 对象可以通过应用程序编写者提供的 RAM 创建。
- 如果 configSUPPORT_STATIC_ALLOCATION设置为 0,则 RTOS 对象只能通过从 FreeRTOS 堆中分配的 RAM 创建。
- 如果 configSUPPORT_STATIC_ALLOCATION未定义,则默认为 0。
- 如果 configSUPPORT_STATIC_ALLOCATION设置为 1,则应用程序编写者还必须提供两个回调 函数:vApplicationGetIdleTaskMemory(),为 RTOS 空闲任务提供内存;(如果 configUSE_TIMERS设置为 1)vApplicationGetTimerTaskMemory(),为 RTOS 守护进程/定时器服务任务提供内存。 示例如下:
/* configSUPPORT_STATIC_ALLOCATION 被设置为 1,因此应用程序必须提供 vApplicationGetIdleTaskMemory() 的实现来提供空闲任务使用的内存。*/
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize )
{
/* 如果要提供给空闲任务的缓冲区在此函数内声明,那么它们必须声明为 static,否则它们将在栈上分配,因此在此函数退出后将不存在。*/
static StaticTask_t xIdleTaskTCB;
static StackType_t uxIdleTaskStack[ configMINIMAL_STACK_SIZE ];
/* 传递一个指向 StaticTask_t 结构的指针,空闲任务的状态将存储在其中。*/
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
/* 传递将用作空闲任务堆栈的数组。*/
*ppxIdleTaskStackBuffer = uxIdleTaskStack;
/* 传递由 *ppxIdleTaskStackBuffer 指向的数组的大小。注意,由于数组必然是 StackType_t 类型,configMINIMAL_STACK_SIZE 以字为单位指定,而不是字节。*/
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
/*-----------------------------------------------------------*/
/* configSUPPORT_STATIC_ALLOCATION 和 configUSE_TIMERS 都被设置为 1,因此应用程序必须提供 vApplicationGetTimerTaskMemory() 的实现来提供计时器服务任务使用的内存。*/
void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize )
{
/* 如果要提供给计时器任务的缓冲区在此函数内声明,那么它们必须声明为 static,否则它们将在栈上分配,因此在此函数退出后将不存在。*/
static StaticTask_t xTimerTaskTCB;
static StackType_t uxTimerTaskStack[ configTIMER_TASK_STACK_DEPTH ];
/* 传递一个指向 StaticTask_t 结构的指针,计时器任务的状态将存储在其中。*/
*ppxTimerTaskTCBBuffer = &xTimerTaskTCB;
/* 传递将用作计时器任务堆栈的数组。*/
*ppxTimerTaskStackBuffer = uxTimerTaskStack;
/* 传递由 *ppxTimerTaskStackBuffer 指向的数组的大小。注意,由于数组必然是 StackType_t 类型,configTIMER_TASK_STACK_DEPTH 以字为单位指定,而不是字节。*/
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
- 应用程序必须提供回调函数示例,用于为空闲任务和定时器服务任务提供 RAM (如果 configSUPPORT_STATIC_ALLOCATION设置为 1)。
(2)configSUPPORT_DYNAMIC_ALLOCATION宏定义常量:
- 如果 configSUPPORT_DYNAMIC_ALLOCATION设置为 1,则 RTOS 对象可通过 RAM (从 FreeRTOS 堆中自动分配)创建。
- 如果 configSUPPORT_DYNAMIC_ALLOCATION设置为 0,则 RTOS 对象只能通过应用程序编写者提供的 RAM 创建 。
- 如果 configSUPPORT_DYNAMIC_ALLOCATION未定义,则默认为 1。
六、FreeRTOS教程示例代码下载:
FreeRTOS教程示例代码将会持续更新...
通过网盘分享的文件:FreeRTOS教程示例代码
链接: https://pan.baidu.com/s/1363h7hHmf8u2pjauwKyhtw?pwd=mi98 提取码: mi98