FreeRTOS任务概述

FreeRTOS是一个实时操作系统,文章详细介绍了任务的概念,包括任务、多任务的定义以及任务的运行、准备就绪、阻塞和挂起四种状态。接着,重点讲解了FreeRTOS中的任务优先级,调度策略,如抢占式调度和时间片轮询。任务创建的细节,如xTaskCreate函数的使用,任务堆栈的作用以及任务句柄和任务控制块的概念。最后,提到了静态内存分配在任务创建中的应用,并指出协程在资源有限的处理器上的使用情况。
摘要由CSDN通过智能技术生成

一、任务的概念

1. 什么是任务

   初学单片机运行的是点灯程序,点灯就是一个任务。将要完成的一系列事情流程化从而可以交给计算机去完成,这就是任务。那初始化程序是不是任务呢,当然是了,初始化也是具有流程的,计算机同样也可以处理这个初始化流程。当然这是基于单片机来讲,老板交给你的任务不能和这个混为一谈。

   在以往的程序编写中都是在main函数中编写简单的处理程序,在while之前编写初始化程序,在while中编写任务轮询,比如说按键检测,显示控制,led灯控制等需要不断去刷新的事件。
在这里插入图片描述

2. 什么是多任务

   相比while的轮询,多任务更偏向于“并发”执行,但又不是真正意义上的多个任务同时执行。而是讲每一个任务都运行极短的时间,在看上去就像是多个任务在同时执行一样。(大部分单片机就一个核心实现不了真正意义上的同时执行)

3. 任务的状态

在这里插入图片描述

图片来源于FreeRTOS官网

运行

   当任务实际执行时(也就是说处理器正被这个任务占有),它被称为处于运行状态。(如果运行 RTOS 的处理器只有一个内核, 那么在任何给定时间内都只能有一个任务处于运行状态。)

准备就绪

  准备就绪任务指那些能够执行(它们不处于阻塞或挂起状态), 但目前没有执行的任务。目前没有执行是因为同等或更高优先级的不同任务已经处于运行状态

阻塞

  如果任务当前正在等待时间或外部事件,则该任务被认为处于阻塞状态。
例如,如果一个任务调用vTaskDelay()(看不懂就不要在意这个函数,后面会学习),它将被阻塞(被置于阻塞状态), 直到延迟结束一个时间事件(任务延时将把处理器占有退出交由其他任务占有)。 任务也可以通过阻塞来等待队列、信号量、事件组、通知或信号量 事件。 处于阻塞状态的任务通常有一个"超时"期,超时后任务将被超时,并被解除阻塞,即使该任务所等待的事件没有发生。
“阻塞”状态下的任务不使用任何处理时间,不能被选择进入运行状态。

