当前位置: 首页 > news >正文

FreeRTOS学习笔记(10):任务创建方式详解:静态创建与动态创建

任务创建方式详解:静态创建与动态创建

一、静态创建与动态创建的区别及优劣势

在 FreeRTOS 中,创建任务有两种方式:静态创建动态创建。本文将通过实际代码示例,详细对比这两种方式的区别、优劣势,并分别演示单任务和多任务的实现过程。

1.1 区别

对比项 静态创建 (xTaskCreateStatic) 动态创建 (xTaskCreate)
内存来源 用户提供的静态数组(全局或局部) 系统堆(由 pvPortMalloc 分配)
任务栈 显式定义 StackType_t 数组 自动从堆中分配
任务控制块 (TCB) 显式定义 StaticTask_t 结构体 自动从堆中分配
灵活性 需预先分配所有内存,无法动态释放 可动态创建/删除,内存自动管理
安全性 内存确定,无堆碎片问题 可能产生堆碎片,需合理配置堆大小
适用场景 硬实时系统、内存受限、安全关键应用 通用应用、任务数量动态变化的场景

1.2 优劣势分析

静态创建优点

  • 确定性:内存分配在编译期确定,无运行时分配失败风险。
  • 无堆碎片:不依赖堆管理器,避免内存碎片。
  • 便于调试:内存地址固定,方便查看栈使用情况。

静态创建缺点

  • 灵活性差:任务数量、栈大小在编译时固定,无法动态调整。
  • 内存利用率低:需要预先为每个任务分配最大可能栈空间,可能造成浪费。

动态创建优点

  • 灵活:可随时创建/删除任务,适合不确定数量的场景。
  • 内存利用率高:任务栈按需分配,删除后内存可回收。

动态创建缺点

  • 不确定性:堆分配可能失败(尤其是内存碎片时)。
  • 额外开销:需要堆管理函数,且存在内存碎片风险。

二、硬件初始化

无论使用哪种创建方式,硬件初始化都是通用的。通常包括:

  • 中断优先级分组
  • LED GPIO 初始化
  • 串口初始化(用于调试输出)
static void BSP_Init(void)
{/* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围:0~15 */NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);/* LED 初始化 */LED_GPIO_Config();/* 串口初始化 */Debug_USART_Config();
}

[请插入图片:硬件初始化后的板子状态,如LED亮起、串口输出]


三、创建单任务——SRAM静态内存方式

本节以第一个 main.c 为例,演示如何静态创建一个 LED 闪烁任务。

3.1 定义任务函数

任务函数是一个无限循环,通常包含延时函数以让出 CPU。

static void LED_Task(void* parameter)
{   while (1){LED1_ON;vTaskDelay(500);   /* 延时500个tick */printf("LED_Task Running, LED1_ON\r\n");LED1_OFF;     vTaskDelay(500);   printf("LED_Task Running, LED1_OFF\r\n");}
}

3.2 空闲任务与定时器任务堆栈函数实现

当使用静态创建时,需要为系统自动创建的空闲任务和定时器任务(如果启用)提供内存。这些函数在 task.c 中被调用,必须由用户实现。

/* 空闲任务堆栈及TCB */
static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE];
static StaticTask_t Idle_Task_TCB;/* 定时器任务堆栈及TCB(如果 configUSE_TIMERS == 1) */
static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];
static StaticTask_t Timer_Task_TCB;void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize)
{*ppxIdleTaskTCBBuffer = &Idle_Task_TCB;*ppxIdleTaskStackBuffer = Idle_Task_Stack;*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize)
{*ppxTimerTaskTCBBuffer = &Timer_Task_TCB;*ppxTimerTaskStackBuffer = Timer_Task_Stack;*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}

3.3 定义任务栈

为 LED 任务分配静态数组作为任务栈。

/* LED任务堆栈 */
static StackType_t LED_Task_Stack[128];

3.4 定义任务控制块

定义 StaticTask_t 类型的结构体变量,用于存放 TCB。

/* LED任务控制块 */
static StaticTask_t LED_Task_TCB;

3.5 静态创建任务

使用 xTaskCreateStatic() 创建任务,并获取任务句柄。

LED_Task_Handle = xTaskCreateStatic((TaskFunction_t)LED_Task,    /* 任务函数 */(const char*   )"LED_Task",   /* 任务名称 */(uint32_t      )128,          /* 任务栈大小 */(void*         )NULL,         /* 参数 */(UBaseType_t   )4,            /* 优先级 */(StackType_t*  )LED_Task_Stack,   /* 栈数组 */(StaticTask_t* )&LED_Task_TCB);   /* TCB结构体 */

3.6 启动任务

调用 vTaskStartScheduler() 启动调度器。

vTaskStartScheduler();

3.7 静态创建任务 main.c 关键代码

完整的 main.c 框架(静态创建)如下:

