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

FreeRTOS任务基础知识

FreeRTOS任务系统

  • 前言
  • 前后台系统
  • 抢占式多任务系统
  • 任务和协程
    • 任务
    • 协程
  • 任务状态
    • 不同任务状态之间的转换关系
  • 任务优先级
    • 任务优先级权衡
    • 任务的优先级可以相同吗
  • 任务函数
  • 任务控制块
    • 1. TCB的作用
    • 2. TCB的核心成员
    • 3. TCB的关键功能
      • 3.1 任务状态管理
      • 3.2 上下文切换
      • 3.3 优先级管理
    • 4. TCB的创建与初始化
      • 4.1 动态创建(xTaskCreate)
      • 4.2 静态创建(xTaskCreateStatic)
    • 5. TCB与任务句柄的关系
  • 任务栈
    • 栈大小

前言

本文介绍了FreeRTOS的任务系统,包括任务的含义、状态、优先级、任务控制块,以及任务栈。并强调了在任务中调用vTaskDelay函数的话,任务状态会经历怎样的改变。本文属于任务基础知识。

前后台系统

在主函数中,运行while(1)死循环,里面轮询执行一些代码,通过中断执行一些需要立即处理的事情。这时候,while死循环是后台程序,而中断服务程序是前台程序。
在这里插入图片描述
上图引自《STM32F4 FreeRTOS开发手册》

抢占式多任务系统

任务调度器负责决定哪个任务先执行,哪个任务后执行。FreeRTOS是抢占式实时多任务系统,下图描述了抢占式系统的执行流程。
在这里插入图片描述
上图引自《STM32F4 FreeRTOS开发手册》

任务和协程

任务(Task)和协程(co-Routine)都可以在应用中使用,二者的API不同,不能通过队列或信号量将数据在二者之间传递。区别在于协程是为资源很少的MCU准备,开销小。

任务

RTOS调度器会开启和关闭每一个任务,同一时间只有一个任务栈执行,每个任务都有自己的堆栈,任务退出执行的时候需要压栈,将寄存器和堆栈内容保存好,等下一次运行的时候就出栈,将上下文环境恢复成跟上次退出的时候完全一样的。
这种情况下,每个任务是独立的,支持优先级设置,但缺点是由于每个任务都有自己的堆栈导致RAM开销大。如果要使用抢占,就必须考虑重入的问题。

协程

MCU的资源越来越多,导致协程的使用几乎没有了。所有协程使用同一个堆栈,使用合作式调度器,可以在抢占式调度器中使用协程。协程是通过宏定义实现的。为降低RAM消耗有很多限制。

任务状态

  • 运行态
    处于运行状态的任务。
  • 就绪态
    已经准备就绪,可以运行的任务。但它没有处于运行态,因为有更高优先级或者相同优先级的任务在运行。
  • 阻塞态
    正在等待某个外部事件的任务就是处于阻塞态。比如在任务中调用了vTaskDelay(),就会等到延时完成。任务在等待队列、信号量、事件组、通知或互斥量的时候也会进入。但有超时事件,一旦到达超时时间,即便没有等到需要的事件也会退出阻塞态,进入就绪态。
  • 挂起态
    跟阻塞态相比,没有超时时间。任务进入挂起态调用vTaskSuspend(),任务退出挂起态调用vTaskResume()
  • 删除态
    任务已被删除,等待内存回收。
状态核心特点触发方式
运行态正在占用 CPU 执行调度器选中
就绪态准备执行,等待高优先级任务释放CPU 任务创建、延时结束、资源释放
阻塞态等待事件(延时、信号量、队列等)vTaskDelay()、xQueueReceive()等
挂起态暂停执行,需显式恢复vTaskSuspend()、vTaskResume()
删除态任务已删除,等待内存回收vTaskDelete()

不同任务状态之间的转换关系

在这里插入图片描述
注意
在任务中调用vTaskDelay(),会直接使这个任务进入阻塞态,把CPU的使用权交出来,调度器会让其他处于就绪态,并且优先级最高的任务来处于运行态,占据CPU。直到延时完成,它会变成就绪态,等待调度器选中它来运行。
并不是让它在就绪态,占用CPU而等待延时结束。

关于vTaskDelay常见误区澄清:

  • 误区 1:认为vTaskDelay()会让任务在就绪队列中等待。
  • 纠正:任务会进入阻塞队列,而非就绪队列。
  • 误区2:认为延时期间任务仍占用 CPU 资源。
  • 纠正:阻塞态任务完全释放 CPU,由其他就绪任务执行。
  • 误区3:混淆vTaskDelay()与忙等待(如for(;;)循环)。
  • 纠正:vTaskDelay()是无负载延时,忙等待会持续占用 CPU。

