FreeRTOS 任务相关 API 函数

一、任务创建与删除(核心生命周期管理)

这是使用 FreeRTOS 的起点,文档介绍了两种创建方式和一种删除方式。

1. 动态创建:xTaskCreate() ------ 最常用
  • 内存来源 :任务控制块(TCB)和任务堆栈所需内存自动 从 FreeRTOS 堆(ucHeap)中分配。
  • 前提条件
    • 必须提供内存管理文件(如 heap_4.c)。
    • configSUPPORT_DYNAMIC_ALLOCATION 必须为 1(默认就是 1)。
  • 关键参数注意
    • usStackDepth:堆栈大小,单位是字(word),不是字节。在 32 位 MCU(如 STM32)上,实际字节数要乘以 4。
    • pxCreatedTask:用于返回任务句柄,后续挂起、删除、改变优先级等操作都需要它。
  • 返回值 :成功返回 pdPASS,失败返回 errCOULD_NOT_ALLOCATE_REQUESTED_MEMORY(通常是堆空间不足)。
2. 静态创建:xTaskCreateStatic() ------ 更可控,但稍复杂
  • 内存来源 :任务堆栈和控制块的内存完全由用户提供(通常定义为全局数组)。
  • 使用前提 :宏 configSUPPORT_STATIC_ALLOCATION 必须设为 1
  • 额外要求 :当启用静态分配后,用户必须实现两个钩子函数 ,为空闲任务和定时器服务任务提供静态内存:
    • vApplicationGetIdleTaskMemory()
    • vApplicationGetTimerTaskMemory()
  • 适用场景:需要将任务内存放到特定区域(如内部 SRAM、外部 SDRAM),或者对动态分配的不确定性(碎片、失败)有顾虑时使用。在资源极其紧张或安全关键的系统中,静态方法更受青睐。
3. 受限任务创建:xTaskCreateRestricted() ------ 用于带 MPU 的芯片
  • 仅在 MCU 带有**内存保护单元(MPU)**时使用,可以为任务设置内存访问权限,提高系统安全性(如隔离用户任务和系统任务)。
4. 删除任务:vTaskDelete()
  • 参数为待删除任务的句柄。传入 NULL 表示删除任务自身。
  • 关键注意事项(高频踩坑点)
    • 若任务是动态创建的,删除后其占用的 TCB 和堆栈内存会在空闲任务中释放 。因此,删除任务后必须给空闲任务运行机会 (比如调用 vTaskDelay(1)taskYIELD()),否则内存无法被回收,造成泄漏。
    • 用户自行分配的内存,必须自行释放 。如果任务内部用 pvPortMalloc() 申请了内存,删除前必须调用 vPortFree(),否则会导致内存泄漏。

二、任务挂起与恢复(暂停/继续,不丢数据)

当你需要暂停某个任务,并在一段时间后从暂停点继续执行时,使用挂起和恢复机制,而非删除+重建(后者会丢失任务内部的变量状态)。

1. 挂起:vTaskSuspend()
  • 将任务置于挂起态(Suspended),该任务将永远不会被调度,直到被恢复。
  • 参数为任务句柄,传入 NULL 表示挂起自身。
2. 恢复(任务级):vTaskResume()
  • 任务上下文中恢复一个被挂起的任务。
  • 只能恢复通过 vTaskSuspend() 挂起的任务。
3. 恢复(中断级):xTaskResumeFromISR()
  • 在**中断服务函数(ISR)**中恢复任务。
  • 特殊返回值
    • 返回 pdTRUE:表示被恢复的任务优先级等于或高于 当前被打断的任务,这意味着退出 ISR 后必须进行上下文切换 (通常调用 portYIELD_FROM_ISR())。
    • 返回 pdFALSE:不需要立即切换。
  • vTaskResume() 的区别:此函数专用于中断环境,且会返回是否需要切换的指示,这是中断安全的必要设计。

三、实验例程深度解析

通过两个完整实验,清晰地展示了上述 API 的标准用法模式。

1. 标准工程结构模式(推荐学习)
  • 步骤 1 :在 main() 中完成硬件初始化。
  • 步骤 2在调度器启动前 ,使用 xTaskCreate() 创建一个"起始任务"(start_task)。
  • 步骤 3 :调用 vTaskStartScheduler() 启动调度器。
  • 步骤 4 :在 start_task 的任务函数中,创建所有其他应用任务、队列、信号量等内核对象
  • 步骤 5 :创建完成后,调用 vTaskDelete(NULL) 删除 start_task 自身,释放其占用的内存。

这种模式的好处是:将系统初始化与业务任务分离,结构清晰,且所有资源创建都在一个任务上下文中完成,便于管理。