int main(void)
{BSP_Init();printf("STM32-FreeRTOS!\r\n");/* 创建 AppTaskCreate 任务(用于创建其他任务) */AppTaskCreate_Handle = xTaskCreateStatic((TaskFunction_t)AppTaskCreate,(const char*   )"AppTaskCreate",(uint32_t      )128,(void*         )NULL,(UBaseType_t   )3,(StackType_t*  )AppTaskCreate_Stack,(StaticTask_t* )&AppTaskCreate_TCB);if (NULL != AppTaskCreate_Handle){vTaskStartScheduler();   /* 启动调度器 */}while(1); /* 不会执行到这里 */
}static void AppTaskCreate(void)
{taskENTER_CRITICAL();/* 创建 LED 任务(静态) */LED_Task_Handle = xTaskCreateStatic((TaskFunction_t)LED_Task,(const char*   )"LED_Task",(uint32_t      )128,(void*         )NULL,(UBaseType_t   )4,(StackType_t*  )LED_Task_Stack,(StaticTask_t* )&LED_Task_TCB);if (NULL != LED_Task_Handle)printf("LED_Task create success!\r\n");elseprintf("LED_Task create fail!\r\n");vTaskDelete(AppTaskCreate_Handle);   /* 删除自身 */taskEXIT_CRITICAL();
}

[请插入图片:静态创建任务实验现象——LED 按 500ms 闪烁,串口输出对应信息]


四、创建单任务——SRAM动态内存方式

动态创建任务使用 xTaskCreate(),任务栈和 TCB 由 FreeRTOS 的堆管理器自动分配。

4.1 动态内存空间的堆从哪里来

FreeRTOSConfig.h 中需要配置堆大小:

#define configTOTAL_HEAP_SIZE    ((size_t)(36 * 1024))   /* 36KB 堆 */

同时选择一种内存管理方案(如 heap_4.c),将其添加到工程中。

4.2 定义任务函数

与静态创建相同,此处略(参考 3.1 节)。

4.3 定义任务栈

动态创建无需显式定义栈数组,栈由堆分配。

4.4 定义任务控制块指针

只需定义一个 TaskHandle_t 类型的指针变量,用于接收任务句柄。

static TaskHandle_t LED_Task_Handle = NULL;

4.5 动态创建任务

使用 xTaskCreate() 创建任务。

xReturn = xTaskCreate((TaskFunction_t)LED_Task,      /* 任务函数 */(const char*    )"LED_Task",    /* 任务名称 */(uint16_t       )512,           /* 栈大小(单位:字) */(void*          )NULL,          /* 参数 */(UBaseType_t    )2,             /* 优先级 */(TaskHandle_t*  )&LED_Task_Handle);  /* 任务句柄 */

4.6 启动任务

与静态创建相同,调用 vTaskStartScheduler()

4.7 对比动态创建与静态创建

项目 静态创建 动态创建
任务栈 显式定义数组 无需定义
TCB 显式定义结构体 无需定义
创建函数 xTaskCreateStatic xTaskCreate
内存来源 静态数组
额外函数 需提供空闲/定时器任务内存 无需

[请插入图片:动态创建任务实验现象——与静态创建现象一致,但内存分配方式不同]


五、创建多任务——SRAM动态内存方式(以第二个 main.c 为例)

第二个 main.c 演示了动态创建多个任务(LED1 和 LED2),并展示了不同优先级下的调度效果。

5.1 多任务创建代码

AppTaskCreate 任务中创建两个任务:

static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;taskENTER_CRITICAL();/* 创建 LED1 任务(优先级 2) */xReturn = xTaskCreate((TaskFunction_t)LED1_Task,(const char*    )"LED1_Task",(uint16_t       )512,(void*          )NULL,(UBaseType_t    )2,(TaskHandle_t*  )&LED1_Task_Handle);if (pdPASS == xReturn)printf("创建LED1_Task任务成功!\r\n");/* 创建 LED2 任务(优先级 3,高于 LED1) */xReturn = xTaskCreate((TaskFunction_t)LED2_Task,(const char*    )"LED2_Task",(uint16_t       )512,(void*          )NULL,(UBaseType_t    )3,(TaskHandle_t*  )&LED2_Task_Handle);if (pdPASS == xReturn)printf("创建LED2_Task任务成功!\r\n");vTaskDelete(AppTaskCreate_Handle);   /* 删除自身 */taskEXIT_CRITICAL();
}

5.2 任务函数实现

static void LED1_Task(void* parameter)
{   while (1){LED1_ON;vTaskDelay(500);printf("LED1_Task Running, LED1_ON\r\n");LED1_OFF;     vTaskDelay(500);printf("LED1_Task Running, LED1_OFF\r\n");}
}static void LED2_Task(void* parameter)
{   while (1){LED2_OFF;vTaskDelay(500);printf("LED2_Task Running, LED2_OFF\r\n");LED2_ON;     vTaskDelay(500);printf("LED2_Task Running, LED2_ON\r\n");}
}

