> 文章列表 > 【ESP32+freeRTOS学习笔记之“ESP32环境下使用freeRTOS的特性分析(3-多核环境下的调度)”】

【ESP32+freeRTOS学习笔记之“ESP32环境下使用freeRTOS的特性分析(3-多核环境下的调度)”】

【ESP32+freeRTOS学习笔记之“ESP32环境下使用freeRTOS的特性分析(3-多核环境下的调度)”】

目录

  • 1、不同核心上分别调度
  • 2、tick中断
  • 3、关于抢占
  • 4、关于同优级的任务按时间片调度
  • 5、空闲任务
  • 6、调度程序暂停
  • 7、启动和终止
  • 8、 禁用中断
  • 9、总结

Vanilla FreeRTOS调度器是具有时间切片的固定优先级抢占调度器,这意味着:

每个任务在创建时都有一个固定的优先级。调度器执行最高优先级的就绪状态任务
调度器将周期性地在具有相同优先级的就绪状态任务之间切换执行(以循环方式)。时间切片是由一个tick中断控制的。

ESP-IDF FreeRTOS调度器支持相同的调度功能(即固定优先级、抢占和时间切片),但会有有一些小的行为差异。

1、不同核心上分别调度

在Vanilla FreeRTOS中,当调度器选择要运行的新任务时,它将始终选择当前优先级最高的就绪状态任务。

在ESP-IDF FreeRTOS中,每个核心将独立调度要运行的任务。当一个特定的核心选择一个任务时,该核心将选择该核心可以运行的最高优先级的就绪状态任务。

例如,给定以下任务:

优先级为10的任务A固定在CPU0上
优先级9的任务B固定在CPU0上
优先级为8的任务C固定在CPU1上
生成的计划将使任务A在CPU0上运行,任务C在CPU1上运行。任务B没有运行,即使它是第二高优先级的任务。

2、tick中断

Vanilla FreeRTOS要求发生周期性的TICK中断。tick中断负责:

  > 递增调度程序的tick计数> 取消任何已超时的阻塞任务> 检查是否需要时间切片(即触发上下文切换)> 执行应用程序勾子函数

在ESP-IDF FreeRTOS中,每个内核将接收一个周期性中断,并独立运行tick中断。每个核心上的tick中断具有相同的周期,但可能异相。然而,上面列出的责任并不是由所有核心运行的:

  > CPU0将执行上面列出的所有中断职责>CPU1将只检查时间切片并执行应用程序tick勾子程序

备注
CPU0全权负责在ESP-IDF FreeRTOS中保持时间。因此,任何阻止CPU0增加tick计数的事情(例如暂停CPU0上的调度器)都将导致整个调度器计时滞后。

3、关于抢占

在Vanilla FreeRTOS中,如果优先级较高的任务准备好执行,则调度器可以抢占当前正在运行的任务。

同样,在ESP-IDF FreeRTOS中,如果调度器确定较高优先级的任务可以在每个内核上运行,则该调度器可以单独抢占该内核。

然而,在某些情况下,可以在多个核心上运行准备就绪的优先级较高的任务。在这种情况下,调度器将只抢占一个核心。当多个核可以被抢占时,调度器总是优先考虑当前核。换句话说,如果较高优先级的就绪任务被取消固定并且具有比两个核的当前优先级更高的优先级,则调度器将始终选择抢占当前核。

例如,给定以下任务:

  >优先级为8的任务A当前正在CPU0上运行>优先级9的任务B当前正在CPU1上运行>优先级为10的任务C已取消固定并被任务B取消阻塞得到的调度将使任务A在CPU0上运行,而任务C抢占任务B,前提是调度器总是优先考虑当前核心。

4、关于同优级的任务按时间片调度

Vanilla FreeRTOS调度器实现时间切片,这意味着如果当前最高就绪优先级包含多个就绪任务,则调度器将以循环方式定期在这些任务之间切换。

然而,在ESP-IDF FreeRTOS中,由于以下原因,导致调度器无法完美的按循环方式在任务之间切换:
>同优先级的某些任务被固定在另一个核心上。
>对于未固定的任务,该任务已经由另一个核心运行。