挂起

  与“阻塞”状态下的任务一样, “挂起”状态下的任务不能被选择进入运行状态,但处于挂起状态的任务没有超时。 相反,任务只有在分别通过 vTaskSuspend() 和 xTaskResume() API 调用明确命令时才会进入或退出挂起状态。
  挂起是用户或特定条件下设定的不让任务运行(挂起是调用特定函数切换任务状态),而,阻塞是系统设定和管理的(阻塞是由调度器或事件切换任务状态

二、FreeRTOS任务

1. 任务优先级

  每个任务均被分配了从 0 到 ( configMAX_PRIORITIES - 1 ) 的优先级,其中的 configMAX_PRIORITIES 在 FreeRTOSConfig.h 中定义。
在这里插入图片描述
  如果所使用的单片机支持“前导零计数”类指令(用于单个指令中的任务选择,ARM 是支持单个指令计算的)且在 FreeRTOSConfig.h 中将 configUSE_PORT_OPTIMISED_TASK_SELECTION 设置为 1,则 configMAX_PRIORITIES 不得大于 32。 在所有其他情况下,configMAX_PRIORITIES 可以采取任何合理范围内的值,但出于 RAM 使用效率的原因,应保持在实际需要的最小值。
注意:下载的官方源文件FreeRTOSConfig.h中是没有configUSE_PORT_OPTIMISED_TASK_SELECTION这个宏的,需要自己在该头文件中加入
在这里插入图片描述
  低优先级数字表示低优先级任务。 空闲任务的优先级为零 (tskIDLE_PRIORITY),则空闲任务的优先级最低。

简述空闲任务
RTOS 调度器启动时,自动创建空闲任务,以确保始终存在一个能够运行的任务。 它以最低优先级创建,以确保如果有更高的优先级应用程序任务处于准备就绪状态,则不使用任何 CPU 时间。

  FreeRTOS 调度器可确保在就绪或运行状态的任务将始终在同样处于就绪状态的较低优先级任务之前获得处理器 (CPU) 时间 。 换句话说,被置于运行状态的任务始终是可运行的最高优先级任务。任意数量的任务可共用相同的优先级。 如果 configUSE_TIME_SLICING(在FreeRTOSConfig.h中) 未经定义, 或者如果 configUSE_TIME_SLICING 设置为 1(默认为1),则相同优先级的就绪状态任务将使用时间切片轮询调度方案共享可用的处理时间 。
注意:下载的官方源文件FreeRTOSConfig.h中是没有configUSE_TIME_SLICING这个宏的,需要自己在该头文件中加入
在这里插入图片描述

2. 任务调度

  调度算法是决定哪个 RTOS 任务应处于运行状态的软件程序。在任何给定时间, 每个处理器核心只能有一个任务处于运行状态。
FreeRTOS 调度算法主要有单核、非对称多核 (AMP)、和对称多核 (SMP)的三种 。这里只学习单核的任务调度策略。

  FreeRTOS 默认使用 固定优先级抢占式 调度策略,对同等优先级的任务执行 时间片 轮询调度

  • “固定优先级” 是指调度器不会更改任务的优先级, 但它可能会因优先级继承(暂时不去理解优先级继承,暂时认为就是某种机制导致让任务的优先级被提高了)而暂时提高任务的优先级。
  • “抢占式调度” 是指调度器始终运行优先级最高且可运行的 RTOS 任务, 无论任务何时能够运行。

例如,如果中断服务程序 (ISR) 更改了优先级最高且可运行的任务(任务优先级降低), 调度器会停止当前正在运行的低优先级任务(中断前的高优先级任务在中断中被修改为了低优先级)并启动高优先级任务——即使这发生在同一个时间片内。
此时,就说高优先级任务 “抢占”了低优先级任务。

  • “轮询调度” 是指具有相同优先级的任务轮流进入运行状态。
  • “时间片” 是指调度器会在每个 tick 中断上在同等优先级任务之间进行切换,tick 中断之间的时间构成一个时间片。tick 中断是 RTOS 用来衡量时间的周期性中断。

  总是运行优先级最高的可运行的任务的后果是, 永远不会进入“阻塞”或 “挂起”状态的高优先级任务会让所有任意的低优先级任务永久饥饿(低优先级任务永远抢占不了处理器的使用权) 。这就是为什么通常最好创建事件驱动型任务(高优先级任务有阻塞、延时的一种任务)的原因之一 。

例如,如果一个高优先级任务正在等待一个事件(延时或者条件等等), 那么它就不应处于该事件的循环(轮询)中,因为如果处于轮询中,它会一直运行,永远不进入“阻塞”或“挂起”状态。 反之,该任务应进入“阻塞” 状态来等待事件。可以使用众多 FreeRTOS 任务间通信和同步原语之一将事件发送给任务。接收到事件后, 优先级更高的任务会自动解除“阻塞”状态。高优先级任务处于“阻塞”状态时, 低优先级任务会运行。

配置 RTOS 调度策略

以下 FreeRTOSConfig.h 设置更改了默认调度行为:

  • configUSE_PREEMPTION

  如果 configUSE_PREEMPTION 设置为 0,则关闭“抢占”, 只有当运行状态的任务进入“阻塞”或“挂起”状态, 或运行状态任务调用 taskYIELD(), 或中断服务程序 (ISR) 手动请求上下文切换时,才会发生上下文切换。
在这里插入图片描述

  • configUSE_TIME_SLICING

  如果 configUSE_TIME_SLICING 设置为 0,则表示时间切片已关闭, 因此调度器不会在每个 tick 中断上在同等优先级的任务之间切换 。

这是在任务优先级部分自己手动加入的一个宏
在这里插入图片描述

3. 任务的创建

  任务的标准结构应该是下面这样的

void vATaskFunction( void *pvParameters )
{
  for( ;; )
  {
      //任务的应用程序应该写在这里
      //需要注意的是,这里的代码不应该是一直在运行的,要有延时或者条件使任务进入阻塞或挂起状态
  }
  //任务不应该有返回值
  //如果要删除这个任务,那么可以在任务中调用以下这段代码,来删除该任务
  vTaskDelete( NULL );
}

  应该关注到任务结构的形参是一个 void * 的参数,为什么是这样的呢?

  在FreeRTOS中void*被重新定义了一个名字叫做TaskFunction_t ,这可以在projdefs.h中可以找到,如下图:
在这里插入图片描述

  TaskFunction_t 类型是指返回 void 并将 void 指针作为其唯一参数的函数。所有实现任务的函数都应该是这种类型的函数。这种参数可用于将任何类型的信息传递到任务中。

创建任务的细节

1. 创建一个任务

  可以使用xTaskCreate()或 xTaskCreateStatic()来创建一个任务。

xTaskCreate函数

在这里插入图片描述

pxTaskCode: 任务函数
pcName:    任务的名称,是字符串类型
usStackDepth:任务堆栈大小
pvParameters:任务形参
uxPriority:  任务优先级
pxCreatedTask:任务句柄

任务句柄保存的是任务控制块的首地址。(原因见任务句柄的描述)

xTaskCreateStatic函数

在这里插入图片描述

与上一个任务创建函数前5个形参一样(看前面的xTaskCreate函数)
puxStackBuffer:任务堆栈,需要用户定义,把堆栈的首地址传入
pxTaskBuffer: 任务控制块

  在上面的图片中,发现这个函数是灰色的,表示不会被编译,也不能被调用。如果要使用这个函数需要将 FreeRTOS.h 文件中的 configSUPPORT_STATIC_ALLOCATION 静态内存分配宏设置为1
在这里插入图片描述
  为了以后的使用方便还是将这个宏定义放置在 FreeRTOSConfig.h 的文件中
在这里插入图片描述

给出一个任务创建成功的案例

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

部分代码参考正点原子提供的代码
接下来开始学习任务的堆栈、任务句柄

2. 任务堆栈

  FreeRTOS 任务调度器在进行任务切换的时候会将当前任务的现场(CPU 寄存器值等)保存在当前任务的任务堆栈中,等到此任务下次运行的时候就会将堆栈中保存的值来恢复现场,恢复现场以后任务就会接着从上次中断的地方开始运行。

  在xTaskCreate函数的定义中可以找到,在使用xTaskCreate创建函数过程中会分配一个大小为usStackDepth的StackType_t类型的一块内存空间。
在这里插入图片描述

  在portmacro.h中可以找到任务堆栈的类型其实就是无符号的32位整型指针 uint32_t*,见下图:
在这里插入图片描述
那么任务堆栈到底是什么呢?

任务堆栈就是一个由 32usStackDepth bit(4usStackDepth Byte)的内存空间。

3. 任务句柄

  在xTaskCreate函数的定义中追寻任务句柄 pxCreatedTask 的下落,结构发现任务句柄被传入了 prvInitialiseNewTask 函数
在这里插入图片描述
  在 prvInitialiseNewTask 函数中继续追寻,发现任务句柄存储的是任务控制块。
  所以上面说任务句柄保存的是任务控制块的首地址,就是这么来的。
在这里插入图片描述
那么任务控制块又是什么呢?

4. 任务控制块

  在xTaskCreate函数的定义中可以发现任务控制块也是动态申请的一块内存空间,这个空间的类型是TCB_t类型的。
在这里插入图片描述
  接着追寻TCB_t类型,原来是被重新命名了。
在这里插入图片描述
  接着追寻 tskTCB 类型,终于看到了庐山真面目,这是一个结构体类型。
在这里插入图片描述

这个类型的具体含义就不再去研究,直接引用正点原子的手册,里面描述得非常清晰。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

最终

  到这里任务的创建就已经非常明了了,这里并没有学习协程的内容,因为协程仅用于 RAM 严重受限的极小处理器, 通常不会用于 32 位微控制器。
  在接下来的学习中将开始以具体的实例来学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值