diff --git a/docs/en/api-reference/system/freertos.rst b/docs/en/api-reference/system/freertos.rst index f207320a73..92a5ef0932 100644 --- a/docs/en/api-reference/system/freertos.rst +++ b/docs/en/api-reference/system/freertos.rst @@ -95,7 +95,7 @@ During startup, ESP-IDF and the FreeRTOS kernel automatically create multiple ta - Affinity - Priority * - Idle Tasks (``IDLEx``) - - An idle task (``IDLEx``) is created for (and pinned to) each core, where ``x`` is the core's number. The ``x`` is dropped when single-core configuration is enabled. + - An idle task (``IDLEx``) is created for (and pinned to) each core, where ``x`` is the core's number. ``x`` is dropped when single-core configuration is enabled. - :ref:`CONFIG_FREERTOS_IDLE_TASK_STACKSIZE` - Core x - ``0`` diff --git a/docs/en/api-reference/system/freertos_idf.rst b/docs/en/api-reference/system/freertos_idf.rst index 56a9358fe9..6ce4cb2fc1 100644 --- a/docs/en/api-reference/system/freertos_idf.rst +++ b/docs/en/api-reference/system/freertos_idf.rst @@ -55,7 +55,7 @@ ESP targets such as ESP32, ESP32-S3, and ESP32-P4 are dual-core SMP SoCs. These - If multiple cores access the same memory address simultaneously, their access will be serialized by the memory bus. - True atomic access to the same memory address is achieved via an atomic compare-and-swap instruction provided by the ISA. -- Cross-core interrupts that allow one core to trigger an interrupt on another core. This allows cores to signal events to each other (such as requesting a context switch on another core). +- Cross-core interrupts that allow one core to trigger an interrupt on the other core. This allows cores to signal events to each other (such as requesting a context switch on the other core). .. note:: @@ -204,7 +204,7 @@ The following example demonstrates the Best Effort Round Robin time slicing in a [0] Head [ B0 , C1 , D0 , AX ] Tail -3. Core 1 has a tick interrupt and searches for a task to run. Task B cannot be run due to incompatible affinity, so core 1 skips to Task C. Task C is selected and moved to the back of the list. +3. Core 1 has a tick interrupt and searches for a task to run. Task B cannot be run due to incompatible affinity, so Core 1 skips to Task C. Task C is selected and moved to the back of the list. .. code-block:: none @@ -226,7 +226,7 @@ The following example demonstrates the Best Effort Round Robin time slicing in a [1] [0] Head [ D0 , AX , C1 , B0 ] Tail -5. Core 1 has another tick and searches for a task to run. Task D cannot be run due to incompatible affinity, so core 1 skips to Task A. Task A is selected and moved to the back of the list. +5. Core 1 has another tick and searches for a task to run. Task D cannot be run due to incompatible affinity, so Core 1 skips to Task A. Task A is selected and moved to the back of the list. .. code-block:: none @@ -309,7 +309,7 @@ Critical Sections Disabling Interrupts ^^^^^^^^^^^^^^^^^^^^ -Vanilla FreeRTOS allows interrupts to be disabled and enabled by calling :c:macro:`taskDISABLE_INTERRUPTS` and :c:macro:`taskENABLE_INTERRUPTS` respectively. IDF FreeRTOS provides the same API, however, interrupts are only disabled or enabled on the current core. +Vanilla FreeRTOS allows interrupts to be disabled and enabled by calling :c:macro:`taskDISABLE_INTERRUPTS` and :c:macro:`taskENABLE_INTERRUPTS` respectively. IDF FreeRTOS provides the same API. However, interrupts are only disabled or enabled on the current core. Disabling interrupts is a valid method of achieving mutual exclusion in Vanilla FreeRTOS (and single-core systems in general). **However, in an SMP system, disabling interrupts is not a valid method of ensuring mutual exclusion**. Critical sections that utilize a spinlock should be used instead. @@ -441,9 +441,9 @@ Single-Core Mode Although IDF FreeRTOS is modified for dual-core SMP, IDF FreeRTOS can also be built for single-core by enabling the :ref:`CONFIG_FREERTOS_UNICORE` option. -For single-core targets (such as the ESP32-S2 and ESP32-C3), the :ref:`CONFIG_FREERTOS_UNICORE` option is always enabled. For multi-core targets (such as ESP32 and ESP32-S3), :ref:`CONFIG_FREERTOS_UNICORE` can also be set, but will result in the application only running core 0. +For single-core targets (such as ESP32-S2 and ESP32-C3), the :ref:`CONFIG_FREERTOS_UNICORE` option is always enabled. For multi-core targets (such as ESP32 and ESP32-S3), :ref:`CONFIG_FREERTOS_UNICORE` can also be set, but will result in the application only running Core 0. -When building for single-core mode, IDF FreeRTOS is designed to be identical to Vanilla FreeRTOS, thus all aforementioned SMP changes to kernel behavior are removed. As a result, building IDF FreeRTOS in single-core mode has the following characteristics: +When building in single-core mode, IDF FreeRTOS is designed to be identical to Vanilla FreeRTOS, thus all aforementioned SMP changes to kernel behavior are removed. As a result, building IDF FreeRTOS in single-core mode has the following characteristics: - All operations performed by the kernel inside critical sections are now deterministic (i.e., no walking of linked lists inside critical sections). - Vanilla FreeRTOS scheduling algorithm is restored (including perfect Round Robin time slicing). diff --git a/docs/zh_CN/api-reference/system/freertos.rst b/docs/zh_CN/api-reference/system/freertos.rst index 4269d4d418..f1ebb9d867 100644 --- a/docs/zh_CN/api-reference/system/freertos.rst +++ b/docs/zh_CN/api-reference/system/freertos.rst @@ -49,7 +49,7 @@ ESP-IDF FreeRTOS 关于用户可配置内核选项的完整列表,参见 :doc:`/api-reference/kconfig`。下列为常用的内核配置选项: -- :ref:`CONFIG_FREERTOS_UNICORE`:仅在 CPU0 上运行 FreeRTOS。注意,这 **不等同于运行原生 FreeRTOS。** 另外,此选项还可能影响除 :component:`freertos` 外其他组件的行为。关于在单核上运行 FreeRTOS 的更多内容,请参考 :ref:`freertos-idf-single-core` (使用 ESP-IDF FreeRTOS 时)或参考 Amazon SMP FreeRTOS 的官方文档,还可以在 ESP-IDF 组件中搜索 ``CONFIG_FREERTOS_UNICORE``。 +- :ref:`CONFIG_FREERTOS_UNICORE`:仅在核 0 上运行 FreeRTOS。注意,这 **不等同于运行原生 FreeRTOS。** 另外,此选项还可能影响除 :component:`freertos` 外其他组件的行为。关于在单核上运行 FreeRTOS 的更多内容,请参考 :ref:`freertos-idf-single-core` (使用 ESP-IDF FreeRTOS 时)或参考 Amazon SMP FreeRTOS 的官方文档,还可以在 ESP-IDF 组件中搜索 ``CONFIG_FREERTOS_UNICORE``。 .. only:: not SOC_HP_CPU_HAS_MULTIPLE_CORES @@ -95,14 +95,14 @@ ESP-IDF FreeRTOS - 亲和性 - 优先级 * - 空闲任务 (``IDLEx``) - - 为每个 CPU 核创建并固定一个空闲任务 (``IDLEx``),其中 ``x`` 是 CPU 核的编号。 当启用单核配置时,``x`` 将被删除。 + - 为每个 CPU 核创建并分配一个空闲任务 (``IDLEx``),其中 ``x`` 是 CPU 核的编号。 当启用单核配置时,``x`` 将被删除。 - :ref:`CONFIG_FREERTOS_IDLE_TASK_STACKSIZE` - - Core x + - 核 x - ``0`` * - FreeRTOS 定时器任务 (``Tmr Svc``) - 如果应用程序调用了任何 FreeRTOS 定时器 API,FreeRTOS 会创建定时器服务或守护任务 - :ref:`CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH` - - Core 0 + - 核 0 - :ref:`CONFIG_FREERTOS_TIMER_TASK_PRIORITY` * - 主任务 (``main``) - 简单调用 ``app_main`` 的任务在 ``app_main`` 返回时会自我删除 @@ -110,14 +110,14 @@ ESP-IDF FreeRTOS - :ref:`CONFIG_ESP_MAIN_TASK_AFFINITY` - ``1`` * - IPC 任务 (``ipcx``) - - 当 :ref:`CONFIG_FREERTOS_UNICORE` 为假时,为每个 CPU 核创建并固定一个 IPC 任务 (``ipcx``)。IPC 任务用于实现处理器间调用 (IPC) 功能 + - 当 :ref:`CONFIG_FREERTOS_UNICORE` 为假时,为每个 CPU 核创建并分配一个 IPC 任务 (``ipcx``)。IPC 任务用于实现处理器间调用 (IPC) 功能 - :ref:`CONFIG_ESP_IPC_TASK_STACK_SIZE` - - Core x + - 核 x - ``24`` * - ESP 定时器任务 (``esp_timer``) - ESP-IDF 创建 ESP 定时器任务用于处理 ESP 定时器回调 - :ref:`CONFIG_ESP_TIMER_TASK_STACK_SIZE` - - Core 0 + - 核 0 - ``22`` .. note:: diff --git a/docs/zh_CN/api-reference/system/freertos_idf.rst b/docs/zh_CN/api-reference/system/freertos_idf.rst index dce5928b36..116e7770f3 100644 --- a/docs/zh_CN/api-reference/system/freertos_idf.rst +++ b/docs/zh_CN/api-reference/system/freertos_idf.rst @@ -1,31 +1,26 @@ -FreeRTOS (ESP-IDF) -================== +FreeRTOS (IDF) +============== :link_to_translation:`en:[English]` +本文档介绍了 ESP-IDF 框架内的 FreeRTOS 双核 SMP 实现,包含以下小节: + +.. contents:: 目录 + :depth: 2 + .. ---------------------------------------------------- Overview ------------------------------------------------------- 概述 ---- -原始 FreeRTOS(下文称 Vanilla FreeRTOS)是一款小巧高效的实时操作系统,适用于许多单核 MCU 和 SoC。但为了支持双核 ESP 芯片,如 ESP32、ESP32-S3、ESP32-P4,ESP-IDF 提供了支持双核对称多处理 (SMP) 的 FreeRTOS 实现(下文称 ESP-IDF FreeRTOS)。 +原始 FreeRTOS(下文称 Vanilla FreeRTOS)是一款小巧高效的实时操作系统,适用于许多单核 MCU 和 SoC。但为了支持双核 ESP 芯片,如 ESP32、ESP32-S3、ESP32-P4,ESP-IDF 特别提供了支持双核对称多处理 (SMP) 的 FreeRTOS 实现(下文称 IDF FreeRTOS)。 -ESP-IDF FreeRTOS 以 Vanilla FreeRTOS v10.5.1 为基础,为支持 SMP,在 API 和内核行为上做了较大改动。本文档描述了 Vanilla FreeRTOS 与 ESP-IDF FreeRTOS 之间的 API 和内核行为差异。 +IDF FreeRTOS 源代码基于 Vanilla FreeRTOS v10.5.1,但内核行为和 API 都有重大修改,以支持双核 SMP。不过用户也可以启用 :ref:`CONFIG_FREERTOS_UNICORE` 选项,将 IDF FreeRTOS 配置为支持单核,详情请参阅 :ref:`freertos-idf-single-core`。 .. note:: 本文档假定读者已具备 Vanilla FreeRTOS 的必要背景知识,即了解其特性、行为和 API 用法。如需了解背景知识,请参阅 `Vanilla FreeRTOS 文档 `_。 -.. note:: - - 启用 :ref:`CONFIG_FREERTOS_UNICORE` 配置选项可以为单核芯片构建 ESP-IDF FreeRTOS,单核 ESP 芯片会默认启用 :ref:`CONFIG_FREERTOS_UNICORE` 配置选项。但请注意,启用 :ref:`CONFIG_FREERTOS_UNICORE` 并不等同于构建 Vanilla FreeRTOS,ESP-IDF FreeRTOS 的某些内核行为和 API 仍有所不同。更多详细信息,请参阅 :ref:`freertos-idf-single-core`。 - -本文档包含以下小节。 - -.. contents:: 目录 - :depth: 2 - - .. -------------------------------------------- Symmetric Multiprocessing ---------------------------------------------- 对称多处理 @@ -41,34 +36,30 @@ ESP-IDF FreeRTOS 以 Vanilla FreeRTOS v10.5.1 为基础,为支持 SMP,在 AP 与单核或非对称多处理系统相比,SMP 系统的主要优势在于: -- 存在多个 CPU,支持多个硬件线程,从而提高整体处理吞吐量。 +- 存在多个核,支持多个硬件线程,从而提高整体处理吞吐量。 - 对称内存支持线程在执行期间切换核,从而提高 CPU 利用率。 尽管 SMP 系统支持线程切换核,但在某些情况下,线程必须或应该仅在特定核上运行。因此,在 SMP 系统中,线程也具备核亲和性,指定线程在哪个特定核上运行。 -- 固定到特定核的线程只能在该核上运行。 -- 未固定于特定核的线程支持在执行期间切换核。 +- 分配给特定核的线程只能在该核上运行。 +- 未分配给特定核的线程支持在执行期间切换核。 ESP 芯片上的 SMP ^^^^^^^^^^^^^^^^ ESP32、ESP32-S3、ESP32-P4 等 ESP 芯片是双核 SMP SoC,具有以下硬件特性以支持 SMP: -- 具有两个完全相同的核,分别称为 CPU0 和 CPU1。代码段无论在哪个核上运行,都有相同的执行效果。 +- 具有两个完全相同的核,分别称为核 0 和核 1。代码段无论在哪个核上运行,都有相同的执行效果。 - 具有对称内存(除了少数例外情况)。 - 如果多个核同时访问相同的内存地址,它们的访问会被内存总线串行化。 - 通过 ISA 提供的原子比较和交换指令,可以实现对同一内存地址的真正原子访问。 -- 跨核中断支持由一个 CPU 触发另一个 CPU 上的中断,这使得核间可以互相发送信号。 +- 跨核中断支持由一个核触发另一个核上的中断,这使得核间可以互相发送信号,如请求在另一个核上进行上下文切换。 +.. note:: -.. only:: not esp32p4 - - .. note:: - - CPU0 又称协议 CPU 或 ``PRO_CPU``,CPU1 又称应用 CPU 或 ``APP_CPU``。在 ESP-IDF 中,CPU0 和 CPU1 的别名 ``PRO_CPU`` 和 ``APP_CPU`` 反映了典型 ESP-IDF 应用程序使用这两个 CPU 的方式。负责处理无线网络(如 Wi-Fi 或蓝牙)的任务通常会固定在 CPU0 上,因此称 CPU0 为 ``PRO_CPU``;而处理应用程序的其余部分任务会被固定在 CPU1 上,因此称 CPU1 为 ``APP_CPU``。 - + 在 ESP-IDF 中,核 0 和核 1 有时分别又被称为 ``PRO_CPU`` 和 ``APP_CPU``。别名 ``PRO_CPU`` 和 ``APP_CPU`` 反映了典型 ESP-IDF 应用程序使用这两个 CPU 的方式。负责处理 Wi-Fi 或蓝牙等协议相关处理程序的任务通常会分配给核 0,因此称核 0 为 ``PRO_CPU``;而处理应用程序其余部分的任务会分配给核 1,因此称核 1 为 ``APP_CPU``。 .. ------------------------------------------------------ Tasks -------------------------------------------------------- @@ -83,27 +74,27 @@ Vanilla FreeRTOS 提供以下用于创建任务的函数: - 使用 :cpp:func:`xTaskCreate` 创建任务时,任务内存动态分配。 - 使用 :cpp:func:`xTaskCreateStatic` 创建任务时,任务内存静态分配,即由用户提供。 -然而,在 SMP 系统中,任务需要分配到特定核。因此,ESP-IDF 提供了 Vanilla FreeRTOS 任务创建函数的 ``PinnedToCore`` 版本: +然而,在 SMP 系统中,任务需要分配到特定核。因此,ESP-IDF 提供了 Vanilla FreeRTOS 任务创建函数的 ``...PinnedToCore()`` 版本: - 使用 :cpp:func:`xTaskCreatePinnedToCore` 可以创建具有特定核亲和性的任务,任务内存动态分配。 - 使用 :cpp:func:`xTaskCreateStaticPinnedToCore` 可以创建具有特定核亲和性的任务,任务内存静态分配,即由用户提供。 -不同于普通的任务创建函数 API,``PinnedToCore`` 版本的任务创建函数 API 有额外的 ``xCoreID`` 参数,用于指定所创建任务的核亲和性。核亲和性的有效值包括: +不同于普通的任务创建函数 API,``...PinnedToCore()`` 版本的任务创建函数 API 有额外的 ``xCoreID`` 参数,用于指定所创建任务的核亲和性。核亲和性的有效值包括: -- ``0``:将创建的任务固定到 CPU0 -- ``1``:将创建的任务固定到 CPU1 -- ``tskNO_AFFINITY``:支持任务在两个 CPU 上运行 +- ``0``:将创建的任务分配给核 0 +- ``1``:将创建的任务分配给核 1 +- ``tskNO_AFFINITY``:支持任务在两个核上运行 -注意,ESP-IDF FreeRTOS 仍支持普通的任务创建函数,但这些标准函数已经过调整,会内部调用其 ``PinnedToCore`` 版本,同时将核亲和性设置为 ``tskNO_AFFINITY``。 +注意,IDF FreeRTOS 仍支持普通的任务创建函数,但这些标准函数已经过调整,会内部调用其 ``...PinnedToCore()`` 版本,同时将核亲和性设置为 ``tskNO_AFFINITY``。 .. note:: - ESP-IDF FreeRTOS 还更改了任务创建函数中的 ``ulStackDepth`` 参数。在 Vanilla FreeRTOS 中,任务堆栈的大小以字为单位指定,而在 ESP-IDF FreeRTOS 中,任务堆栈的大小以字节为单位指定。 + IDF FreeRTOS 还更改了任务创建函数中的 ``ulStackDepth`` 参数。在 Vanilla FreeRTOS 中,任务堆栈的大小以字为单位指定,而在 IDF FreeRTOS 中,任务堆栈的大小以字节为单位指定。 执行任务 ^^^^^^^^ -ESP-IDF FreeRTOS 中任务的结构与 Vanilla FreeRTOS 相同。具体而言,ESP-IDF FreeRTOS 任务: +IDF FreeRTOS 中任务的结构与 Vanilla FreeRTOS 相同。具体而言,IDF FreeRTOS 任务: - 只能处于以下任一状态:运行中、就绪、阻塞或挂起。 - 任务函数通常为无限循环。 @@ -114,7 +105,7 @@ ESP-IDF FreeRTOS 中任务的结构与 Vanilla FreeRTOS 相同。具体而言, 调用 :cpp:func:`vTaskDelete` 可以在 Vanilla FreeRTOS 中删除任务。该函数可用于删除其他任务,若任务句柄为 ``NULL`` 则删除当前运行任务。如果删除的任务是当前正在运行的任务时,任务的内存释放有时会委托给空闲任务执行。 -ESP-IDF FreeRTOS 提供了同样的 :cpp:func:`vTaskDelete` 函数。然而,ESP-IDF FreeRTOS 是一个双核系统,因此调用 :cpp:func:`vTaskDelete` 时,行为上会与 Vanilla FreeRTOS 有以下差异: +IDF FreeRTOS 提供了同样的 :cpp:func:`vTaskDelete` 函数。然而,IDF FreeRTOS 是一个双核系统,因此调用 :cpp:func:`vTaskDelete` 时,行为上会与 Vanilla FreeRTOS 有以下差异: - 删除另一个核上运行的任务时,会在另一个核上触发一次让步,任务内存由其中一个空闲任务释放。 - 如果删除的任务没有在任一核上运行,则会立即释放其内存。 @@ -141,50 +132,50 @@ SMP 调度器 - 调度器可以在不需要当前运行任务的协作下切换执行到另一个任务。 - 调度器会采用轮转方式,定期在具有相同优先级的就绪状态任务间切换执行,时间分片由时钟中断控制。 -ESP-IDF FreeRTOS 调度器支持相同的调度特性,即固定优先级、抢占和时间分片,但也存在细微的行为差异。 +IDF FreeRTOS 调度器支持相同的调度特性,即固定优先级、抢占和时间分片,但也存在细微的行为差异。 固定优先级 ^^^^^^^^^^ -在 Vanilla FreeRTOS 中,当调度器选择要运行的新任务时,往往会选择当前优先级最高的就绪任务。而在 ESP-IDF FreeRTOS 中,每个核都独立地调度要运行的任务。当特定核选择一个任务时,该核会选择优先级最高且可以在该核上运行的就绪状态任务。满足以下条件时,任务可以在核上运行: +在 Vanilla FreeRTOS 中,当调度器选择要运行的新任务时,往往会选择当前优先级最高的就绪任务。而在 IDF FreeRTOS 中,每个核都独立地调度要运行的任务。当特定核选择一个任务时,该核会选择优先级最高且可以在该核上运行的就绪状态任务。满足以下条件时,任务可以在核上运行: -- 任务亲和性兼容,即已固定或未固定到当前核上。 +- 任务亲和性兼容,即已分配或未分配给当前核。 - 该任务当前没有在其他核上运行。 但是,两个具有最高优先级的就绪任务不一定始终由调度器运行,因为还需考虑到任务的核亲和性。例如,给定以下任务: -- 优先级为 10 的任务 A,固定到 CPU0 -- 优先级为 9 的任务 B,固定到 CPU0 -- 优先级为 8 的任务 C,固定到 CPU1 +- 优先级为 10 的任务 A,分配给核 0 +- 优先级为 9 的任务 B,分配给核 0 +- 优先级为 8 的任务 C,分配给核 1 -经过调度后,任务 A 将在 CPU0 上运行,任务 C 将在 CPU1 上运行。即使任务 B 是第二优先级任务,也不会被执行。 +经过调度后,任务 A 将在核 0 上运行,任务 C 将在核 1 上运行。即使任务 B 是第二优先级任务,也不会被执行。 抢占 ^^^^ -在 Vanilla FreeRTOS 中,如果优先级更高的任务已准备好执行,调度器可以抢占当前正在运行的任务。同样,在 ESP-IDF FreeRTOS 任务中,如果调度器确定一个优先级更高的任务可以在某个核上运行,那么调度器可以单独抢占各个核。 +在 Vanilla FreeRTOS 中,如果优先级更高的任务已准备好执行,调度器可以抢占当前正在运行的任务。同样,在 IDF FreeRTOS 任务中,如果调度器确定一个优先级更高的任务可以在某个核上运行,那么调度器可以单独抢占各个核。 -但在某些情况下,一个优先级更高的就绪任务可以在多个核上运行。此时,调度器只会抢占一个核。即便当前有多个核可以抢占,调度器总是优先选择当前核。换句话说,如果优先级更高的就绪任务未固定,并且其优先级高于两个核的当前优先级,调度器将始终选择抢占当前核。例如,给定以下任务: +但在某些情况下,一个优先级更高的就绪任务可以在多个核上运行。此时,调度器只会抢占一个核。即便当前有多个核可以抢占,调度器总是优先选择当前核。换句话说,如果优先级更高的就绪任务未分配,并且其优先级高于两个核的当前优先级,调度器将始终选择抢占当前核。例如,给定以下任务: -- 优先级为 8 的任务 A 当前在 CPU0 上运行 -- 优先级为 9 的任务 B 当前在 CPU1 上运行 -- 优先级为 10 的任务 C 未固定,并由任务 B 解除了阻塞 +- 优先级为 8 的任务 A 当前在核 0 上运行 +- 优先级为 9 的任务 B 当前在核 1 上运行 +- 优先级为 10 的任务 C 未分配,并由任务 B 解除了阻塞 -经过调度后,任务 A 将在 CPU0 上运行,任务 C 将抢占任务 B,因为调度器总是优先选择当前核。 +经过调度后,任务 A 将在核 0 上运行,任务 C 将抢占任务 B,因为调度器总是优先选择当前核。 时间分片 ^^^^^^^^ Vanilla FreeRTOS 实现了时间分片,这意味着如果当前优先级最高的就绪任务包含多个就绪任务,调度器会在这些任务间轮转定期切换。 -然而,在 ESP-IDF FreeRTOS 中,由于以下原因,特定任务可能无法在特定核上运行,因此无法实现完美的轮转时间分片: +然而,在 IDF FreeRTOS 中,由于以下原因,特定任务可能无法在特定核上运行,因此无法实现完美的轮转时间分片: -- 任务固定到了另一个核。 -- 任务未固定,但已经由其他核运行。 +- 任务分配给了另一个核。 +- 任务未分配,但已经由其他核运行。 因此,当核在所有就绪状态任务中搜索寻找要运行的任务时,可能需要跳过同一优先级列表中的一些任务,或者降低优先级,以找到可以运行的就绪状态任务。 -ESP-IDF FreeRTOS 调度器会确保已选择运行的任务置于列表末尾,为同一优先级的就绪状态任务实现最佳轮转时间分片。这样,在下一次调度迭代(即,下一个滴答中断或让步)中,未经选择的任务优先级会更高。 +IDF FreeRTOS 调度器会确保已选择运行的任务置于列表末尾,为同一优先级的就绪状态任务实现最佳轮转时间分片。这样,在下一次调度迭代(即,下一个滴答中断或让步)中,未经选择的任务优先级会更高。 以下示例展示了最佳轮转时间分片的实操。假设: @@ -192,78 +183,66 @@ ESP-IDF FreeRTOS 调度器会确保已选择运行的任务置于列表末尾, - 优先级是当前具有就绪状态任务的最高优先级。 - 第一个字符代表任务名称,即 ``A``、``B``、``C``、``D``。 - - 第二个字符表示任务核的固定情况,``X`` 表示未固定。 + - 第二个字符表示任务核的分配情况,``X`` 表示未分配。 - 任务列表始终从头开始搜索 -.. code-block:: none +1. 起始状态,尚未选择要运行的就绪状态任务。 - -------------------------------------------------------------------------------- + .. code-block:: none - 1. 起始状态,尚未选择要运行的就绪状态任务。 + Head [ AX , B0 , C1 , D0 ] Tail - Head [ AX , B0 , C1 , D0 ] Tail +2. 核 0 有一个滴答中断,搜索要运行的任务。选择任务 A,并将其移至列表末尾。 - -------------------------------------------------------------------------------- + .. code-block:: none - 2. 核 0 有一个滴答中断,搜索要运行的任务。 + Core 0 ─┐ + ▼ + Head [ AX , B0 , C1 , D0 ] Tail - 选择任务 A,并将其移至列表末尾。 + [0] + Head [ B0 , C1 , D0 , AX ] Tail - Core0--| - Head [ AX , B0 , C1 , D0 ] Tail +3. 核 1 有一个滴答中断,搜索要运行的任务。由于亲和性不兼容,任务 B 无法运行,因此核 1 跳到任务 C。选择任务 C,并将其移至列表末尾。 - 0 - Head [ B0 , C1 , D0 , AX ] Tail + .. code-block:: none - -------------------------------------------------------------------------------- + Core 1 ──────┐ + ▼ [0] + Head [ B0 , C1 , D0 , AX ] Tail - 3. 核 1 有一个滴答中断,搜索要运行的任务。 + [0] [1] + Head [ B0 , D0 , AX , C1 ] Tail - 由于亲和性不兼容,任务 B 无法运行,因此核 1 跳到任务 C。 +4. 核 0 有另一个滴答中断,搜索要运行的任务。选择任务 B,并将其移至列表末尾。 - 选择任务 C,并将其移至列表末尾。 + .. code-block:: none - Core1-------| 0 - Head [ B0 , C1 , D0 , AX ] Tail + Core 0 ─┐ + ▼ [1] + Head [ B0 , D0 , AX , C1 ] Tail - 0 1 - Head [ B0 , D0 , AX , C1 ] Tail + [1] [0] + Head [ D0 , AX , C1 , B0 ] Tail - -------------------------------------------------------------------------------- +5. 核 1 有另一个滴答中断,搜索要运行的任务。由于亲和性不兼容,任务 D 无法运行,因此核 1 跳到任务 A。选择任务 A,并将其移至列表末尾。 - 4. 核 0 有另一个滴答中断,搜索要运行的任务。 + .. code-block:: none - 选择任务 B,并将其移至列表末尾。 - - - Core0--| 1 - Head [ B0 , D0 , AX , C1 ] Tail - - 1 0 - Head [ D0 , AX , C1 , B0 ] Tail - - -------------------------------------------------------------------------------- - - 5. 核 1 有另一个滴答中断,搜索要运行的任务。 - - 由于亲和性不兼容,任务 D 无法运行,因此核 1 跳到任务 A - - 选择任务 A,并将其移至列表末尾 - - Core1-------| 0 - Head [ D0 , AX , C1 , B0 ] Tail - - 0 1 - Head [ D0 , C1 , B0 , AX ] Tail + Core 1 ──────┐ + ▼ [0] + Head [ D0 , AX , C1 , B0 ] Tail + [0] [1] + Head [ D0 , C1 , B0 , AX ] Tail 在使用最佳轮转时间分片时需注意: - 相同优先级的多个就绪状态任务不一定可以像在 Vanilla FreeRTOS 中一样按顺序运行。如以上示例所示,核可能会跳过任务。 - 然而,经过足够的滴答次数,任务最终将获得一些处理时间。 - 如果核找不到优先级最高的可运行就绪任务,它将降低优先级来搜索任务。 -- 为了实现理想的轮转时间分片,应确保特定优先级的所有任务都固定到同一个核上。 +- 为了实现理想的轮转时间分片,应确保特定优先级的所有任务都分配给同一个核。 时钟中断 ^^^^^^^^ @@ -275,14 +254,14 @@ Vanilla FreeRTOS 要求定期发生滴答中断,滴答中断有以下作用: - 检查是否需要进行时间分片,即触发上下文切换 - 执行应用程序滴答函数 -在 ESP-IDF FreeRTOS 中,每个核都会接收到定期中断,并独立运行滴答中断。每个核上的滴答中断周期相同,但可能不同步。然而,上述滴答中断任务不会由所有核同时执行,具体而言: +在 IDF FreeRTOS 中,每个核都会接收到定期中断,并独立运行滴答中断。每个核上的滴答中断周期相同,但可能不同步。然而,上述滴答中断任务不会由所有核同时执行,具体而言: -- CPU0 执行上述所有滴答中断任务 -- CPU1 仅检查是否需要时间分片并执行应用程序滴答函数 +- 核 0 执行上述所有滴答中断任务 +- 核 1 仅检查是否需要时间分片并执行应用程序滴答函数 .. note:: - 在 ESP-IDF FreeRTOS 中,CPU0 是负责时间计数的唯一核。因此,任何阻止 CPU0 增加滴答计数的情况,例如暂停 CPU0 上的调度器,都会导致整个调度器的时间计数滞后。 + 在 IDF FreeRTOS 中,核 0 是负责时间计数的唯一核。因此,任何阻止核 0 增加滴答计数的情况,例如暂停核 0 上的调度器,都会导致整个调度器的时间计数滞后。 空闲任务 ^^^^^^^^ @@ -292,7 +271,7 @@ Vanilla FreeRTOS 要求定期发生滴答中断,滴答中断有以下作用: - 释放已删除任务的内存 - 执行应用程序的空闲函数 -而 ESP-IDF FreeRTOS 为每个核单独创建了一个固定的空闲任务。每个核上的空闲任务起到与其 Vanilla FreeRTOS 对应任务相同的作用。 +而 IDF FreeRTOS 为每个核单独创建了一个固定的空闲任务。每个核上的空闲任务起到与其 Vanilla FreeRTOS 对应任务相同的作用。 调度器挂起 ^^^^^^^^^^ @@ -305,41 +284,34 @@ Vanilla FreeRTOS 支持调用 :cpp:func:`vTaskSuspendAll` 挂起调度器,调 调度器恢复时,:cpp:func:`xTaskResumeAll` 会补上所有丢失的时钟计数,并解除超时任务的阻塞。 -在 ESP-IDF FreeRTOS 中,无法在多个核上同时挂起调度器。因此,在特定核上(如核 A)调用 :cpp:func:`vTaskSuspendAll` 时: +在 IDF FreeRTOS 中,无法在多个核上同时挂起调度器。因此,在特定核上(如核 A)调用 :cpp:func:`vTaskSuspendAll` 时: - 只在核 A 上禁用任务切换,但仍启用核 A 的中断。 - 禁止在核 A 上调用任何阻塞或让出函数,在核 A 上禁用时间分片。 -- 核 A 上的中断解除任务阻塞时,对核 A 亲和的任务会进入核 A 的待执行任务列表。未固定的任务或对其他核亲和的任务可以在运行调度器的核上调度。 -- 所有核上的调度器均挂起时,由中断解除阻塞的任务将进入它们固定到的核的待执行任务列表。如果任务未固定,则进入调用中断的核的待执行任务列表。 -- 如果核 A 是 CPU0,则时钟计数将被冻结,挂起的时钟计数将递增,但仍会发生时钟中断以执行应用程序时钟函数。 +- 核 A 上的中断解除任务阻塞时,对核 A 亲和的任务会进入核 A 的待执行任务列表。未分配的任务或对其他核亲和的任务可以在运行调度器的核上调度。 +- 所有核上的调度器均挂起时,由中断解除阻塞的任务将进入它们分配的核的待执行任务列表。如果任务未分配,则进入调用中断的核的待执行任务列表。 +- 如果核 A 是核 0,则时钟计数将被冻结,挂起的时钟计数将递增,但仍会发生时钟中断以执行应用程序时钟函数。 在特定核(如核 A)上调用 :cpp:func:`xTaskResumeAll` 时: - 任何添加到核 A 的待执行任务列表中的任务将恢复执行。 -- 如果核 A 是 CPU0,则挂起的时钟计数将回退,补上丢失的时钟计数。 +- 如果核 A 是核 0,则挂起的时钟计数将回退,补上丢失的时钟计数。 .. warning:: - ESP-IDF FreeRTOS 上的调度器挂起仅暂停特定核上的调度,因此调度器挂起 **不能** 确保访问共享数据时任务互斥。如果需要互斥,请使用适当的锁定机制,如互斥锁或自旋锁。 + IDF FreeRTOS 上的调度器挂起仅暂停特定核上的调度,因此调度器挂起 **不能** 确保访问共享数据时任务互斥。如果需要互斥,请使用适当的锁定机制,如互斥锁或自旋锁。 + +.. ------------------------------------------------ Critical Sections -------------------------------------------------- + +临界区 +------ 禁用中断 ^^^^^^^^ -Vanilla FreeRTOS 支持调用 :c:macro:`taskDISABLE_INTERRUPTS` 禁用中断,调用 :c:macro:`taskENABLE_INTERRUPTS` 启用中断。 +Vanilla FreeRTOS 支持通过调用 :c:macro:`taskDISABLE_INTERRUPTS` 和 :c:macro:`taskENABLE_INTERRUPTS` 分别禁用和启用中断。IDF FreeRTOS 提供了相同的 API,但中断只能在当前核上禁用或启用。 -ESP-IDF FreeRTOS 提供相同的 API,但只能在当前核上启用或禁用中断。 - -.. warning:: - - 在 Vanilla FreeRTOS 和一般的单核系统中,禁用中断是实现互斥的有效方法。但在 SMP 系统中,禁用中断 **不能** 确保实现互斥。详情请参阅 :ref:`critical-sections`。 - - -.. ------------------------------------------------ Critical Sections -------------------------------------------------- - -.. _critical-sections: - -临界区 ------- +在 Vanilla FreeRTOS 以及其他普通单核系统中,禁用中断可以有效实现互斥,**但在 SMP 系统中,禁用中断并不能确保实现互斥**,而应使用有自旋锁的临界区以实现互斥。 API 变更 ^^^^^^^^ @@ -351,7 +323,7 @@ Vanilla FreeRTOS 通过禁用中断实现临界区 (Critical Section),以防 - ``taskENTER_CRITICAL_FROM_ISR()`` 通过禁用中断嵌套从 ISR 进入临界区 - ``taskEXIT_CRITICAL_FROM_ISR()`` 通过重新启用中断嵌套从 ISR 退出临界区 -然而,在 SMP 系统中,仅禁用中断并不能构成临界区,因为存在其他核意味着共享资源仍可以同时访问。因此,ESP-IDF FreeRTOS 中的临界区是使用自旋锁实现的。为适应自旋锁,ESP-IDF FreeRTOS 中的临界区 API 包含一个额外的自旋锁参数,具体如下: +然而,在 SMP 系统中,仅禁用中断并不能构成临界区,因为存在其他核意味着共享资源仍可以同时访问。因此,IDF FreeRTOS 中的临界区是使用自旋锁实现的。为适应自旋锁,IDF FreeRTOS 中的临界区 API 包含一个额外的自旋锁参数,具体如下: - 自旋锁为 ``portMUX_TYPE`` (**请勿与 FreeRTOS 互斥混淆**) - ``taskENTER_CRITICAL(&spinlock)`` 从任务上下文进入临界区 @@ -361,7 +333,7 @@ Vanilla FreeRTOS 通过禁用中断实现临界区 (Critical Section),以防 .. note:: - 临界区 API 可以递归调用,即可以嵌套使用临界区。只要退出临界区的次数与进入的次数相同,多次递归进入临界区就是有效的。但是,由于临界区可以针对不同的自旋锁,因此在递归进入临界区时,应注意避免死锁。 + 临界区 API 可以递归调用,即可以嵌套使用临界区。只要退出临界区的次数与进入的次数相同,多次递归进入临界区就是有效的。但是,由于临界区可以针对不同的自旋锁,因此在递归进入临界区时,应注意避免死锁。 自旋锁可以静态或动态分配。因此,提供了静态和动态初始化自旋锁的宏,如以下代码片段所示。 @@ -369,35 +341,35 @@ Vanilla FreeRTOS 通过禁用中断实现临界区 (Critical Section),以防 .. code:: c - // 静态分配并初始化自旋锁 - static portMUX_TYPE my_spinlock = portMUX_INITIALIZER_UNLOCKED; + // 静态分配并初始化自旋锁 + static portMUX_TYPE my_spinlock = portMUX_INITIALIZER_UNLOCKED; - void some_function(void) - { - taskENTER_CRITICAL(&my_spinlock); - // 此时已处于临界区 - taskEXIT_CRITICAL(&my_spinlock); - } + void some_function(void) + { + taskENTER_CRITICAL(&my_spinlock); + // 此时已处于临界区 + taskEXIT_CRITICAL(&my_spinlock); + } - 静态分配自旋锁并使用 ``portMUX_INITIALIZE()`` 初始化: .. code:: c - // 动态分配自旋锁 - portMUX_TYPE *my_spinlock = malloc(sizeof(portMUX_TYPE)); - // 动态初始化自旋锁 - portMUX_INITIALIZE(my_spinlock); + // 动态分配自旋锁 + portMUX_TYPE *my_spinlock = malloc(sizeof(portMUX_TYPE)); + // 动态初始化自旋锁 + portMUX_INITIALIZE(my_spinlock); - ... + ... - taskENTER_CRITICAL(my_spinlock); - // 访问资源 - taskEXIT_CRITICAL(my_spinlock); + taskENTER_CRITICAL(my_spinlock); + // 访问资源 + taskEXIT_CRITICAL(my_spinlock); 实现 ^^^^ -ESP-IDF FreeRTOS 中,特定核进入和退出临界区的过程如下: +IDF FreeRTOS 中,特定核进入和退出临界区的过程如下: - 对于 ``taskENTER_CRITICAL(&spinlock)`` 或 ``taskENTER_CRITICAL_ISR(&spinlock)`` @@ -437,43 +409,52 @@ ESP-IDF FreeRTOS 中,特定核进入和退出临界区的过程如下: 通常情况下,当发生上下文切换时: - - CPU 寄存器的当前状态保存到要切出的任务栈中 - - CPU 寄存器的先前保存状态从要切入的任务栈中加载 + - 核寄存器的当前状态保存到要切出的任务栈中 + - 核寄存器的先前保存状态从要切入的任务栈中加载 - 然而,ESP-IDF FreeRTOS 为 CPU 的浮点单元 (FPU) 寄存器实现了延迟上下文切换。换句话说,当在特定核上(如 CPU0)发生上下文切换时,核的 FPU 寄存器状态不会立即保存到要被切出的任务的堆栈中(如任务 A)。FPU 的寄存器在发生以下情况前将保持不变: + 然而,IDF FreeRTOS 为核的浮点运算单元 (FPU) 寄存器实现了延迟上下文切换。换句话说,当在特定核上(如核 0)发生上下文切换时,核的 FPU 寄存器状态不会立即保存到要被切出的任务的堆栈中(如任务 A)。FPU 的寄存器在发生以下情况前将保持不变: - 另一个任务(如任务 B)在同一核上运行并使用 FPU,这将触发异常,将 FPU 寄存器保存到任务 A 的堆栈中。 - 任务 A 重新调度到同一核并继续执行。在这种情况下,不需要保存和恢复 FPU 的寄存器。 - 然而,由于任务并未固定到某一核上,可以随意调度(如任务 A 切换到 CPU1),因此很难实现跨核复制和恢复 FPU 寄存器状态。因此,当任务在其执行流程中用 ``float`` 类型使用 FPU 时,ESP-IDF FreeRTOS 会自动将任务固定到当前正在运行的核上,确保所有使用 FPU 的任务始终在特定核上运行。 + 然而,由于任务并未分配给某一核,可以随意调度(如任务 A 切换到核 1),因此很难实现跨核复制和恢复 FPU 寄存器状态。因此,当任务在其执行流程中用 ``float`` 类型使用 FPU 时,IDF FreeRTOS 会自动将任务分配给当前正在运行的核,确保所有使用 FPU 的任务始终在特定核上运行。 - 此外,请注意,由于 FPU 寄存器状态与特定任务相关联,ESP-IDF FreeRTOS 默认不支持在中断上下文中使用 FPU。 + 此外,请注意,由于 FPU 寄存器状态与特定任务相关联,IDF FreeRTOS 默认不支持在中断上下文中使用 FPU。 - .. only: esp32 + .. only:: esp32 - .. note:: + .. note:: - 如需在 ISR 例程中使用 ``float`` 类型,请参考配置选项:ref:`CONFIG_FREERTOS_FPU_IN_ISR`。 + 如需在 ISR 例程中使用 ``float`` 类型,请参考配置选项:ref:`CONFIG_FREERTOS_FPU_IN_ISR`。 .. note:: - 具有 FPU 的 ESP 芯片不支持双精度浮点运算 ``double`` 的硬件加速。``double`` 通过软件实现,因此比起 ``float`` 类型,``double`` 操作可能消耗更多 CPU 时间。 + 具有 FPU 的 ESP 芯片不支持双精度浮点运算 ``double`` 的硬件加速。``double`` 通过软件实现,因此比起 ``float`` 类型,``double`` 操作可能消耗更多 CPU 时间。 .. -------------------------------------------------- Single Core ----------------------------------------------------- .. _freertos-idf-single-core: -单核 ESP-IDF FreeRTOS -^^^^^^^^^^^^^^^^^^^^^ +单核模式 +^^^^^^^^ -尽管 ESP-IDF FreeRTOS 为 SMP 调度器,仍需要考虑单核 ESP 芯片,如 ESP32-S2 和 ESP32-C3。为这些芯片构建 ESP-IDF 应用程序时,同样使用 ESP-IDF FreeRTOS,但核的数量将设置为 ``1``,即 :ref:`CONFIG_FREERTOS_UNICORE` 将始终对单核芯片启用。 +尽管 IDF FreeRTOS 是为双核 SMP 专门设计的,但也可通过启用 :ref:`CONFIG_FREERTOS_UNICORE` 选项,将 IDF FreeRTOS 配置为支持单核。 -对于 ESP32 和 ESP32-S3 等多核芯片也可以设置 :ref:`CONFIG_FREERTOS_UNICORE`,启用该选项后 ESP-IDF FreeRTOS 仅在 CPU0 上运行,其他核都将处于非活动状态。 +对于 ESP32-S2 和 ESP32-C3 等单核芯片,:ref:`CONFIG_FREERTOS_UNICORE` 选项始终启用。对于 ESP32 和 ESP32-S3 等多核芯片也可以设置 :ref:`CONFIG_FREERTOS_UNICORE`,对于多核目标(如 ESP32 和 ESP32-S3),也可以设置 :ref:`CONFIG_FREERTOS_UNICORE`,但启用该选项后应用仅在核 0 上运行。 -.. note:: +在单核模式下,IDF FreeRTOS 与 Vanilla FreeRTOS 完全相同,因此无需考虑前文提到的对内核行为的 SMP 更改。因此,在单核模式下构建 IDF FreeRTOS 具有以下特点: + +- 内核在临界区内执行的所有操作都是确定的(即在临界区内不走链表)。 +- 恢复了 Vanilla FreeRTOS 调度算法(包括完美的轮转时间分片)。 +- 移除了所有单核构建中的 SMP 专用数据。 + +在单核模式下仍可调用 SMP API,这些 API 仍然保持公开,以便为单核和多核构建源代码,而无需调用不同的 API 集。不过,SMP API 在单核模式下不会展示任何 SMP 行为,因此实际上等同于其对应的单核模式 API。例如: + +- 任何 ``...ForCore(..., BaseType_t xCoreID)`` SMP API 将只接受 ``0`` 为 ``xCoreID`` 的有效值。 +- ``...PinnedToCore()`` 任务创建 API 将直接忽略 ``xCoreID`` 核亲和参数。 +- 临界区 API 仍需要自旋锁参数,但不会使用自旋锁,临界区将恢复为仅用于禁用/启用中断。 - 请注意,开启 :ref:`CONFIG_FREERTOS_UNICORE` **不等于运行 Vanilla FreeRTOS**,此时仍然可以调用 ESP-IDF FreeRTOS 的附加 API,且即使为单核编译,ESP-IDF FreeRTOS 的行为变化仍会导致一定的性能开销。 .. ------------------------------------------------- API References ----------------------------------------------------