vTaskDelay()的核心作用:使任务主动放弃 CPU,进入阻塞态,等待指定时间后再参与调度。
状态转换路径:
运行态 → vTaskDelay() → 阻塞态 → 延时到期 → 就绪态 → 调度器选中 → 运行态。

合理使用vTaskDelay()是实现任务协作和降低系统功耗的关键,它能确保高优先级任务在延时期间仍可执行。

任务优先级

每个任务的优先级在0~(configMAX_PRIORITIES-1)。
configMAX_PRIORITIESFreeRTOSConfig.h 中定义,指定系统支持的最大优先级数值(从 0 开始),用于定义系统支持的最大任务优先级数量,它直接影响任务调度器的行为和内存使用效率。而configMAX_PRIORITIES这个宏的大小,用户可以根据需要自行设定。

#define configMAX_PRIORITIES			( 5 )

任务的优先级数字越小,优先级越低。所以0的优先级最低,configMAX_PRIORITIES-1优先级最高。空闲任务优先级为0。
跟STM32的中断优先级相反

任务优先级权衡

参数作用配置建议
configMAX_PRIORITIES定义系统支持的最大任务优先级数量根据实际需求设置,通常 5~32
任务的优先级范围0(最低)到 configMAX_PRIORITIES-1(最高)避免使用过多优先级层级
内存影响优先级数量越多,调度器数据结构占用内存越大够用即可,避免浪费

合理配置 configMAX_PRIORITIES 是平衡系统性能与资源消耗的关键,需根据应用复杂度和实时性要求进行优化。

任务的优先级可以相同吗

#define configUSE_TIME_SLICING 1

configUSE_TIME_SLICING 等于1的话,允许不同任务的优先级相同。这样的任务会使用时间片轮转调度器运行。

任务函数

任务函数就是实现这个任务想要做什么事情的函数。比如:

void led0_task(void * pxParameters)
{while(1){LED0 = ~LED0;vTaskDelay(500);}
}

这个函数返回值是void,入参是void*类型的指针。任务函数中,有一个死循环,不允许退出任务!死循环里可以执行具体的代码,完成这个任务需要做的事。
如果需要从这个任务函数中退出,就一定要执行vTaskDelete(NULL)删除此任务。比如:

void start_task(void * pxParameters)
{taskENTER_CRITICAL();//进入临界区//创建LED0任务xTaskCreate((TaskFunction_t) interrupt_task,(const char*) "interrupt_task",(uint16_t) INTERRUPT_STK_SIZE,(void*) NULL,(UBaseType_t)INTERRUPT_TASK_PRIO,(TaskHandle_t*) &INTERRUPTTask_Handler);vTaskDelete(StartTask_Handler);//删除开始任务taskEXIT_CRITICAL();//推出临界区
}

这个任务函数中,没有死循环,只有创建了另一个任务,然后删除了start_task任务。

任务控制块

在FreeRTOS中,对于每一个任务都有一些属性需要存储,这些属性整合到一个结构体中,就叫任务控制块,名为TCB_t,是管理任务的核心数据结构,每个任务都有唯一的TCB实例。它存储了任务的所有状态信息,是调度器进行任务管理的基础。以下是详细解析:

1. TCB的作用

  • 保存任务状态:如任务当前状态(运行/就绪/阻塞)、优先级、栈指针等。
  • 实现任务切换:调度器通过TCB恢复或保存任务上下文。
  • 管理任务资源:如任务句柄、栈空间、挂起的事件等。

2. TCB的核心成员

TCB的定义位于task.h中,主要包含以下字段(简化版):

typedef struct tskTaskControlBlock {volatile StackType_t *pxTopOfStack;  // 栈顶指针,指向当前栈顶位置ListItem_t xStateListItem;           // 状态列表项,用于就绪/阻塞队列ListItem_t xEventListItem;           // 事件列表项,用于等待特定事件UBaseType_t uxPriority;              // 任务优先级StackType_t *pxStack;                // 栈底指针,指向任务栈的起始地址char pcTaskName[ configMAX_TASK_NAME_LEN ]; // 任务名称// ... 其他成员(如事件标志、栈溢出检测等)
} tskTCB;

3. TCB的关键功能

3.1 任务状态管理

  • 通过xStateListItem将任务链接到不同队列:
    • 就绪队列:所有就绪态任务的TCB通过xStateListItem链接。
    • 阻塞队列:等待事件的任务TCB通过xEventListItem链接到对应事件队列。

