FreeRTOS之任务栈

1. 任务栈是什么?

任务栈是每个任务在运行时用来存储局部变量函数调用参数返回地址一片内存区域

在 FreeRTOS 中,每个任务都有自己的独立栈空间,这样可以避免任务之间的相互干扰。

栈,简单来说,是一种数据结构,它按照 “后进先出” 的规则存储数据

用通俗易懂的比喻来说,假设你要去参加一场冒险,你得带上各种东西,像地图、食物、工具等等。这个用来装这些东西的背包,就类似于任务栈在程序中的角色。

2.任务栈有啥用?

(一)辅助函数调用

当一个任务调用另一个函数时,就好比你在冒险途中遇到一个岔路口,决定去探索一条新的小路。这时候,你得记住你从哪里来的,以便之后能回到原来的路继续前进。在程序里,任务栈就负责记录这些信息。它会把当前函数的返回地址(也就是你出发的地方)存起来,同时还会存储函数调用的参数,就像你出发去探索小路时带上的必要物品。当被调用的函数执行完,程序就可以根据栈里存储的返回地址,准确地回到原来的函数继续执行,就像你顺利回到主路一样。

(二)存储局部变量

每个任务在运行过程中都会产生一些局部变量,这些局部变量就像是临时收集的小物件。任务栈为这些局部变量提供了存储的地方。它们在栈里 “安家”,直到函数执行结束,就像你完成了某个小任务,这些临时物件也就不再需要,它们所占用的栈空间就可以被释放,用来存放其他东西。

(三)任务切换

在 FreeRTOS 系统中,多个任务会轮流执行。当系统要从一个任务切换到另一个任务时,任务栈发挥着关键作用。它会保存当前任务的运行状态包括各种寄存器的值等,当这个任务再次被调度执行时,系统可以从栈中恢复这些状态,让任务感觉就像从来没有被打断过一样,继续愉快地运行。

2. 如何为任务分配栈空间?

在 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);             // 任务控制块

静态分配的好处是避免了动态内存分配的开销,但需要在代码中明确管理栈空间。

3. 栈空间大小如何确定?

栈空间大小的设置是一个需要权衡的问题。如果栈空间太小,可能会导致栈溢出;如果太大,则会浪费宝贵的内存资源。通常,可以通过以下方法来确定栈大小:

  1. 经验法:根据任务的复杂度,分配一个合理的栈大小。例如,简单的任务可以分配 128 字,复杂的任务可能需要 256 字或更多。

  2. 调试法:在开发过程中,通过调试工具查看任务的实际栈使用情况。FreeRTOS 提供了 uxTaskGetStackHighWaterMark() 函数,可以用来获取任务栈的最高使用水位。

4. 栈溢出检测

栈溢出是导致系统崩溃的常见原因之一。为了避免栈溢出,FreeRTOS 提供了两种检测机制:

方法 1:检查栈指针

在任务切换时,FreeRTOS 会检查栈指针是否在合法的栈空间范围内。如果栈指针超出范围,则触发栈溢出钩子函数。

方法 2:检查栈填充值

在任务创建时,FreeRTOS 会用一个已知值填充栈空间。在任务切换时,检查栈空间的最后 16 字节是否被覆盖。如果被覆盖,则触发栈溢出钩子函数。

5. 栈空间的管理

FreeRTOS 对任务栈的管理是独立于标准 C 的 malloc()free() 的。任务的栈空间在任务创建时分配,在任务删除时释放。

6. 实际案例:任务栈的使用

假设我们有一个任务,用于控制 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);

你可能感兴趣的:(单片机,嵌入式硬件,操作系统,freertos,嵌入式)