因此,当核心在就绪状态任务列表中搜索要运行的任务时,该核心可能需要跳过同一优先级列表中的几个任务,或者降到较低的优先级,以便找到该核心可以运行的就绪状态任务。

下面的示例演示了时间切片的操作。假设:

有四个优先级相同的就绪状态任务AX、B0、C1、D1,其中:

  • 优先级是当前就绪状态任务的最高优先级
  • 第一个字符表示任务的名称(即A、B、C、D)
  • 第二个字符表示核心固定任务(X表示未固定)

任务列表总是从头开始搜索
【ESP32+freeRTOS学习笔记之“ESP32环境下使用freeRTOS的特性分析(3-多核环境下的调度)”】

因此

  • 用户不能期望具有相同优先级的多个就绪状态任务按顺序运行(如Vanilla FreeRTOS中的情况)。如上面的例子所示,不同核心调茺时可能需要跳过一些非本核心的同优先级任务。
  • 然而,如果有足够的节拍,任务最终会得到一些处理时间。
  • 如果核心找不到具有最高就绪状态优先级的任务可运行任务,它将降到较低优先级来搜索任务。

为了实现理想的循环时间切片,用户应该确保特定优先级的所有任务都固定在同一个核心上。

5、空闲任务

Vanilla FreeRTOS将在调度程序启动时隐式创建优先级为0的空闲任务。空闲任务在没有其他任务准备运行时运行,它具有以下职责:

释放已删除任务的内存
执行应用程序空闲勾子函数

在ESP-IDF FreeRTOS中,为每个核心创建一个单独的固定空闲任务。

6、调度程序暂停

Vanilla FreeRTOS允许通过分别调用vTaskSuspendAll()和xTaskResumeAll()来暂停/恢复调度程序。当调度程序被挂起时:

  > 任务切换被禁用,但中断仍处于启用状态。> 禁止调用任何阻塞/让步函数,并禁用时间切片。> tick计数被冻结(但tick中断仍将发生以执行tick的勾子函数)

在调度程序恢复时,xTaskResumeAll()将赶上所有丢失的节拍,并使任何超时的任务取消阻塞。

在ESP-IDF FreeRTOS中,不可能跨多个内核暂停调度程序。因此,当在特定核心(例如,核心A)上调用vTaskSuspendAll()时:

  >任务切换仅在核心A上禁用,但核心A的中断仍处于启用状态>禁止在核心A上调用任何阻塞/让步函数。在核心A上禁用时间切片。>如果核心A上的中断取消阻塞任何任务,这些任务将进入核心A自己的挂起准备任务列表>如果核心A是CPU0,则tick计数被冻结,而挂起的tick计数反而被递增。但是,为了执行应用程序勾子函数,tick中断仍将发生。

当在特定核心(例如核心a)上调用xTaskResumeAll()时:

  >添加到核心A的挂起就绪任务列表中的任何任务都将恢复>如果核心A是CPU0,则挂起的tick计数将被解开以赶上丢失的tick。

警告
鉴于ESP-IDF FreeRTOS上的调度器暂停只会暂停特定核心上的调度,因此调度器暂停不是一种有效的方法,无法确保访问共享数据时任务之间的互斥。如果用户需要互斥,则应使用适当的锁定原语,如互斥或自旋锁。

7、启动和终止

  ESP-IDF FreeRTOS不需要用户调用vTaskStartScheduler()来启动调度程序。ESP-IDF应用程序的启动流程将自动调用此函数。用户代码的入口点是一个用户定义的void app_main(void)函数。ESP-IDF FreeRTOS不支持调度程序终止。调用vTaskEndScheduler()只会导致应用程序中止。

8、 禁用中断

  Vanilla FreeRTOS允许通过分别调用taskDISABLE_interrupts和taskENABLE_interrupts来禁用和启用中断。ESP-IDF FreeRTOS提供相同的API,但中断仅在当前内核上禁用或启用。

警告
在Vanilla FreeRTOS(以及一般的单核系统)中,禁用中断是实现互斥的有效方法。然而,在SMP系统中,禁用中断并不是确保互斥的有效方法。

9、总结

在ESP32环境中,双核的特性,使得FreeRTOS在调度方面必须要考虑在双核上的区别对待。CPU0与CPU1上调度相关的责任和效果是有区别的。这些特殊细节,在编程过程中要重视。