3.2 上下文切换

  • 保存上下文:任务切换时,当前寄存器值被保存到任务栈,栈顶地址更新到pxTopOfStack
  • 恢复上下文:调度器选中任务后,从pxTopOfStack恢复寄存器值,使任务继续执行。

3.3 优先级管理

  • uxPriority字段存储任务优先级,调度器据此决定任务执行顺序。
  • 支持动态优先级调整(vTaskPrioritySet())。

4. TCB的创建与初始化

4.1 动态创建(xTaskCreate)

TaskHandle_t xTaskCreate(TaskFunction_t pxTaskCode,     // 任务函数const char * const pcName,     // 任务名称configSTACK_DEPTH_TYPE usStackDepth, // 栈深度void *pvParameters,            // 任务参数UBaseType_t uxPriority,        // 优先级TaskHandle_t *pxCreatedTask    // 任务句柄(即TCB指针)
);

4.2 静态创建(xTaskCreateStatic)

// 需要预先定义栈空间和TCB内存
StackType_t xStack[ configMINIMAL_STACK_SIZE ];
StaticTask_t xTaskBuffer;TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode,const char * const pcName,uint32_t ulStackDepth,void *pvParameters,UBaseType_t uxPriority,StackType_t *puxStackBuffer,   // xStack,静态栈空间StaticTask_t *pxTaskBuffer     // &xTaskBuffer,静态TCB内存
);

5. TCB与任务句柄的关系

  • 任务句柄(TaskHandle_t):本质是指向TCB的指针。
  • 通过句柄可操作任务:
    TaskHandle_t xTaskHandle;// 创建任务并获取句柄
    xTaskCreate(vTaskFunction, "Task", 1024, NULL, 1, &xTaskHandle);// 使用句柄操作任务
    vTaskSuspend(xTaskHandle);     // 挂起任务
    vTaskResume(xTaskHandle);      // 恢复任务
    vTaskPrioritySet(xTaskHandle, 2); // 修改优先级
    

任务栈

任务切换时需要将当前运行的任务保存在此任务的栈中,等下次运行的时候,从该任务的堆栈中恢复现场,使其接着上次运行的位置继续运行。
如果使用xTaskCreate()创建任务,即动态方法,任务堆栈会自动创建;但如果使用xTaskCreateStatic()创建任务,即静态创建,就需要我们自行定义任务堆栈。将堆栈首地址作为函数入参传递给xTaskCreateStatic函数,前面有介绍,这里回顾一下。
在这里插入图片描述

栈大小

栈的数据类型是StackType_t,本质上是uint32_t。

#define portSTACK_TYPE	uint32_t
typedef portSTACK_TYPE StackType_t;

如果我们定义栈深度是100,那么实际的栈大小是400字节。

相关文章:

  • VLLM : RuntimeError: NCCL error: invalid usage
  • RT_Thread——线程管理(下)
  • 高端性能封装正在突破性能壁垒,其芯片集成技术助力人工智能革命。
  • window 显示驱动开发-如何查询视频处理功能(三)
  • 从零手写Java版本的LSM Tree (八):LSM Tree 主程序实现
  • 华为云Flexus+DeepSeek征文 | MaaS平台避坑指南:DeepSeek商用服务开通与成本控制
  • HTML5实现简洁的体育赛事网站源码
  • Nosql之Redis集群
  • 多元隐函数 偏导公式
  • 【微服务基石篇】服务间的对话:RestTemplate、WebClient与OpenFeign对比与实战
  • 我的世界Java版1.21.4的Fabric模组开发教程(十二)方块状态
  • VRRP(虚拟路由冗余协议)深度解析
  • API网关Envoy的鉴权与限流:构建安全可靠的微服务网关
  • 算法思想之广度优先搜索(BFS)及示例(亲子游戏)
  • yolo模型精度提升策略
  • OpenHarmony标准系统-HDF框架之I2C驱动开发
  • Gemini 2.5 Pro (0605版本) 深度测评与体验指南
  • 如何将联系人从 iPhone 转移到 Android
  • 初探 OpenCV for Android:利用官方示例开启视觉之旅
  • 计算机技术、互联网与 IT 前沿:量子计算、Web3.0 等趋势洞察及行业应用
  • 网站建设的安全可行性/万网域名官网
  • 台州找人做网站/2022新闻热点10条
  • wordpress js篡改/百度推广优化
  • 免费做app网站/seo引擎优化专员
  • 淮安哪里有做网站的/google play应用商店
  • 网站后台登陆路径/品牌公关公司