2. 动态创建实验(4.2 节)
  • 核心逻辑
    • 任务 2 的优先级(3)高于任务 1(2),因此一开始任务 2 会优先执行。
    • 任务 1 每运行一次,计数加 1。当运行满 5 次后,调用 vTaskDelete(Task2Task_Handler) 删除任务 2。
    • 删除后,任务 2 不再运行,LED1 停止闪烁,串口也不再打印任务 2 的信息。
  • 关键观察点 :删除任务后,空闲任务会回收任务 2 的内存(前提是给了空闲任务运行时间,例程中通过 vTaskDelay 让出了 CPU)。
3. 静态创建实验(4.3 节)
  • 与动态实验功能完全相同,但所有任务的堆栈(StackType_t Task1TaskStack[128])和控制块(StaticTask_t Task1TaskTCB)都由用户显式定义。
  • 需注意 :必须实现 vApplicationGetIdleTaskMemoryvApplicationGetTimerTaskMemory,否则编译会报链接错误(文档图 4.3.1 所示)。
4. 挂起/恢复实验(4.5 节)
  • 任务设计

    • key_task:轮询按键状态。
    • task1_tasktask2_task:执行周期任务并显示运行次数。
  • 按键功能

    • KEY2:挂起任务 2(vTaskSuspend(Task2Task_Handler))。
    • KEY1:恢复任务 1(vTaskResume(Task1Task_Handler))。
    • WKUP:挂起任务 1。
    • KEY0(中断方式):在外部中断 ISR 中调用 xTaskResumeFromISR() 恢复任务 2。
  • 中断中的关键代码模式

    c 复制代码
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xHigherPriorityTaskWoken = xTaskResumeFromISR(Task2Task_Handler);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken); // 根据返回值决定是否切换

    这是所有在 ISR 中使用 FreeRTOS API 的标准范式,务必牢记。

  • 实验结论 :挂起任务后,其运行计数值保持不变;恢复后继续累加,证明了挂起不会丢失任务内部状态。


四、综合对比与选择建议

功能 API 函数 适用场景 注意事项
动态创建 xTaskCreate() 绝大多数常规应用,简单快捷 需确保堆空间足够;内存释放依赖空闲任务
静态创建 xTaskCreateStatic() 内存敏感、安全关键、需固定内存地址的场景 需用户提供堆栈和TCB;需实现两个系统钩子函数
删除任务 vTaskDelete() 任务不再需要时,回收资源 动态任务的内存由空闲任务回收;用户内存需手动释放
挂起任务 vTaskSuspend() 临时停止任务,保留上下文和数据 挂起后任务不再被调度,直到被恢复
恢复(任务级) vTaskResume() 在任务代码中恢复另一个任务 仅用于任务上下文
恢复(中断级) xTaskResumeFromISR() 在ISR中恢复任务,实现中断触发任务运行 必须检查返回值并决定是否切换上下文

五、总结与最佳实践

  1. 任务创建模式:强烈推荐"起始任务 + 动态创建"的模式,兼顾了代码清晰度和灵活性。除非有特殊内存要求,否则无需使用静态创建。
  2. 内存管理 :务必理解动态创建的任务删除后,内存不会立即释放,而是交给空闲任务。因此,频繁创建删除任务的系统,要保证空闲任务有足够机会运行,并注意堆碎片问题(使用 heap_4.c 可缓解)。
  3. 中断安全 :任何时候在 ISR 中调用 FreeRTOS API(如 xTaskResumeFromISRxQueueSendFromISR),都要遵循"检查返回值 → 调用 portYIELD_FROM_ISR"的模式,确保调度器能及时响应高优先级任务。
  4. 调试手段 :实验代码中大量使用 printf 和 LCD 显示,这是学习 RTOS 行为的有力工具。在实际产品中,可用更轻量的日志方式代替。

掌握这些任务 API,就掌握了 FreeRTOS 任务管理的核心。后续的信号量、队列、事件组等,都是在此基础上构建的任务间通信与同步机制。

如果你对某个特定点(比如中断中的上下文切换细节,或 heap_4.c 的内存管理算法)还想深入了解,可以随时提出。

相关推荐
ysu_03141 小时前
【创作128天纪念】从新手村踏上征途
c语言
c238561 小时前
C/C++每日一练21
c语言·开发语言·c++
学生哥-_-2 小时前
STC8H1K08单片机在Keil环境下各外设的配置及使用方法
单片机·嵌入式硬件·mongodb
c2385610 小时前
MySQL 基础用法(上):库表管理与数据增删改
c语言·数据库·c++·mysql
李永奉10 小时前
C语言-嵌入式开发C语言常用预处理指令合集
c语言·开发语言
YuforiaCode12 小时前
第十六届蓝桥杯 2025 C/C++组 数列差分
c语言·c++·蓝桥杯
捷瑞电子工坊15 小时前
C语言二维数组在内存中是如何存储的?
c语言·二维数组·内存存储·数组底层
lilili也15 小时前
visual studio add matlab
c语言·c++
zlinear数据采集卡16 小时前
ZLinear DABM-D223 通信协议与软件开发全解析:从 USB CDC 到 DDS 波形输出
arm开发·嵌入式硬件·fpga开发·开源