5.3 实验现象与串口显示

  • LED 效果:LED1 和 LED2 交替闪烁(但由于优先级不同,高优先级任务会抢占低优先级任务,但两者都含有 vTaskDelay,所以调度器会在延时期间切换,实际现象仍为交替闪烁)。
  • 串口输出
这是一个[野火]-STM32全系列开发板-FreeRTOS-动态创建多任务实验!
创建LED1_Task任务成功!
创建LED2_Task任务成功!
LED1_Task Running, LED1_ON
LED2_Task Running, LED2_OFF
LED1_Task Running, LED1_OFF
LED2_Task Running, LED2_ON
...

[请插入图片:串口调试助手显示的交替输出截图]
[请插入图片:开发板上 LED1 和 LED2 交替闪烁效果]


六、总结

  • 静态创建适用于内存确定、安全关键的场景,代码编写稍显繁琐,但运行可靠。
  • 动态创建更加灵活,适合任务数量变化、原型开发阶段,但需合理配置堆大小并留意内存碎片。
  • 实际开发中,可根据项目需求混合使用两种方式(例如,关键任务用静态,非关键任务用动态)。
  • 通过实验现象可观察到,无论采用哪种创建方式,任务都能按预期运行,体现了 FreeRTOS 调度器的稳定性。

[请插入图片:两种创建方式对比思维导图]


参考资料

  • FreeRTOS 官方文档:Memory Management
  • 野火《FreeRTOS 内核实现与应用开发实战指南》

希望本文能帮助你深入理解 FreeRTOS 任务创建的两种方式,并在实际项目中合理选用。

http://www.jsqmd.com/news/546613/

相关文章:

  • 3个核心突破:webSpoon企业级数据集成实战指南
  • 在UP-MOBNET-Ⅱ实验箱上玩转俄罗斯方块:从源码编译到U盘移植的保姆级教程
  • 颠覆PDF转换体验:Marker无缝实现25页/秒全场景文档格式精准迁移
  • 贵阳装修工作室怎么选?2026年最新专业评估与五强服务商推荐 - 2026年企业推荐榜
  • 2026上海企业增资扩股,这五家专业律师团队值得关注 - 2026年企业推荐榜
  • ArduinoMqtt:面向MCU的零堆内存同步MQTT客户端实现
  • 从气象API到网页展示:用Leaflet-velocity实现实时风场动画的保姆级教程
  • 告别杂乱农场:星露谷物语规划神器助你打造高效田园
  • 四川正规文武寄宿学校:武术夏令营学校/知名的武术学校/专业学武术的学校/乐山文礼武校/乐山武术学校/选择指南 - 优质品牌商家
  • 从‘暴力匹配’到KMP优化:用nextval数组提升字符串查找效率的实战图解
  • 深入解析NAND Flash基础操作与系统集成——从阵列结构到多Die协同
  • 5分钟搞定!RevokeMsgPatcher 2.1:Windows平台微信QQ防撤回终极解决方案
  • 2026年污水处理工程厂家权威推荐榜:红膜储存水池/红膜沼气储存袋/红膜沼气池/肥水一体化工程/黑膜储存水池/选择指南 - 优质品牌商家
  • Anthropic 经济指数报告:学习曲线
  • MX28智能舵机RS485底层驱动开发实战
  • 2026年高精度温控仪市场深度解析:五大技术实力派源头厂家横向对比 - 2026年企业推荐榜
  • 别再死记硬背了!用大白话+动图搞懂惯性导航里的‘比力方程’和‘哥氏加速度’
  • Linux initramfs深度解析: 从内核启动到根文件系统的桥梁(3)
  • 衡水地区玻璃钢夹砂管道怎么选?认准这3大标准,源头厂家不踩坑! - 2026年企业推荐榜
  • Mac本地AI绘画解决方案:Mochi Diffusion完全指南
  • 东佑达步进电缸控制器TC100的labview控制vi,可以通过RS485控制电缸运动
  • 2026年奶茶创业新观察:为何“实力系统”比“网红单品”更持久? - 2026年企业推荐榜
  • AceCommon:Arduino嵌入式零堆分配轻量C++工具库
  • 语言边界消融术:当Obsidian插件遇见i18n的魔法
  • 2026色母机选购指南:数据驱动下的市场格局与TOP5服务商深度测评 - 2026年企业推荐榜
  • OpenClaw怎么部署?OpenClaw天翼云新手4分钟安装及使用教程【最新版】
  • 2026年长春APP开发服务商综合实力解析与选型指南 - 2026年企业推荐榜
  • 如何在3分钟内构建你的专属在线PPT制作工具
  • 2026年AI大模型领域薪资爆发:抓住五大热门岗位,非常详细收藏我这一篇就够了!
  • 告别手动配置困境:LivePortrait人像动画工具全平台部署终极指南