From 912f6c527cefd8c8e011697fac7e38a2fcf0fe1d Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Thu, 14 Dec 2023 00:04:52 +0800 Subject: [PATCH 1/3] docs(freertos/idf): Update IDF FreeRTOS documentation This commit updates the IDF FreeRTOS documentation as follows: - Update terminology ("CPU" to "Core", "ESP-IDF FreeRTOS" to "IDF FreeRTOS") - Fixed some inconsistent formatting - Rearranged some sub sections - Updated section regarding single-core mode to be coherent with v10.5.1 update. --- docs/en/api-reference/system/freertos.rst | 6 +- docs/en/api-reference/system/freertos_idf.rst | 277 ++++++++---------- docs/zh_CN/api-reference/system/freertos.rst | 2 +- .../api-reference/system/freertos_idf.rst | 4 +- 4 files changed, 135 insertions(+), 154 deletions(-) diff --git a/docs/en/api-reference/system/freertos.rst b/docs/en/api-reference/system/freertos.rst index db18a58131..f207320a73 100644 --- a/docs/en/api-reference/system/freertos.rst +++ b/docs/en/api-reference/system/freertos.rst @@ -49,7 +49,7 @@ Vanilla FreeRTOS requires that ports and applications configure the kernel by ad For the full list of user configurable kernel options, see :doc:`/api-reference/kconfig`. The list below highlights some commonly used kernel configuration options: -- :ref:`CONFIG_FREERTOS_UNICORE` runs FreeRTOS only on CPU0. Note that this is **not equivalent to running Vanilla FreeRTOS**. Furthermore, this option may affect behavior of components other than :component:`freertos`. For more details regarding the effects of running FreeRTOS on a single core, refer to :ref:`freertos-smp-single-core` (if using ESP-IDF FreeRTOS) or the official Amazon SMP FreeRTOS documentation. Alternatively, users can also search for occurrences of ``CONFIG_FREERTOS_UNICORE`` in the ESP-IDF components. +- :ref:`CONFIG_FREERTOS_UNICORE` runs FreeRTOS only on Core 0. Note that this is **not equivalent to running Vanilla FreeRTOS**. Furthermore, this option may affect behavior of components other than :component:`freertos`. For more details regarding the effects of running FreeRTOS on a single core, refer to :ref:`freertos-idf-single-core` (if using ESP-IDF FreeRTOS) or the official Amazon SMP FreeRTOS documentation. Alternatively, users can also search for occurrences of ``CONFIG_FREERTOS_UNICORE`` in the ESP-IDF components. .. only:: not SOC_HP_CPU_HAS_MULTIPLE_CORES @@ -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 CPU core, where ``x`` is the CPU 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. The ``x`` is dropped when single-core configuration is enabled. - :ref:`CONFIG_FREERTOS_IDLE_TASK_STACKSIZE` - Core x - ``0`` @@ -110,7 +110,7 @@ During startup, ESP-IDF and the FreeRTOS kernel automatically create multiple ta - :ref:`CONFIG_ESP_MAIN_TASK_AFFINITY` - ``1`` * - IPC Tasks (``ipcx``) - - When :ref:`CONFIG_FREERTOS_UNICORE` is false, an IPC task (``ipcx``) is created for (and pinned to) each CPU core. IPC tasks are used to implement the Inter-processor Call (IPC) feature. + - When :ref:`CONFIG_FREERTOS_UNICORE` is false, an IPC task (``ipcx``) is created for (and pinned to) each core. IPC tasks are used to implement the Inter-processor Call (IPC) feature. - :ref:`CONFIG_ESP_IPC_TASK_STACK_SIZE` - Core x - ``24`` diff --git a/docs/en/api-reference/system/freertos_idf.rst b/docs/en/api-reference/system/freertos_idf.rst index b85a2c52bb..56a9358fe9 100644 --- a/docs/en/api-reference/system/freertos_idf.rst +++ b/docs/en/api-reference/system/freertos_idf.rst @@ -1,30 +1,25 @@ -FreeRTOS (ESP-IDF) -================== +FreeRTOS (IDF) +============== :link_to_translation:`zh_CN:[中文]` +This document provides information regarding the dual-core SMP implementation of FreeRTOS inside ESP-IDF. This document is split into the following sections: + +.. contents:: Sections + :depth: 2 + .. ---------------------------------------------------- Overview ------------------------------------------------------- Overview -------- -The original FreeRTOS (hereinafter referred to as Vanilla FreeRTOS) is a compact and efficient real-time operating system supported on many single-core MCUs and SoCs. However, to support numerous dual-core ESP targets, such as ESP32, ESP32-S3, and ESP32-P4, ESP-IDF provides an implementation of FreeRTOS with dual-core symmetric multiprocessing (SMP) capabilities (hereinafter referred to as ESP-IDF FreeRTOS). +The original FreeRTOS (hereinafter referred to as **Vanilla FreeRTOS**) is a compact and efficient real-time operating system supported on numerous single-core MCUs and SoCs. However, to support dual-core ESP targets, such as ESP32, ESP32-S3, and ESP32-P4, ESP-IDF provides a unique implementation of FreeRTOS with dual-core symmetric multiprocessing (SMP) capabilities (hereinafter referred to as **IDF FreeRTOS**). -ESP-IDF FreeRTOS is based on Vanilla FreeRTOS v10.5.1 but contains significant modifications to both API and kernel behavior in order to support dual-core SMP. This document describes the API and behavioral differences between Vanilla FreeRTOS and ESP-IDF FreeRTOS. +IDF FreeRTOS source code is based on Vanilla FreeRTOS v10.5.1 but contains significant modifications to both kernel behavior and API in order to support dual-core SMP. However, IDF FreeRTOS can also be configured for single-core by enabling the :ref:`CONFIG_FREERTOS_UNICORE` option (see :ref:`freertos-idf-single-core` for more details). .. note:: - This document assumes that the reader has a requisite understanding of Vanilla FreeRTOS, i.e., its features, behavior, and API usage. Refer to the `Vanilla FreeRTOS documentation `_ for more details. - -.. note:: - - ESP-IDF FreeRTOS can be built for a single core by enabling the :ref:`CONFIG_FREERTOS_UNICORE` configuration option. ESP targets that are single core always have the :ref:`CONFIG_FREERTOS_UNICORE` option enabled. However, note that building with :ref:`CONFIG_FREERTOS_UNICORE` enabled does not equate to building with Vanilla FreeRTOS, as some of the behavioral and API changes of ESP-IDF are still present. For more details, see :ref:`freertos-smp-single-core`. - -This document is split into the following parts. - -.. contents:: Contents - :depth: 2 - + This document assumes that the reader has a requisite understanding of Vanilla FreeRTOS, i.e., its features, behavior, and API usage. Refer to the `Vanilla FreeRTOS documentation `_ for more details. .. -------------------------------------------- Symmetric Multiprocessing ---------------------------------------------- @@ -34,17 +29,17 @@ Symmetric Multiprocessing Basic Concepts ^^^^^^^^^^^^^^ -Symmetric multiprocessing is a computing architecture where two or more identical CPUs (cores) are connected to a single shared main memory and controlled by a single operating system. In general, an SMP system: +Symmetric multiprocessing is a computing architecture where two or more identical CPU cores are connected to a single shared main memory and controlled by a single operating system. In general, an SMP system: - has multiple cores running independently. Each core has its own register file, interrupts, and interrupt handling. - presents an identical view of memory to each core. Thus, a piece of code that accesses a particular memory address has the same effect regardless of which core it runs on. The main advantages of an SMP system compared to single-core or asymmetric multiprocessing systems are that: -- the presence of multiple CPUs allows for multiple hardware threads, thus increasing overall processing throughput. +- the presence of multiple cores allows for multiple hardware threads, thus increasing overall processing throughput. - having symmetric memory means that threads can switch cores during execution. This, in general, can lead to better CPU utilization. -Although an SMP system allows threads to switch cores, there are scenarios where a thread must or should only run on a particular core. Therefore, threads in an SMP system also have a core affinity that specifies which particular core the thread is allowed to run on. +Although an SMP system allows threads to switch cores, there are scenarios where a thread must/should only run on a particular core. Therefore, threads in an SMP system also have a core affinity that specifies which particular core the thread is allowed to run on. - A thread that is pinned to a particular core is only able to run on that core. - A thread that is unpinned will be allowed to switch between cores during execution instead of being pinned to a particular core. @@ -54,21 +49,17 @@ SMP on an ESP Target ESP targets such as ESP32, ESP32-S3, and ESP32-P4 are dual-core SMP SoCs. These targets have the following hardware features that make them SMP-capable: -- Two identical cores are known as CPU0 and CPU1. This means that the execution of a piece of code is identical regardless of which core it runs on. +- Two identical cores are known as Core 0 and Core 1. This means that the execution of a piece of code is identical regardless of which core it runs on. - Symmetric memory (with some small exceptions). - 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 CPU to trigger an interrupt on another CPU. This allows cores to signal each other. +- 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). +.. note:: -.. only:: not esp32p4 - - .. note:: - - CPU0 is also known as Protocol CPU or ``PRO_CPU`` and CPU1 is also known as Application CPU or ``APP_CPU``. The ``PRO_CPU`` and ``APP_CPU`` aliases for CPU0 and CPU1 exist in ESP-IDF as they reflect how typical ESP-IDF applications utilize the two CPUs. Typically, the tasks responsible for handling wireless networking (e.g., Wi-Fi or Bluetooth) are pinned to CPU0, thus the name ``PRO_CPU``; whereas the tasks handling the remainder of the application are pinned to CPU1, thus the name ``APP_CPU``. - + Within ESP-IDF, Core 0 and Core 1 are sometimes referred to as ``PRO_CPU`` and ``APP_CPU`` respectively. The aliases exist in ESP-IDF as they reflect how typical ESP-IDF applications utilize the two cores. Typically, the tasks responsible for handling protocol related processing such as Wi-Fi or Bluetooth are pinned to Core 0 (thus the name ``PRO_CPU``), where as the tasks handling the remainder of the application are pinned to Core 1, (thus the name ``APP_CPU``). .. ------------------------------------------------------ Tasks -------------------------------------------------------- @@ -83,27 +74,27 @@ Vanilla FreeRTOS provides the following functions to create a task: - :cpp:func:`xTaskCreate` creates a task. The task's memory is dynamically allocated. - :cpp:func:`xTaskCreateStatic` creates a task. The task's memory is statically allocated, i.e., provided by the user. -However, in an SMP system, tasks need to be assigned a particular affinity. Therefore, ESP-IDF provides a ``PinnedToCore`` version of Vanilla FreeRTOS's task creation functions: +However, in an SMP system, tasks need to be assigned a particular affinity. Therefore, ESP-IDF provides a ``...PinnedToCore()`` version of Vanilla FreeRTOS's task creation functions: - :cpp:func:`xTaskCreatePinnedToCore` creates a task with a particular core affinity. The task's memory is dynamically allocated. - :cpp:func:`xTaskCreateStaticPinnedToCore` creates a task with a particular core affinity. The task's memory is statically allocated, i.e., provided by the user. -The ``PinnedToCore`` versions of the task creation function API differ from their vanilla counterparts by having an extra ``xCoreID`` parameter that is used to specify the created task's core affinity. The valid values for core affinity are: +The ``...PinnedToCore()`` versions of the task creation function API differ from their vanilla counterparts by having an extra ``xCoreID`` parameter that is used to specify the created task's core affinity. The valid values for core affinity are: -- ``0``, which pins the created task to CPU0 -- ``1``, which pins the created task to CPU1 -- ``tskNO_AFFINITY``, which allows the task to be run on both CPUs +- ``0``, which pins the created task to Core 0 +- ``1``, which pins the created task to Core 1 +- ``tskNO_AFFINITY``, which allows the task to be run on both cores -Note that ESP-IDF FreeRTOS still supports the vanilla versions of the task creation functions. However, these standard functions have been modified to essentially invoke their respective ``PinnedToCore`` counterparts while setting the core affinity to ``tskNO_AFFINITY``. +Note that IDF FreeRTOS still supports the vanilla versions of the task creation functions. However, these standard functions have been modified to essentially invoke their respective ``...PinnedToCore()`` counterparts while setting the core affinity to ``tskNO_AFFINITY``. .. note:: - ESP-IDF FreeRTOS also changes the units of ``ulStackDepth`` in the task creation functions. Task stack sizes in Vanilla FreeRTOS are specified in a number of words, whereas in ESP-IDF FreeRTOS, the task stack sizes are specified in bytes. + IDF FreeRTOS also changes the units of ``ulStackDepth`` in the task creation functions. Task stack sizes in Vanilla FreeRTOS are specified in a number of words, whereas in IDF FreeRTOS, the task stack sizes are specified in bytes. Execution ^^^^^^^^^ -The anatomy of a task in ESP-IDF FreeRTOS is the same as in Vanilla FreeRTOS. More specifically, ESP-IDF FreeRTOS tasks: +The anatomy of a task in IDF FreeRTOS is the same as in Vanilla FreeRTOS. More specifically, IDF FreeRTOS tasks: - Can only be in one of the following states: Running, Ready, Blocked, or Suspended. - Task functions are typically implemented as an infinite loop. @@ -114,7 +105,7 @@ Deletion Task deletion in Vanilla FreeRTOS is called via :cpp:func:`vTaskDelete`. The function allows deletion of another task or the currently running task if the provided task handle is ``NULL``. The actual freeing of the task's memory is sometimes delegated to the idle task if the task being deleted is the currently running task. -ESP-IDF FreeRTOS provides the same :cpp:func:`vTaskDelete` function. However, due to the dual-core nature, there are some behavioral differences when calling :cpp:func:`vTaskDelete` in ESP-IDF FreeRTOS: +IDF FreeRTOS provides the same :cpp:func:`vTaskDelete` function. However, due to the dual-core nature, there are some behavioral differences when calling :cpp:func:`vTaskDelete` in IDF FreeRTOS: - When deleting a task that is currently running on the other core, a yield is triggered on the other core, and the task's memory is freed by one of the idle tasks. - A deleted task's memory is freed immediately if it is not running on either core. @@ -141,50 +132,50 @@ The Vanilla FreeRTOS scheduler is best described as a **fixed priority preemptiv - The scheduler can switch execution to another task without the cooperation of the currently running task. - The scheduler periodically switches execution between ready-state tasks of the same priority in a round-robin fashion. Time slicing is governed by a tick interrupt. -The ESP-IDF FreeRTOS scheduler supports the same scheduling features, i.e., Fixed Priority, Preemption, and Time Slicing, albeit with some small behavioral differences. +The IDF FreeRTOS scheduler supports the same scheduling features, i.e., Fixed Priority, Preemption, and Time Slicing, albeit with some small behavioral differences. Fixed Priority ^^^^^^^^^^^^^^ -In Vanilla FreeRTOS, when the scheduler selects a new task to run, it always selects the current highest priority ready-state task. In ESP-IDF FreeRTOS, each core independently schedules tasks to run. When a particular core selects a task, the core will select the highest priority ready-state task that can be run by the core. A task can be run by the core if: +In Vanilla FreeRTOS, when the scheduler selects a new task to run, it always selects the current highest priority ready-state task. In IDF FreeRTOS, each core independently schedules tasks to run. When a particular core selects a task, the core will select the highest priority ready-state task that can be run by the core. A task can be run by the core if: - The task has a compatible affinity, i.e., is either pinned to that core or is unpinned. - The task is not currently being run by another core. However, please do not assume that the two highest priority ready-state tasks are always run by the scheduler, as a task's core affinity must also be accounted for. For example, given the following tasks: -- Task A of priority 10 pinned to CPU0 -- Task B of priority 9 pinned to CPU0 -- Task C of priority 8 pinned to CPU1 +- Task A of priority 10 pinned to Core 0 +- Task B of priority 9 pinned to Core 0 +- Task C of priority 8 pinned to Core 1 -The resulting schedule will have Task A running on CPU0 and Task C running on CPU1. Task B is not run even though it is the second-highest priority task. +The resulting schedule will have Task A running on Core 0 and Task C running on Core 1. Task B is not run even though it is the second-highest priority task. Preemption ^^^^^^^^^^ -In Vanilla FreeRTOS, the scheduler can preempt the currently running task if a higher priority task becomes ready to execute. Likewise in ESP-IDF FreeRTOS, each core can be individually preempted by the scheduler if the scheduler determines that a higher-priority task can run on that core. +In Vanilla FreeRTOS, the scheduler can preempt the currently running task if a higher priority task becomes ready to execute. Likewise in IDF FreeRTOS, each core can be individually preempted by the scheduler if the scheduler determines that a higher-priority task can run on that core. However, there are some instances where a higher-priority task that becomes ready can be run on multiple cores. In this case, the scheduler only preempts one core. The scheduler always gives preference to the current core when multiple cores can be preempted. In other words, if the higher priority ready task is unpinned and has a higher priority than the current priority of both cores, the scheduler will always choose to preempt the current core. For example, given the following tasks: -- Task A of priority 8 currently running on CPU0 -- Task B of priority 9 currently running on CPU1 +- Task A of priority 8 currently running on Core 0 +- Task B of priority 9 currently running on Core 1 - Task C of priority 10 that is unpinned and was unblocked by Task B -The resulting schedule will have Task A running on CPU0 and Task C preempting Task B given that the scheduler always gives preference to the current core. +The resulting schedule will have Task A running on Core 0 and Task C preempting Task B given that the scheduler always gives preference to the current core. Time Slicing ^^^^^^^^^^^^ The Vanilla FreeRTOS scheduler implements time slicing, which means that if the current highest ready priority contains multiple ready tasks, the scheduler will switch between those tasks periodically in a round-robin fashion. -However, in ESP-IDF FreeRTOS, it is not possible to implement perfect Round Robin time slicing due to the fact that a particular task may not be able to run on a particular core due to the following reasons: +However, in IDF FreeRTOS, it is not possible to implement perfect Round Robin time slicing due to the fact that a particular task may not be able to run on a particular core due to the following reasons: - The task is pinned to another core. - For unpinned tasks, the task is already being run by another core. Therefore, when a core searches the ready-state task list for a task to run, the core may need to skip over a few tasks in the same priority list or drop to a lower priority in order to find a ready-state task that the core can run. -The ESP-IDF FreeRTOS scheduler implements a Best Effort Round Robin time slicing for ready-state tasks of the same priority by ensuring that tasks that have been selected to run are placed at the back of the list, thus giving unselected tasks a higher priority on the next scheduling iteration (i.e., the next tick interrupt or yield). +The IDF FreeRTOS scheduler implements a Best Effort Round Robin time slicing for ready-state tasks of the same priority by ensuring that tasks that have been selected to run are placed at the back of the list, thus giving unselected tasks a higher priority on the next scheduling iteration (i.e., the next tick interrupt or yield). The following example demonstrates the Best Effort Round Robin time slicing in action. Assume that: @@ -196,67 +187,55 @@ The following example demonstrates the Best Effort Round Robin time slicing in a - The task list is always searched from the head. -.. code-block:: none +1. Starting state. None of the ready-state tasks have been selected to run. - -------------------------------------------------------------------------------- + .. code-block:: none - 1. Starting state. None of the ready-state tasks have been selected to run. + Head [ AX , B0 , C1 , D0 ] Tail - Head [ AX , B0 , C1 , D0 ] Tail +2. Core 0 has a tick interrupt and searches for a task to run. Task A is selected and moved to the back of the list. - -------------------------------------------------------------------------------- + .. code-block:: none - 2. Core 0 has a tick interrupt and searches for a task to run. + Core 0 ─┐ + ▼ + Head [ AX , B0 , C1 , D0 ] Tail - Task A is selected and moved to the back of the list. + [0] + Head [ B0 , C1 , D0 , AX ] Tail - Core0--| - Head [ AX , B0 , C1 , D0 ] 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. - 0 - Head [ B0 , C1 , D0 , AX ] Tail + .. code-block:: none - -------------------------------------------------------------------------------- + Core 1 ──────┐ + ▼ [0] + Head [ B0 , C1 , D0 , AX ] Tail - 3. Core 1 has a tick interrupt and searches for a task to run. + [0] [1] + Head [ B0 , D0 , AX , C1 ] Tail - Task B cannot be run due to incompatible affinity, so core 1 skips to Task C. +4. Core 0 has another tick interrupt and searches for a task to run. Task B is selected and moved to the back of the list. - Task C is selected and moved to the back of the list. + .. 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. 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. - 4. Core 0 has another tick interrupt and searches for a task to run. + .. code-block:: none - Task B is selected and moved to the back of the list. - - - Core0--| 1 - Head [ B0 , D0 , AX , C1 ] Tail - - 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. - - 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 The implications to users regarding the Best Effort Round Robin time slicing: @@ -275,14 +254,14 @@ Vanilla FreeRTOS requires that a periodic tick interrupt occurs. The tick interr - Checking if time slicing is required, i.e., triggering a context switch - Executing the application tick hook -In ESP-IDF FreeRTOS, each core receives a periodic interrupt and independently runs the tick interrupt. The tick interrupts on each core are of the same period but can be out of phase. However, the tick responsibilities listed above are not run by all cores: +In IDF FreeRTOS, each core receives a periodic interrupt and independently runs the tick interrupt. The tick interrupts on each core are of the same period but can be out of phase. However, the tick responsibilities listed above are not run by all cores: -- CPU0 executes all of the tick interrupt responsibilities listed above -- CPU1 only checks for time slicing and executes the application tick hook +- Core 0 executes all of the tick interrupt responsibilities listed above +- Core 1 only checks for time slicing and executes the application tick hook .. note:: - CPU0 is solely responsible for keeping time in ESP-IDF FreeRTOS. Therefore, anything that prevents CPU0 from incrementing the tick count, such as suspending the scheduler on CPU0, will cause the entire scheduler's timekeeping to lag behind. + Core 0 is solely responsible for keeping time in IDF FreeRTOS. Therefore, anything that prevents Core 0 from incrementing the tick count, such as suspending the scheduler on Core 0, will cause the entire scheduler's timekeeping to lag behind. Idle Tasks ^^^^^^^^^^ @@ -292,7 +271,7 @@ Vanilla FreeRTOS will implicitly create an idle task of priority 0 when the sche - Freeing the memory of deleted tasks - Executing the application idle hook -In ESP-IDF FreeRTOS, a separate pinned idle task is created for each core. The idle tasks on each core have the same responsibilities as their vanilla counterparts. +In IDF FreeRTOS, a separate pinned idle task is created for each core. The idle tasks on each core have the same responsibilities as their vanilla counterparts. Scheduler Suspension ^^^^^^^^^^^^^^^^^^^^ @@ -305,41 +284,34 @@ Vanilla FreeRTOS allows the scheduler to be suspended/resumed by calling :cpp:fu On scheduler resumption, :cpp:func:`xTaskResumeAll` catches up all of the lost ticks and unblock any timed-out tasks. -In ESP-IDF FreeRTOS, suspending the scheduler across multiple cores is not possible. Therefore when :cpp:func:`vTaskSuspendAll` is called on a particular core (e.g., core A): +In IDF FreeRTOS, suspending the scheduler across multiple cores is not possible. Therefore when :cpp:func:`vTaskSuspendAll` is called on a particular core (e.g., core A): - Task switching is disabled only on core A but interrupts for core A are left enabled. - Calling any blocking/yielding function on core A is forbidden. Time slicing is disabled on core A. - If an interrupt on core A unblocks any tasks, tasks with affinity to core A will go into core A's own pending ready task list. Unpinned tasks or tasks with affinity to other cores can be scheduled on cores with the scheduler running. - If the scheduler is suspended on all cores, tasks unblocked by an interrupt will be directed to the pending ready task lists of their pinned cores. For unpinned tasks, they will be placed in the pending ready list of the core where the interrupt occurred. -- If core A is CPU0, the tick count is frozen, and a pended tick count is incremented instead. However, the tick interrupt will still occur in order to execute the application tick hook. +- If core A is on Core 0, the tick count is frozen, and a pended tick count is incremented instead. However, the tick interrupt will still occur in order to execute the application tick hook. When :cpp:func:`xTaskResumeAll` is called on a particular core (e.g., core A): - Any tasks added to core A's pending ready task list will be resumed. -- If core A is CPU0, the pended tick count is unwound to catch up with the lost ticks. +- If core A is Core 0, the pended tick count is unwound to catch up with the lost ticks. .. warning:: - Given that scheduler suspension on ESP-IDF FreeRTOS only suspends scheduling on a particular core, scheduler suspension is **NOT** a valid method ensuring mutual exclusion between tasks when accessing shared data. Users should use proper locking primitives such as mutexes or spinlocks if they require mutual exclusion. + Given that scheduler suspension on IDF FreeRTOS only suspends scheduling on a particular core, scheduler suspension is **NOT** a valid method of ensuring mutual exclusion between tasks when accessing shared data. Users should use proper locking primitives such as mutexes or spinlocks if they require mutual exclusion. + +.. ------------------------------------------------ Critical Sections -------------------------------------------------- + +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. +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. -ESP-IDF FreeRTOS provides the same API, however, interrupts are only disabled or enabled on the current core. - -.. warning:: - - 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 ensuring mutual exclusion. Refer to :ref:`critical-sections` for more details. - - -.. ------------------------------------------------ Critical Sections -------------------------------------------------- - -.. _critical-sections: - -Critical Sections ------------------ +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. API Changes ^^^^^^^^^^^ @@ -351,7 +323,7 @@ Vanilla FreeRTOS implements critical sections by disabling interrupts, which pre - ``taskENTER_CRITICAL_FROM_ISR()`` enters a critical section from an ISR by disabling interrupt nesting - ``taskEXIT_CRITICAL_FROM_ISR()`` exits a critical section from an ISR by reenabling interrupt nesting -However, in an SMP system, merely disabling interrupts does not constitute a critical section as the presence of other cores means that a shared resource can still be concurrently accessed. Therefore, critical sections in ESP-IDF FreeRTOS are implemented using spinlocks. To accommodate the spinlocks, the ESP-IDF FreeRTOS critical section APIs contain an additional spinlock parameter as shown below: +However, in an SMP system, merely disabling interrupts does not constitute a critical section as the presence of other cores means that a shared resource can still be concurrently accessed. Therefore, critical sections in IDF FreeRTOS are implemented using spinlocks. To accommodate the spinlocks, the IDF FreeRTOS critical section APIs contain an additional spinlock parameter as shown below: - Spinlocks are of ``portMUX_TYPE`` (**not to be confused to FreeRTOS mutexes**) - ``taskENTER_CRITICAL(&spinlock)`` enters a critical from a task context @@ -361,7 +333,7 @@ However, in an SMP system, merely disabling interrupts does not constitute a cri .. note:: - The critical section API can be called recursively, i.e., nested critical sections. Entering a critical section multiple times recursively is valid so long as the critical section is exited the same number of times it was entered. However, given that critical sections can target different spinlocks, users should take care to avoid deadlocking when entering critical sections recursively. + The critical section API can be called recursively, i.e., nested critical sections. Entering a critical section multiple times recursively is valid so long as the critical section is exited the same number of times it was entered. However, given that critical sections can target different spinlocks, users should take care to avoid deadlocking when entering critical sections recursively. Spinlocks can be allocated statically or dynamically. As such, macros are provided for both static and dynamic initialization of spinlocks, as demonstrated by the following code snippets. @@ -369,35 +341,35 @@ Spinlocks can be allocated statically or dynamically. As such, macros are provid .. code:: c - // Statically allocate and initialize the spinlock - static portMUX_TYPE my_spinlock = portMUX_INITIALIZER_UNLOCKED; + // Statically allocate and initialize the spinlock + static portMUX_TYPE my_spinlock = portMUX_INITIALIZER_UNLOCKED; - void some_function(void) - { - taskENTER_CRITICAL(&my_spinlock); - // We are now in a critical section - taskEXIT_CRITICAL(&my_spinlock); - } + void some_function(void) + { + taskENTER_CRITICAL(&my_spinlock); + // We are now in a critical section + taskEXIT_CRITICAL(&my_spinlock); + } - Allocating a dynamic spinlock and initializing it using ``portMUX_INITIALIZE()``: .. code:: c - // Allocate the spinlock dynamically - portMUX_TYPE *my_spinlock = malloc(sizeof(portMUX_TYPE)); - // Initialize the spinlock dynamically - portMUX_INITIALIZE(my_spinlock); + // Allocate the spinlock dynamically + portMUX_TYPE *my_spinlock = malloc(sizeof(portMUX_TYPE)); + // Initialize the spinlock dynamically + portMUX_INITIALIZE(my_spinlock); - ... + ... - taskENTER_CRITICAL(my_spinlock); - // Access the resource - taskEXIT_CRITICAL(my_spinlock); + taskENTER_CRITICAL(my_spinlock); + // Access the resource + taskEXIT_CRITICAL(my_spinlock); Implementation ^^^^^^^^^^^^^^ -In ESP-IDF FreeRTOS, the process of a particular core entering and exiting a critical section is as follows: +In IDF FreeRTOS, the process of a particular core entering and exiting a critical section is as follows: - For ``taskENTER_CRITICAL(&spinlock)`` or ``taskENTER_CRITICAL_ISR(&spinlock)`` @@ -437,43 +409,52 @@ Misc Usually, when a context switch occurs: - - the current state of a CPU's registers is saved to the stack of the task being switched out - - the previously saved state of the CPU's registers is loaded from the stack of the task being switched in + - the current state of a core's registers are saved to the stack of the task being switched out + - the previously saved state of the core's registers is loaded from the stack of the task being switched in - However, ESP-IDF FreeRTOS implements Lazy Context Switching for the Floating Point Unit (FPU) registers of a CPU. In other words, when a context switch occurs on a particular core (e.g., CPU0), the state of the core's FPU registers is not immediately saved to the stack of the task getting switched out (e.g., Task A). The FPU registers are left untouched until: + However, IDF FreeRTOS implements Lazy Context Switching for the Floating Point Unit (FPU) registers of a core. In other words, when a context switch occurs on a particular core (e.g., Core 0), the state of the core's FPU registers is not immediately saved to the stack of the task getting switched out (e.g., Task A). The FPU registers are left untouched until: - A different task (e.g., Task B) runs on the same core and uses FPU. This will trigger an exception that saves the FPU registers to Task A's stack. - Task A gets scheduled to the same core and continues execution. Saving and restoring the FPU registers is not necessary in this case. - However, given that tasks can be unpinned and thus can be scheduled on different cores (e.g., Task A switches to CPU1), it is unfeasible to copy and restore the FPU registers across cores. Therefore, when a task utilizes FPU by using a ``float`` type in its call flow, ESP-IDF FreeRTOS will automatically pin the task to the current core it is running on. This ensures that all tasks that use FPU are always pinned to a particular core. + However, given that tasks can be unpinned and thus can be scheduled on different cores (e.g., Task A switches to Core 1), it is unfeasible to copy and restore the FPU registers across cores. Therefore, when a task utilizes FPU by using a ``float`` type in its call flow, IDF FreeRTOS will automatically pin the task to the current core it is running on. This ensures that all tasks that use FPU are always pinned to a particular core. - Furthermore, ESP-IDF FreeRTOS by default does not support the usage of FPU within an interrupt context given that the FPU register state is tied to a particular task. + Furthermore, IDF FreeRTOS by default does not support the usage of FPU within an interrupt context given that the FPU register state is tied to a particular task. - .. only: esp32 + .. only:: esp32 - .. note:: + .. note:: - Users that require the use of the ``float`` type in an ISR routine should refer to the :ref:`CONFIG_FREERTOS_FPU_IN_ISR` configuration option. + Users that require the use of the ``float`` type in an ISR routine should refer to the :ref:`CONFIG_FREERTOS_FPU_IN_ISR` configuration option. .. note:: - ESP targets that contain an FPU do not support hardware acceleration for double precision floating point arithmetic (``double``). Instead, ``double`` is implemented via software, hence the behavioral restrictions regarding the ``float`` type do not apply to ``double``. Note that due to the lack of hardware acceleration, ``double`` operations may consume significantly more CPU time in comparison to ``float``. + ESP targets that contain an FPU do not support hardware acceleration for double precision floating point arithmetic (``double``). Instead, ``double`` is implemented via software, hence the behavioral restrictions regarding the ``float`` type do not apply to ``double``. Note that due to the lack of hardware acceleration, ``double`` operations may consume significantly more CPU time in comparison to ``float``. .. -------------------------------------------------- Single Core ----------------------------------------------------- -.. _freertos-smp-single-core: +.. _freertos-idf-single-core: -ESP-IDF FreeRTOS Single Core -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Single-Core Mode +---------------- -Although ESP-IDF FreeRTOS is an SMP scheduler, some ESP targets are single-core (such as ESP32-S2 and ESP32-C3). When building ESP-IDF applications for these targets, ESP-IDF FreeRTOS is still used but the number of cores will be set to ``1`` (i.e., the :ref:`CONFIG_FREERTOS_UNICORE` will always be enabled for single-core targets). +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 multicore targets (such as ESP32 and ESP32-S3), :ref:`CONFIG_FREERTOS_UNICORE` can also be set. This results in ESP-IDF FreeRTOS only running on CPU0, and all other cores will be inactive. +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. -.. note:: +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: + +- 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). +- All SMP specific data is removed from single-core builds. + +SMP APIs can still be called in single-core mode. These APIs remain exposed to allow source code to be built for single-core and multi-core, without needing to call a different set of APIs. However, SMP APIs will not exhibit any SMP behavior in single-core mode, thus becoming equivalent to their single-core counterparts. For example: + +- any ``...ForCore(..., BaseType_t xCoreID)`` SMP API will only accept ``0`` as a valid value for ``xCoreID``. +- ``...PinnedToCore()`` task creation APIs will simply ignore the ``xCoreID`` core affinity argument. +- Critical section APIs will still require a spinlock argument, but no spinlock will be taken and critical sections revert to simply disabling/enabling interrupts. - Users should bear in mind that enabling :ref:`CONFIG_FREERTOS_UNICORE` **is NOT equivalent to running Vanilla FreeRTOS**. The additional APIs of ESP-IDF FreeRTOS can still be called, and the behavior changes of ESP-IDF FreeRTOS incur a small amount of overhead even when compiled for only a single core. .. ------------------------------------------------- API References ---------------------------------------------------- diff --git a/docs/zh_CN/api-reference/system/freertos.rst b/docs/zh_CN/api-reference/system/freertos.rst index f6663e2375..4269d4d418 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-smp-single-core` (使用 ESP-IDF FreeRTOS 时)或参考 Amazon SMP FreeRTOS 的官方文档,还可以在 ESP-IDF 组件中搜索 ``CONFIG_FREERTOS_UNICORE``。 +- :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``。 .. only:: not SOC_HP_CPU_HAS_MULTIPLE_CORES diff --git a/docs/zh_CN/api-reference/system/freertos_idf.rst b/docs/zh_CN/api-reference/system/freertos_idf.rst index 5d4830683d..dce5928b36 100644 --- a/docs/zh_CN/api-reference/system/freertos_idf.rst +++ b/docs/zh_CN/api-reference/system/freertos_idf.rst @@ -18,7 +18,7 @@ ESP-IDF FreeRTOS 以 Vanilla FreeRTOS v10.5.1 为基础,为支持 SMP,在 AP .. 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-smp-single-core`。 + 启用 :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`。 本文档包含以下小节。 @@ -462,7 +462,7 @@ ESP-IDF FreeRTOS 中,特定核进入和退出临界区的过程如下: .. -------------------------------------------------- Single Core ----------------------------------------------------- -.. _freertos-smp-single-core: +.. _freertos-idf-single-core: 单核 ESP-IDF FreeRTOS ^^^^^^^^^^^^^^^^^^^^^ From 95a5d3ff369237ec7b35cb3fb2c4696872f223ce Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Mon, 18 Dec 2023 05:05:26 +0800 Subject: [PATCH 2/3] docs(freertos/idf): Add IDF FreeRTOS upgarde migration notes for v5.2 --- docs/en/migration-guides/release-5.x/5.2/system.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/en/migration-guides/release-5.x/5.2/system.rst b/docs/en/migration-guides/release-5.x/5.2/system.rst index 916ad605f3..7f2b7a8c82 100644 --- a/docs/en/migration-guides/release-5.x/5.2/system.rst +++ b/docs/en/migration-guides/release-5.x/5.2/system.rst @@ -6,6 +6,19 @@ System FreeRTOS -------- +IDF FreeRTOS Upgrade +^^^^^^^^^^^^^^^^^^^^ + +The IDF FreeRTOS kernel (which is a dual-core SMP implementation of FreeRTOS) has been upgraded to be based on Vanilla FreeRTOS v10.5.1. With this upgrade, the design and implementation of IDF FreeRTOS has also been changed significantly. As a result, users should take not of the following changes to kernel behavior and API: + +- When enabling single-core mode via the :ref:`CONFIG_FREERTOS_UNICORE` option, the kernel's behavior will now be identical to Vanilla FreeRTOS (see :ref:`freertos-idf-single-core` for more details). +- For SMP related APIs that were added by IDF FreeRTOS, checks on ``xCoreID`` arguments are now stricter. Providing out of range values for ``xCoreID`` arguments will now trigger an assert. +- The following SMP related APIs are now deprecated and replaced due to naming consistency reasons: + + - ``xTaskGetAffinity()`` is deprecated, call :cpp:func:`xTaskGetCoreID` instead. + - ``xTaskGetIdleTaskHandleForCPU()`` is deprecated, call :cpp:func:`xTaskGetIdleTaskHandleForCore` instead. + - ``xTaskGetCurrentTaskHandleForCPU()`` is deprecated, call :cpp:func:`xTaskGetCurrentTaskHandleForCore` instead. + Task Snapshot ^^^^^^^^^^^^^ From 53be71f8f25f7cffb3fabceb35305dbf50d08c20 Mon Sep 17 00:00:00 2001 From: mofeifei Date: Wed, 20 Dec 2023 12:10:40 +0800 Subject: [PATCH 3/3] docs: Update cn trans for freertos docs --- docs/en/api-reference/system/freertos.rst | 2 +- docs/en/api-reference/system/freertos_idf.rst | 12 +- docs/zh_CN/api-reference/system/freertos.rst | 14 +- .../api-reference/system/freertos_idf.rst | 291 ++++++++---------- 4 files changed, 150 insertions(+), 169 deletions(-) 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 ----------------------------------------------------