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.
This commit is contained in:
Darian Leung 2023-12-14 00:04:52 +08:00
parent 39e68f87d6
commit 31b195d150
4 changed files with 135 additions and 154 deletions

View File

@ -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``

View File

@ -1,31 +1,26 @@
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 <https://www.freertos.org/index.html>`_ 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
.. -------------------------------------------- Symmetric Multiprocessing ----------------------------------------------
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,68 +187,56 @@ 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.
--------------------------------------------------------------------------------
1. Starting state. None of the ready-state tasks have been selected to run.
.. code-block:: none
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.
2. Core 0 has a tick interrupt and searches for a task to run.
.. code-block:: none
Task A is selected and moved to the back of the list.
Core0--|
Core 0 ─┐
Head [ AX , B0 , C1 , D0 ] Tail
0
[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.
.. code-block:: none
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.
Core1-------| 0
Core 1 ──────┐
▼ [0]
Head [ B0 , C1 , D0 , AX ] Tail
0 1
[0] [1]
Head [ B0 , D0 , AX , C1 ] Tail
--------------------------------------------------------------------------------
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.
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
Core 0 ─┐
▼ [1]
Head [ B0 , D0 , AX , C1 ] Tail
1 0
[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.
.. code-block:: none
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
Core 1 ──────┐
▼ [0]
Head [ D0 , AX , C1 , B0 ] Tail
0 1
[0] [1]
Head [ D0 , C1 , B0 , AX ] Tail
The implications to users regarding the Best Effort Round Robin time slicing:
- Users cannot expect multiple ready-state tasks of the same priority to run sequentially as is the case in Vanilla FreeRTOS. As demonstrated in the example above, a core may need to skip over tasks.
@ -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
@ -397,7 +369,7 @@ Spinlocks can be allocated statically or dynamically. As such, macros are provid
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,19 +409,19 @@ 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::
@ -462,18 +434,27 @@ Misc
.. -------------------------------------------------- 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 ----------------------------------------------------

View File

@ -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

View File

@ -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 FreeRTOSESP-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 FreeRTOSESP-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
^^^^^^^^^^^^^^^^^^^^^