1、简述
FreeRTOS应用程序由一组独立的任务构成。
在任何时间点,应用程序中只能执行一个任务,FreeRTOS调度器负责决定所要执行的任务。
每个任务在自己的上下文中执行,不依赖于系统内的其他任务或 FreeRTOS的调度器本身。
FreeRTOS调度器负责确保任务调入时的处理器上下文(寄存器值、堆栈内容等)与任务调出时的处理器上下文完全相同。为实现这一点,每个任务都分配有自己的堆栈。当任务调出时,执行上下文被保存到该任务的堆栈中,以便以后再调入相同的任务时可以准确地恢复其执行上下文。
2、任务状态
任务状态有四种:运行Running、准备就绪Ready、阻塞Blocked、挂起Suspended
1)运行状态
任务正在使用处理器,执行实际任务。
如果运行 FreeRTOS 的处理器只有一个内核, 那么在任何给定时间内都只能有一个任务处于运行状态。
2)准备就绪
准备就绪任务指那些能够执行(它们不处于阻塞或挂起状态),但目前没有执行,因为同等或更高优先级的不同任务已经处于运行状态。
3)阻塞
如果任务当前正在等待时间或外部事件,则该任务被认为处于阻塞状态。
例如,如果一个任务调用vTaskDelay(),它将被阻塞(被置于阻塞状态), 直到延迟结束-一个时间事件。 任务也可以通过阻塞来等待队列、信号量、事件组、通知或信号量 事件。 处于阻塞状态的任务通常有一个"超时"期, 超时后任务将被超时,并被解除阻塞, 即使该任务所等待的事件没有发生。
“阻塞”状态下的任务不使用任何处理时间,不能被选择进入运行状态。
4)挂起
与“阻塞”状态下的任务一样, “挂起”状态下的任务不能 被选择进入运行状态,但处于挂起状态的任务 没有超时。 相反,任务只有在分别通过 vTaskSuspend() 和 xTaskResume() API 调用明确命令时 才会进入或退出挂起状态。
3、任务优先级
1)优先级高的先执行
FreeRTOS 调度器总是将优先级高的已就绪的任务,转为运行状态。换句话说,被置于运行状态的任务 始终是可运行的最高优先级任务。
低优先级数字表示低优先级任务。 空闲任务的优先级为零 (tskIDLE_PRIORITY)。
2)相同优先级的任务
任意数量的任务可共用相同的优先级。 如果 configUSE_TIME_SLICING 未经定义, 或者如果 configUSE_TIME_SLICING 设置为 1,则相同优先级的就绪状态任务将使用时间切片轮询调度方案共享可用的处理时间 。
3)有多少个优先级?
每个任务均被分配了从 0 到 ( configMAX_PRIORITIES - 1 ) 的优先级
其中 configMAX_PRIORITIES 在 FreeRTOSConfig.h 中定义。
4)configMAX_PRIORITIES设置为多大?
如果正在使用的端口实现了端口优化的任务选择机制,该机制使用 “前导零计数”类指令(用于单个指令中的任务选择)且 在 FreeRTOSConfig.h 中将 configUSE_PORT_OPTIMISED_TASK_SELECTION 设置为 1,则 configMAX_PRIORITIES 不得大于 32。 在所有其他情况下,configMAX_PRIORITIES 可以采取任何合理范围内的值,但出于 RAM 使用效率的原因,应保持在实际需要的最小值。
4、任务调度
FreeRTOS 调度算法, 主要针对单核、非对称多核 (AMP)、和对称多核 (SMP) RTOS 配置。调度算法是决定哪个 RTOS 任务应处于运行状态的软件程序。在任何给定时间, 每个处理器核心只能有一个任务处于运行状态。在 AMP 中, 每个处理器核心运行自身的 FreeRTOS 实例。在 SMP 中, 存在一个 FreeRTOS 实例,可以跨多核调度 RTOS 任务 。
默认 RTOS 调度策略(单核)
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 中断上在同等优先级的任务之间切换 。
FreeRTOS AMP 调度策略
使用 FreeRTOS 的非对称多处理 (AMP) 是指多核设备的每个核心都单独运行自己的 FreeRTOS 实例。这些 核心并不都需要具有相同架构, 但如果 FreeRTOS 实例之间需要进行通信,则需要共享一些内存。
每个核心都会运行自己的 FreeRTOS 实例, 因此任何给定核心上的调度算法与上文的单核系统调度算法完全相同 。您可以使用流缓冲区或消息缓冲区作为核间通信原语, 这样一来,一个核心上的任务可以进入“阻塞”状态, 以等待另一个核心发来的数据或事件。
FreeRTOS SMP 调度策略
使用 FreeRTOS 的对称多处理 (SMP) 是指 一个 FreeRTOS 实例可以跨多个处理器核心调度 RTOS 任务。由于 只有一个 FreeRTOS 实例在运行,一次只能使用 FreeRTOS 的一个端口, 因此每个核心必须具有相同的处理器架构并共用 相同的内存空间。
FreeRTOS SMP 调度策略使用与单核调度策略相同的算法, 但与单核和 AMP 场景不同的是, SMP 在任何给定时间都会导致多个任务处于运行状态 (每个核心上都有一个运行状态的任务)。这意味着, 只有缺乏可运行的高优先级任务时,才会运行低优先级任务的假设不再成立 。要想了解其中的原因,请考虑一下, 若起初只有一个高优先级任务 和两个中等优先级任务处于 “就绪”状态,SMP 调度器会如何选择在双核微控制器上运行的任务。调度器需要选择两个任务,每个核心对应一个任务。 首先,高优先级任务是指可运行的最高优先级任务, 因此会选择将它用于第一个核心。 这样就剩下了两个中等优先级的任务 作为可运行的最高优先级任务,因此会将它们用于第二个核心 。结果是高优先级和中等优先级的任务同时运行。
配置 SMP RTOS 调度策略
以下配置选项有助于移动下述代码:将为单核或 AMP RTOS 配置编写的代码移动到 SMP RTOS 配置中, 且这些代码依赖于该假设——如果有一个可运行的高优先级任务,那么低优先级任务不会运行。
configRUN_MULTIPLE_PRIORITIES
如果在下述文件中将 configRUN_MULTIPLE_PRIORITIES 设置为 0: FreeRTOSConfig.h,则调度器只会同时运行 具有相同优先级的多个任务。这可能会修复基于“一次只运行一个任务”这一假设编写的代码, 但这就无法享受到 SMP 配置带来的一些好处。
configUSE_CORE_AFFINITY
如果在 中将 FreeRTOSConfig.hconfigUSE_CORE_AFFINITY 设置为 1, 则 vTaskCoreAffinitySet() API 函数可用于定义某个任务可以在哪些核心上运行 以及不可以在哪些核心上运行。使用该方法,应用程序编写者可以防止 同时执行假设了自身执行顺序的两个任务 。
FreeRTOS 内核开发者文档:https://www.freertos.org/zh-cn-cmn-s/features.html