任务栈是每个任务在运行时用来存储局部变量、函数调用参数和返回地址的一片内存区域。
在 FreeRTOS 中,每个任务都有自己的独立栈空间,这样可以避免任务之间的相互干扰。
栈,简单来说,是一种数据结构,它按照 “后进先出” 的规则存储数据。
用通俗易懂的比喻来说,假设你要去参加一场冒险,你得带上各种东西,像地图、食物、工具等等。这个用来装这些东西的背包,就类似于任务栈在程序中的角色。
当一个任务调用另一个函数时,就好比你在冒险途中遇到一个岔路口,决定去探索一条新的小路。这时候,你得记住你从哪里来的,以便之后能回到原来的路继续前进。在程序里,任务栈就负责记录这些信息。它会把当前函数的返回地址(也就是你出发的地方)存起来,同时还会存储函数调用的参数,就像你出发去探索小路时带上的必要物品。当被调用的函数执行完,程序就可以根据栈里存储的返回地址,准确地回到原来的函数继续执行,就像你顺利回到主路一样。
每个任务在运行过程中都会产生一些局部变量,这些局部变量就像是临时收集的小物件。任务栈为这些局部变量提供了存储的地方。它们在栈里 “安家”,直到函数执行结束,就像你完成了某个小任务,这些临时物件也就不再需要,它们所占用的栈空间就可以被释放,用来存放其他东西。
在 FreeRTOS 系统中,多个任务会轮流执行。当系统要从一个任务切换到另一个任务时,任务栈发挥着关键作用。它会保存当前任务的运行状态,包括各种寄存器的值等,当这个任务再次被调度执行时,系统可以从栈中恢复这些状态,让任务感觉就像从来没有被打断过一样,继续愉快地运行。
在 FreeRTOS 中,任务栈的分配方式有两种:动态分配和静态分配。
使用 xTaskCreate()
函数创建任务时,任务的栈空间会从 FreeRTOS 的堆空间中动态分配。你需要在调用时指定栈的大小(以“栈深度”为单位,而不是字节)。例如:
xTaskCreate(
vTaskFunction, // 任务函数
"TaskName", // 任务名称
128, // 栈深度(通常以字为单位)
NULL, // 任务参数
1, // 任务优先级
NULL); // 任务句柄
在动态分配中,栈的大小需要根据任务的实际需求来设置。如果任务会调用多个嵌套函数或使用大量局部变量,就需要分配更大的栈空间。
静态分配则是在编译时预先分配栈空间。使用 xTaskCreateStatic()
函数时,你需要手动定义任务的栈空间和任务控制块(TCB)。例如:
StaticTask_t task_tcb;
StackType_t task_stack[128];
xTaskCreateStatic(
vTaskFunction, // 任务函数
"TaskName", // 任务名称
128, // 栈深度
NULL, // 任务参数
1, // 任务优先级
task_stack, // 栈空间
&task_tcb); // 任务控制块
静态分配的好处是避免了动态内存分配的开销,但需要在代码中明确管理栈空间。
栈空间大小的设置是一个需要权衡的问题。如果栈空间太小,可能会导致栈溢出;如果太大,则会浪费宝贵的内存资源。通常,可以通过以下方法来确定栈大小:
经验法:根据任务的复杂度,分配一个合理的栈大小。例如,简单的任务可以分配 128 字,复杂的任务可能需要 256 字或更多。
调试法:在开发过程中,通过调试工具查看任务的实际栈使用情况。FreeRTOS 提供了 uxTaskGetStackHighWaterMark()
函数,可以用来获取任务栈的最高使用水位。
栈溢出是导致系统崩溃的常见原因之一。为了避免栈溢出,FreeRTOS 提供了两种检测机制:
在任务切换时,FreeRTOS 会检查栈指针是否在合法的栈空间范围内。如果栈指针超出范围,则触发栈溢出钩子函数。
在任务创建时,FreeRTOS 会用一个已知值填充栈空间。在任务切换时,检查栈空间的最后 16 字节是否被覆盖。如果被覆盖,则触发栈溢出钩子函数。
FreeRTOS 对任务栈的管理是独立于标准 C 的 malloc()
和 free()
的。任务的栈空间在任务创建时分配,在任务删除时释放。
假设我们有一个任务,用于控制 LED 的闪烁。任务代码如下:
void vTaskLED(void* pvParameters) {
while (1) {
LED_TurnOn();
vTaskDelay(pdMS_TO_TICKS(500)); // 延时 500ms
LED_TurnOff();
vTaskDelay(pdMS_TO_TICKS(500)); // 延时 500ms
}
}
在创建任务时,需要为它分配足够的栈空间。如果任务中没有复杂的嵌套调用,分配 128 字的栈空间通常就足够了。
xTaskCreate(
vTaskLED,
"LED Task",
128,
NULL,
1,
NULL);