|
|
@@ -1,147 +1,192 @@
|
|
|
-ESP-IDF FreeRTOS SMP Changes
|
|
|
-============================
|
|
|
+ESP-IDF FreeRTOS (SMP)
|
|
|
+======================
|
|
|
+
|
|
|
+.. note::
|
|
|
+ This document assumes that the reader has a requisite understanding of Vanilla FreeRTOS (its features, behavior, and API usage). Refer to the `Vanilla FreeRTOS documentation <https://www.freertos.org/index.html>`_ for more details.
|
|
|
+
|
|
|
+This document describes the API and behavioral differences between Vanilla FreeRTOS and ESP-IDF FreeRTOS that were made in order to support Symmetric Multiprocessing (SMP). This document is split into the following parts.
|
|
|
+
|
|
|
+.. contents:: Contents
|
|
|
+ :depth: 3
|
|
|
+
|
|
|
+
|
|
|
+.. ---------------------------------------------------- Overview -------------------------------------------------------
|
|
|
|
|
|
Overview
|
|
|
--------
|
|
|
|
|
|
-.. only:: not CONFIG_FREERTOS_UNICORE
|
|
|
-
|
|
|
- The vanilla FreeRTOS is designed to run on a single core. However the {IDF_TARGET_NAME} is
|
|
|
- dual core containing a Protocol CPU (known as **CPU 0** or **PRO_CPU**) and an
|
|
|
- Application CPU (known as **CPU 1** or **APP_CPU**). The two cores are
|
|
|
- identical in practice and share the same memory. This allows the two cores to
|
|
|
- run tasks interchangeably between them.
|
|
|
-
|
|
|
-The ESP-IDF FreeRTOS is a modified version of vanilla FreeRTOS which supports
|
|
|
-symmetric multiprocessing (SMP). ESP-IDF FreeRTOS is based on the Xtensa port
|
|
|
-of FreeRTOS v10.2.0. This guide outlines the major differences between vanilla
|
|
|
-FreeRTOS and ESP-IDF FreeRTOS. The API reference for vanilla FreeRTOS can be
|
|
|
-found via https://www.freertos.org/a00106.html
|
|
|
-
|
|
|
-For information regarding features that are exclusive to ESP-IDF FreeRTOS,
|
|
|
-see :doc:`ESP-IDF FreeRTOS Additions</api-reference/system/freertos_additions>`.
|
|
|
-
|
|
|
-.. only:: not CONFIG_FREERTOS_UNICORE
|
|
|
-
|
|
|
- :ref:`tasks-and-task-creation`: Use :cpp:func:`xTaskCreatePinnedToCore` or
|
|
|
- :cpp:func:`xTaskCreateStaticPinnedToCore` to create tasks in ESP-IDF FreeRTOS. The
|
|
|
- last parameter of the two functions is ``xCoreID``. This parameter specifies
|
|
|
- which core the task is pinned to. Acceptable values are ``0`` for **PRO_CPU**,
|
|
|
- ``1`` for **APP_CPU**, or ``tskNO_AFFINITY`` which allows the task to run on
|
|
|
- both.
|
|
|
-
|
|
|
- :ref:`round-robin-scheduling`: The ESP-IDF FreeRTOS scheduler implements a "Best Effort Round-Robin Scheduling" instead of the ideal Round-Robin scheduling in vanilla FreeRTOS.
|
|
|
-
|
|
|
- :ref:`scheduler-suspension`: Suspending the scheduler in ESP-IDF FreeRTOS will only
|
|
|
- affect the scheduler on the the calling core. In other words, calling
|
|
|
- :cpp:func:`vTaskSuspendAll` on **PRO_CPU** will not prevent **APP_CPU** from scheduling, and
|
|
|
- vice versa. Use critical sections or semaphores instead for simultaneous
|
|
|
- access protection.
|
|
|
-
|
|
|
- :ref:`tick-interrupt-synchronicity`: Tick interrupts of **PRO_CPU** and **APP_CPU**
|
|
|
- are not synchronized. Do not expect to use :cpp:func:`vTaskDelay` or
|
|
|
- :cpp:func:`vTaskDelayUntil` as an accurate method of synchronizing task execution
|
|
|
- between the two cores. Use a counting semaphore instead as their context
|
|
|
- switches are not tied to tick interrupts due to preemption.
|
|
|
-
|
|
|
- :ref:`critical-sections`: In ESP-IDF FreeRTOS, critical sections are implemented using
|
|
|
- mutexes. Entering critical sections involve taking a mutex, then disabling the
|
|
|
- scheduler and interrupts of the calling core. However the other core is left
|
|
|
- unaffected. If the other core attemps to take same mutex, it will spin until
|
|
|
- the calling core has released the mutex by exiting the critical section.
|
|
|
-
|
|
|
-.. only:: esp32
|
|
|
-
|
|
|
- :ref:`floating-points`: The ESP32 supports hardware acceleration of single
|
|
|
- precision floating point arithmetic (``float``). However the use of hardware
|
|
|
- acceleration leads to some behavioral restrictions in ESP-IDF FreeRTOS.
|
|
|
- Therefore, tasks that utilize ``float`` will automatically be pinned to a core if
|
|
|
- not done so already. Furthermore, ``float`` cannot be used in interrupt service
|
|
|
- routines.
|
|
|
-
|
|
|
-:ref:`deletion-callbacks`: Deletion callbacks are called automatically during task deletion and are
|
|
|
-used to free memory pointed to by TLSP. Call
|
|
|
-:cpp:func:`vTaskSetThreadLocalStoragePointerAndDelCallback()` to set TLSP and Deletion
|
|
|
-Callbacks.
|
|
|
-
|
|
|
-:ref:`esp-idf-freertos-configuration`: Several aspects of ESP-IDF FreeRTOS can be
|
|
|
-set in the project configuration (``idf.py menuconfig``) such as running ESP-IDF in
|
|
|
-Unicore (single core) Mode, or configuring the number of Thread Local Storage Pointers
|
|
|
-each task will have.
|
|
|
-
|
|
|
-It is not necessary to manually start the FreeRTOS scheduler by calling :cpp:func:`vTaskStartScheduler`. In ESP-IDF the
|
|
|
-scheduler is started by the :doc:`startup` and is already running when the ``app_main`` function is called (see :ref:`app-main-task` for details).
|
|
|
-
|
|
|
-.. _tasks-and-task-creation:
|
|
|
-
|
|
|
-Tasks and Task Creation
|
|
|
------------------------
|
|
|
-
|
|
|
-Tasks in ESP-IDF FreeRTOS are designed to run on a particular core, therefore
|
|
|
-two new task creation functions have been added to ESP-IDF FreeRTOS by
|
|
|
-appending ``PinnedToCore`` to the names of the task creation functions in
|
|
|
-vanilla FreeRTOS. The vanilla FreeRTOS functions of :cpp:func:`xTaskCreate`
|
|
|
-and :cpp:func:`xTaskCreateStatic` have led to the addition of
|
|
|
-:cpp:func:`xTaskCreatePinnedToCore` and :cpp:func:`xTaskCreateStaticPinnedToCore` in
|
|
|
-ESP-IDF FreeRTOS
|
|
|
-
|
|
|
-For more details see :component_file:`freertos/FreeRTOS-Kernel/tasks.c`
|
|
|
-
|
|
|
-The ESP-IDF FreeRTOS task creation functions are nearly identical to their
|
|
|
-vanilla counterparts with the exception of the extra parameter known as
|
|
|
-``xCoreID``. This parameter specifies the core on which the task should run on
|
|
|
-and can be one of the following values.
|
|
|
-
|
|
|
- - ``0`` pins the task to **PRO_CPU**
|
|
|
- - ``1`` pins the task to **APP_CPU**
|
|
|
- - ``tskNO_AFFINITY`` allows the task to be run on both CPUs
|
|
|
-
|
|
|
-For example ``xTaskCreatePinnedToCore(tsk_callback, “APP_CPU Task”, 1000, NULL, 10, NULL, 1)``
|
|
|
-creates a task of priority 10 that is pinned to **APP_CPU** with a stack size
|
|
|
-of 1000 bytes. It should be noted that the ``uxStackDepth`` parameter in
|
|
|
-vanilla FreeRTOS specifies a task’s stack depth in terms of the number of
|
|
|
-words, whereas ESP-IDF FreeRTOS specifies the stack depth in terms of bytes.
|
|
|
-
|
|
|
-Note that the vanilla FreeRTOS functions :cpp:func:`xTaskCreate` and
|
|
|
-:cpp:func:`xTaskCreateStatic` have been defined in ESP-IDF FreeRTOS as inline functions which call
|
|
|
-:cpp:func:`xTaskCreatePinnedToCore` and :cpp:func:`xTaskCreateStaticPinnedToCore`
|
|
|
-respectively with ``tskNO_AFFINITY`` as the ``xCoreID`` value.
|
|
|
-
|
|
|
-Each Task Control Block (TCB) in ESP-IDF stores the ``xCoreID`` as a member.
|
|
|
-Hence when each core calls the scheduler to select a task to run, the
|
|
|
-``xCoreID`` member will allow the scheduler to determine if a given task is
|
|
|
-permitted to run on the core that called it.
|
|
|
-
|
|
|
-Scheduling
|
|
|
-----------
|
|
|
-
|
|
|
-The vanilla FreeRTOS implements scheduling in the ``vTaskSwitchContext()``
|
|
|
-function. This function is responsible for selecting the highest priority task
|
|
|
-to run from a list of tasks in the Ready state known as the Ready Tasks List
|
|
|
-(described in the next section). In ESP-IDF FreeRTOS, each core will call
|
|
|
-``vTaskSwitchContext()`` independently to select a task to run from the
|
|
|
-Ready Tasks List which is shared between both cores. There are several
|
|
|
-differences in scheduling behavior between vanilla and ESP-IDF FreeRTOS such as
|
|
|
-differences in Round Robin scheduling, scheduler suspension, and tick interrupt
|
|
|
-synchronicity.
|
|
|
-
|
|
|
-.. _round-robin-scheduling:
|
|
|
-
|
|
|
-Round Robin Scheduling
|
|
|
-^^^^^^^^^^^^^^^^^^^^^^
|
|
|
-
|
|
|
-Given multiple tasks in the Ready state and of the same priority, vanilla FreeRTOS implements Round Robin scheduling between multiple ready state tasks of the same priority. This will result in running those tasks in turn each time the scheduler is called (e.g. when the tick interrupt occurs or when a task blocks/yields).
|
|
|
-
|
|
|
-On the other hand, it is not possible for the ESP-IDF FreeRTOS scheduler to implement perfect Round Robin due to the fact that a particular task may not be able to run on a particular core due to the following reasons:
|
|
|
+The original FreeRTOS (hereinafter referred to as Vanilla FreeRTOS) is a small and efficient Real Time Operating System supported on many single-core MCUs and SoCs. However, numerous ESP targets (such as the ESP32 and ESP32-S3) are capable of dual core symmetric multiprocessing (SMP). Therefore, the version of FreeRTOS used in ESP-IDF (hereinafter referred to as ESP-IDF FreeRTOS) is a modified version of Vanilla FreeRTOS v10.4.3. Theses modifications allow ESP-IDF FreeRTOS to utilize the dual core SMP capabilities of ESP SoCs.
|
|
|
+
|
|
|
+.. only:: CONFIG_FREERTOS_UNICORE
|
|
|
+
|
|
|
+ .. note::
|
|
|
+ Some ESP targets (such as the ESP32-S2 and ESP32-C3) are single core SoCs. ESP-IDF applications built for these targets will be built with **ESP-IDF FreeRTOS instead of Vanilla FreeRTOS**. However, the builds for these single core targets will always have the :ref:`CONFIG_FREERTOS_UNICORE` configuration enabled. See :ref:`freertos-smp-single-core` for more details.
|
|
|
+
|
|
|
+.. note::
|
|
|
+ - For information regarding features that have been added to ESP-IDF FreeRTOS, see :doc:`ESP-IDF FreeRTOS Additions</api-reference/system/freertos_additions>`.
|
|
|
+ - For a detailed ESP-IDF FreeRTOS API Reference, see :doc:`FreeRTOS API reference<../api-reference/system/freertos>`.
|
|
|
+
|
|
|
+
|
|
|
+.. -------------------------------------------- Symmetric Multiprocessing ----------------------------------------------
|
|
|
+
|
|
|
+Symmetric Multiprocessing
|
|
|
+-------------------------
|
|
|
+
|
|
|
+Basic Concepts
|
|
|
+^^^^^^^^^^^^^^
|
|
|
+
|
|
|
+SMP (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...
|
|
|
+
|
|
|
+- 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 will have 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 increases 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/should only run on a particular core. Therefore, threads in an SMP systems will 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 will only be 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.
|
|
|
+
|
|
|
+SMP on an ESP Target
|
|
|
+^^^^^^^^^^^^^^^^^^^^
|
|
|
+
|
|
|
+ESP targets (such as the ESP32, ESP32-S3) are dual core SMP SoCs. These targets have the following hardware features that make them SMP capable:
|
|
|
+
|
|
|
+- Two identical cores known as CPU0 (i.e., Protocol CPU or PRO_CPU) and CPU1 (i.e., Application CPU or APP_CPU). 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, their access will be serialized at the memory bus level.
|
|
|
+ - 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 and interrupt on another CPU. This allows cores to signal each other.
|
|
|
+
|
|
|
+.. note::
|
|
|
+ The "PRO_CPU" and "APP_CPU" aliases for CPU0 and CPU1 exist in ESP-IDF as they reflect how typical IDF applications will utilize the two CPUs. Typically, the tasks responsible for handling wireless networking (e.g., WiFi or Bluetooth) will be pinned to CPU0 (thus the name PRO_CPU), whereas the tasks handling the remainder of the application will be pinned to CPU1 (thus the name APP_CPU).
|
|
|
+
|
|
|
+
|
|
|
+.. ------------------------------------------------------ Tasks --------------------------------------------------------
|
|
|
+
|
|
|
+Tasks
|
|
|
+-----
|
|
|
+
|
|
|
+Creation
|
|
|
+^^^^^^^^
|
|
|
+
|
|
|
+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:
|
|
|
+
|
|
|
+- :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 functions API differ from their vanilla counter parts 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
|
|
|
+
|
|
|
+Note that ESP-IDF FreeRTOS still supports the vanilla versions of the task creation functions. However, they have been modified to simply call their ``PinnedToCore`` counterparts with ``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 number of words, whereas in ESP-IDF FreeRTOS, the task stack sizes are specified in bytes.
|
|
|
+
|
|
|
+Execution
|
|
|
+^^^^^^^^^
|
|
|
+
|
|
|
+The anatomy of a task in ESP-IDF FreeRTOS is the same as Vanilla FreeRTOS. More specifically, ESP-IDF FreeRTOS tasks:
|
|
|
+
|
|
|
+- Can only be in one of following states: Running, Ready, Blocked, or Suspended.
|
|
|
+- Task functions are typically implemented as an infinite loop
|
|
|
+- Task functions should never return
|
|
|
+
|
|
|
+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:
|
|
|
+
|
|
|
+- When deleting a task that is pinned to the other core, that task's memory is always freed by the idle task of the other core (due to the need to clear FPU registers).
|
|
|
+- 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 (depending on the task's core affinity)
|
|
|
+- A deleted task's memory is freed immediately if...
|
|
|
+
|
|
|
+ - The tasks is currently running on this core and is also pinned to this core
|
|
|
+ - The task is not currently running and is not pinned to any core
|
|
|
+
|
|
|
+Users should avoid calling :cpp:func:`vTaskDelete` on a task that is currently running on the other core. This is due to the fact that it is difficult to know what the task currently running on the other core is executing, thus can lead to unpredictable behavior such as...
|
|
|
+
|
|
|
+- Deleting a task that is holding a mutex
|
|
|
+- Deleting a task that has yet to free memory it previously allocated
|
|
|
+
|
|
|
+Where possible, users should design their application such that :cpp:func:`vTaskDelete` is only ever called on tasks in a known state. For example:
|
|
|
+
|
|
|
+- Tasks self deleting (via ``vTaskDelete(NULL)``) when their execution is complete and have also cleaned up all resources used within the task.
|
|
|
+- Tasks placing themselves in the suspend state (via :cpp:func:`vTaskSuspend`) before being deleted by another task.
|
|
|
+
|
|
|
+
|
|
|
+.. --------------------------------------------------- Scheduling ------------------------------------------------------
|
|
|
+
|
|
|
+SMP Scheduler
|
|
|
+-------------
|
|
|
+
|
|
|
+The Vanilla FreeRTOS scheduler is best described as a **Fixed Priority Preemptive scheduler with Time Slicing** meaning that:
|
|
|
+
|
|
|
+- Each tasks is given a constant priority upon creation. The scheduler executes highest priority ready state task
|
|
|
+- The scheduler can switch execution to another task without the cooperation of the currently running task
|
|
|
+- The scheduler will periodically switch 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.
|
|
|
+
|
|
|
+Fixed Priority
|
|
|
+^^^^^^^^^^^^^^
|
|
|
+
|
|
|
+In Vanilla FreeRTOS, when scheduler selects a new task to run, it will always select the current highest priority ready state task. In ESP-IDF FreeRTOS, each core will independently schedule 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, users should 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
|
|
|
+
|
|
|
+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.
|
|
|
+
|
|
|
+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.
|
|
|
+
|
|
|
+However, there are some instances where a higher priority task that becomes ready can be run on multiple cores. In this case, the scheduler will only preempt 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 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.
|
|
|
+
|
|
|
+Time Slicing
|
|
|
+^^^^^^^^^^^^
|
|
|
+
|
|
|
+The Vanilla FreeRTOS scheduler implements time slicing meaning that if 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:
|
|
|
|
|
|
- The task is pinned to the 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 scheduling for ready state tasks of the same priority by ensuring that tasks that have been selected to run will be 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 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 will be 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 Scheduling in action. Assume that:
|
|
|
+The following example demonstrates the Best Effort Round Robin time slicing in action. Assume that:
|
|
|
|
|
|
- There are four ready state tasks of the same priority ``AX, B0, C1, D1`` where:
|
|
|
- The priority is the current highest priority with ready state tasks
|
|
|
@@ -205,248 +250,179 @@ The following example demonstrates the Best Effort Round Robin Scheduling in act
|
|
|
Head [ D0 , C1 , B0 , AX ] Tail
|
|
|
|
|
|
|
|
|
-The implications to users regarding the Best Effort Round Robin Scheduling:
|
|
|
+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.
|
|
|
- However, given enough ticks, a task will eventually be given some processing time.
|
|
|
- If a core cannot find a task runnable task at the highest ready state priority, it will drop to a lower priority to search for tasks.
|
|
|
-- To achieve ideal round robin scheduling, users should ensure that all tasks of a particular priority are pinned to the same core.
|
|
|
+- To achieve ideal round robin time slicing, users should ensure that all tasks of a particular priority are pinned to the same core.
|
|
|
+
|
|
|
+Tick Interrupts
|
|
|
+^^^^^^^^^^^^^^^
|
|
|
+
|
|
|
+Vanilla FreeRTOS requires that a periodic tick interrupt occurs. The tick interrupt is responsible for:
|
|
|
+
|
|
|
+- Incrementing the scheduler's tick count
|
|
|
+- Unblocking any blocked tasks that have timed out
|
|
|
+- Checking if time slicing is required (i.e., triggering a context switch)
|
|
|
+- Executing the application tick hook
|
|
|
+
|
|
|
+In ESP-IDF FreeRTOS, each core will receive a periodic interrupt and independently run the tick interrupt. The tick interrupts on each core are of the same period but can be out of phase. Furthermore, the tick interrupt responsibilities listed above are not run by all cores:
|
|
|
+
|
|
|
+- CPU0 will execute all of the tick interrupt responsibilities listed above
|
|
|
+- CPU1 will only check for time slicing and execute 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 schedulers time keeping to lag behind.
|
|
|
|
|
|
-.. _scheduler-suspension:
|
|
|
+Idle Tasks
|
|
|
+^^^^^^^^^^
|
|
|
+
|
|
|
+Vanilla FreeRTOS will implicitly create an idle task of priority 0 when the scheduler is started. The idle task runs when no other task is ready to run, and it has the following responsibilities:
|
|
|
+
|
|
|
+- 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.
|
|
|
|
|
|
Scheduler Suspension
|
|
|
^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
-In vanilla FreeRTOS, suspending the scheduler via :cpp:func:`vTaskSuspendAll` will
|
|
|
-prevent calls of ``vTaskSwitchContext`` from context switching until the
|
|
|
-scheduler has been resumed with :cpp:func:`xTaskResumeAll`. However servicing ISRs
|
|
|
-are still permitted. Therefore any changes in task states as a result from the
|
|
|
-current running task or ISRs will not be executed until the scheduler is
|
|
|
-resumed. Scheduler suspension in vanilla FreeRTOS is a common protection method
|
|
|
-against simultaneous access of data shared between tasks, whilst still allowing
|
|
|
-ISRs to be serviced.
|
|
|
+Vanilla FreeRTOS allows the scheduler to be suspended/resumed by calling :cpp:func:`vTaskSuspendAll` and :cpp:func:`xTaskResumeAll` respectively. While the scheduler is suspended:
|
|
|
|
|
|
-In ESP-IDF FreeRTOS, :cpp:func:`xTaskSuspendAll` will only prevent calls of
|
|
|
-``vTaskSwitchContext()`` from switching contexts on the core that called for the
|
|
|
-suspension. Hence if **PRO_CPU** calls :cpp:func:`vTaskSuspendAll`, **APP_CPU** will
|
|
|
-still be able to switch contexts. If data is shared between tasks that are
|
|
|
-pinned to different cores, scheduler suspension is **NOT** a valid method of
|
|
|
-protection against simultaneous access. Consider using critical sections
|
|
|
-(disables interrupts) or semaphores (does not disable interrupts) instead when
|
|
|
-protecting shared resources in ESP-IDF FreeRTOS.
|
|
|
+- Task switching is disabled but interrupts are left enabled.
|
|
|
+- Calling any blocking/yielding function is forbidden, and time slicing is disabled.
|
|
|
+- The tick count is frozen (but the tick interrupt will still occur to execute the application tick hook)
|
|
|
|
|
|
-In general, it's better to use other RTOS primitives like mutex semaphores to protect
|
|
|
-against data shared between tasks, rather than :cpp:func:`vTaskSuspendAll`.
|
|
|
+On scheduler resumption, :cpp:func:`xTaskResumeAll` will catch 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:
|
|
|
|
|
|
-.. _tick-interrupt-synchronicity:
|
|
|
+- Task switching is disabled only on the current core but interrupts for the current core are left enabled
|
|
|
+- Calling any blocking/yielding function on the current core is forbidden. Time slicing is disabled on the current core.
|
|
|
+- If suspending on CPU0, the tick count is frozen. The tick interrupt will still occur to execute the application tick hook.
|
|
|
|
|
|
-Tick Interrupt Synchronicity
|
|
|
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
+When resuming the scheduler on CPU0, :cpp:func:`xTaskResumeAll` will catch up all of the lost ticks and unblock any timed out tasks.
|
|
|
|
|
|
-In ESP-IDF FreeRTOS, tasks on different cores that unblock on the same tick
|
|
|
-count might not run at exactly the same time due to the scheduler calls from
|
|
|
-each core being independent, and the tick interrupts to each core being
|
|
|
-unsynchronized.
|
|
|
-
|
|
|
-In vanilla FreeRTOS the tick interrupt triggers a call to
|
|
|
-:cpp:func:`xTaskIncrementTick` which is responsible for incrementing the tick
|
|
|
-counter, checking if tasks which have called :cpp:func:`vTaskDelay` have fulfilled
|
|
|
-their delay period, and moving those tasks from the Delayed Task List to the
|
|
|
-Ready Task List. The tick interrupt will then call the scheduler if a context
|
|
|
-switch is necessary.
|
|
|
-
|
|
|
-In ESP-IDF FreeRTOS, delayed tasks are unblocked with reference to the tick
|
|
|
-interrupt on PRO_CPU as PRO_CPU is responsible for incrementing the shared tick
|
|
|
-count. However tick interrupts to each core might not be synchronized (same
|
|
|
-frequency but out of phase) hence when PRO_CPU receives a tick interrupt,
|
|
|
-APP_CPU might not have received it yet. Therefore if multiple tasks of the same
|
|
|
-priority are unblocked on the same tick count, the task pinned to PRO_CPU will
|
|
|
-run immediately whereas the task pinned to APP_CPU must wait until APP_CPU
|
|
|
-receives its out of sync tick interrupt. Upon receiving the tick interrupt,
|
|
|
-APP_CPU will then call for a context switch and finally switches contexts to
|
|
|
-the newly unblocked task.
|
|
|
-
|
|
|
-Therefore, task delays should **NOT** be used as a method of synchronization
|
|
|
-between tasks in ESP-IDF FreeRTOS. Instead, consider using a counting semaphore
|
|
|
-to unblock multiple tasks at the same time.
|
|
|
-
|
|
|
-
|
|
|
-.. _critical-sections:
|
|
|
-
|
|
|
-Critical Sections & Disabling Interrupts
|
|
|
-----------------------------------------
|
|
|
-
|
|
|
-Vanilla FreeRTOS implements critical sections with ``taskENTER_CRITICAL()`` which
|
|
|
-calls ``portDISABLE_INTERRUPTS()``. This prevents preemptive context switches and
|
|
|
-servicing of ISRs during a critical section. Therefore, critical sections are
|
|
|
-used as a valid protection method against simultaneous access in vanilla FreeRTOS.
|
|
|
-
|
|
|
-.. only:: not CONFIG_FREERTOS_UNICORE
|
|
|
-
|
|
|
- On the other hand, {IDF_TARGET_NAME} has no hardware method for cores to disable each
|
|
|
- other’s interrupts. Calling ``portDISABLE_INTERRUPTS()`` will have no effect on
|
|
|
- the interrupts of the other core. Therefore, disabling interrupts is **NOT**
|
|
|
- a valid protection method against simultaneous access to shared data as it
|
|
|
- leaves the other core free to access the data even if the current core has
|
|
|
- disabled its own interrupts.
|
|
|
+.. warning::
|
|
|
+ Given that scheduler suspension on ESP-IDF FreeRTOS will only suspend 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.
|
|
|
|
|
|
-.. only:: CONFIG_FREERTOS_UNICORE
|
|
|
+Disabling Interrupts
|
|
|
+^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
- ESP-IDF contains some modifications to work with dual core concurrency,
|
|
|
- and the dual core API is used even on a single core only chip.
|
|
|
-
|
|
|
-For this reason, ESP-IDF FreeRTOS implements critical sections using special
|
|
|
-mutexes, referred by ``portMUX_Type`` objects. These are implemented on top of a
|
|
|
-specific spinlock component. Calls to ``taskENTER_CRITICAL`` or
|
|
|
-``taskEXIT_CRITICAL`` each provide a spinlock object as an argument. The
|
|
|
-spinlock is associated with a shared resource requiring access protection. When
|
|
|
-entering a critical section in ESP-IDF FreeRTOS, the calling core will disable
|
|
|
-interrupts similar to the vanilla FreeRTOS implementation, and will then take the
|
|
|
-spinlock and enter the critical section. The other core is unaffected at this point,
|
|
|
-unless it enters its own critical section and attempts to take the same spinlock.
|
|
|
-In that case it will spin until the lock is released. Therefore, the ESP-IDF FreeRTOS
|
|
|
-implementation of critical sections allows a core to have protected access to a shared
|
|
|
-resource without disabling the other core. The other core will only be affected if it
|
|
|
-tries to concurrently access the same resource.
|
|
|
-
|
|
|
-The ESP-IDF FreeRTOS critical section functions have been modified as follows…
|
|
|
-
|
|
|
- - ``taskENTER_CRITICAL(mux)``, ``taskENTER_CRITICAL_ISR(mux)``,
|
|
|
- ``portENTER_CRITICAL(mux)``, ``portENTER_CRITICAL_ISR(mux)`` are all macro
|
|
|
- defined to call internal function :cpp:func:`vPortEnterCritical`
|
|
|
-
|
|
|
- - ``taskEXIT_CRITICAL(mux)``, ``taskEXIT_CRITICAL_ISR(mux)``,
|
|
|
- ``portEXIT_CRITICAL(mux)``, ``portEXIT_CRITICAL_ISR(mux)`` are all macro
|
|
|
- defined to call internal function :cpp:func:`vPortExitCritical`
|
|
|
-
|
|
|
- - ``portENTER_CRITICAL_SAFE(mux)``, ``portEXIT_CRITICAL_SAFE(mux)`` macro identifies
|
|
|
- the context of execution, i.e ISR or Non-ISR, and calls appropriate critical
|
|
|
- section functions (``port*_CRITICAL`` in Non-ISR and ``port*_CRITICAL_ISR`` in ISR)
|
|
|
- in order to be in compliance with Vanilla FreeRTOS.
|
|
|
-
|
|
|
-For more details see :component_file:`esp_hw_support/include/soc/spinlock.h`,
|
|
|
-:component_file:`freertos/FreeRTOS-Kernel/include/freertos/task.h`,
|
|
|
-and :component_file:`freertos/FreeRTOS-Kernel/tasks.c`
|
|
|
-
|
|
|
-It should be noted that when modifying vanilla FreeRTOS code to be ESP-IDF
|
|
|
-FreeRTOS compatible, it is trivial to modify the type of critical section called
|
|
|
-as they are all defined to call the same function. As long as the same spinlock
|
|
|
-is provided upon entering and exiting, the exact macro or function used for the
|
|
|
-call should not matter.
|
|
|
-
|
|
|
-
|
|
|
-.. only:: not CONFIG_FREERTOS_UNICORE
|
|
|
-
|
|
|
- .. _floating-points:
|
|
|
-
|
|
|
- Floating Point Arithmetic
|
|
|
- -------------------------
|
|
|
-
|
|
|
- ESP-IDF FreeRTOS implements Lazy Context Switching for FPUs. In other words,
|
|
|
- the state of a core's FPU registers are not immediately saved when a context
|
|
|
- switch occurs. Therefore, tasks that utilize ``float`` must be pinned to a
|
|
|
- particular core upon creation. If not, ESP-IDF FreeRTOS will automatically pin
|
|
|
- the task in question to whichever core the task was running on upon the task's
|
|
|
- first use of ``float``. Likewise due to Lazy Context Switching, only interrupt
|
|
|
- service routines of lowest priority (that is it the Level 1) can use ``float``,
|
|
|
- higher priority interrupts do not support FPU usage.
|
|
|
-
|
|
|
- ESP32 does not support hardware acceleration for double precision floating point
|
|
|
- arithmetic (``double``). Instead ``double`` is implemented via software hence the
|
|
|
- behavioral restrictions with regards to ``float`` do not apply to ``double``. Note
|
|
|
- that due to the lack of hardware acceleration, ``double`` operations may consume
|
|
|
- significantly larger amount of CPU time in comparison to ``float``.
|
|
|
-
|
|
|
-.. _task-deletion:
|
|
|
-
|
|
|
-Task Deletion
|
|
|
--------------
|
|
|
+Vanilla FreeRTOS allows interrupts to be disabled and enabled by calling :c:macro:`taskDISABLE_INTERRUPTS` and :c:macro:`taskENABLE_INTERRUPTS` respectively.
|
|
|
|
|
|
-In FreeRTOS task deletion the freeing of task memory will occur
|
|
|
-immediately (within :cpp:func:`vTaskDelete`) if the task being deleted is not currently
|
|
|
-running or is not pinned to the other core (with respect to the core
|
|
|
-:cpp:func:`vTaskDelete` is called on). TLSP deletion callbacks will also run immediately
|
|
|
-if the same conditions are met.
|
|
|
+ESP-IDF FreeRTOS provides the same API, however interrupts will only disabled or enabled on the current core.
|
|
|
|
|
|
-However, calling :cpp:func:`vTaskDelete` to delete a task that is either currently
|
|
|
-running or pinned to the other core will still result in the freeing of memory
|
|
|
-being delegated to the Idle Task.
|
|
|
+.. warning::
|
|
|
+ Disabling interrupts is a valid method of achieve 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 Critical Sections for more details.
|
|
|
|
|
|
+Startup and Termination
|
|
|
+^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
-.. _deletion-callbacks:
|
|
|
+ESP-IDF FreeRTOS **does not** require users to call :cpp:func:`vTaskStartScheduler` to start the scheduler. The startup flow of an ESP-IDF application will already call this automatically. The entry point for user code is a user defined ``void app_main(void)`` function. For more details regarding the startup of ESP-IDF FreeRTOS applications, see :ref:`freertos-applications`.
|
|
|
|
|
|
-Thread Local Storage Pointers & Deletion Callbacks
|
|
|
---------------------------------------------------
|
|
|
+ESP-IDF FreeRTOS **does not** support scheduler termination. Calling :cpp:func:`vTaskEndScheduler` will simply cause the application to abort.
|
|
|
|
|
|
-Thread Local Storage Pointers (TLSP) are pointers stored directly in the TCB.
|
|
|
-TLSP allow each task to have its own unique set of pointers to data structures.
|
|
|
-However task deletion behavior in vanilla FreeRTOS does not automatically
|
|
|
-free the memory pointed to by TLSP. Therefore if the memory pointed to by
|
|
|
-TLSP is not explicitly freed by the user before task deletion, memory leak will
|
|
|
-occur.
|
|
|
|
|
|
-ESP-IDF FreeRTOS provides the added feature of Deletion Callbacks. Deletion
|
|
|
-Callbacks are called automatically during task deletion to free memory pointed
|
|
|
-to by TLSP. Each TLSP can have its own Deletion Callback. Note that due to the
|
|
|
-to `Task Deletion`_ behavior, there can be instances where Deletion
|
|
|
-Callbacks are called in the context of the Idle Tasks. Therefore Deletion
|
|
|
-Callbacks **should never attempt to block** and critical sections should be kept
|
|
|
-as short as possible to minimize priority inversion.
|
|
|
+.. ------------------------------------------------ Critical Sections --------------------------------------------------
|
|
|
|
|
|
-Deletion callbacks are of type
|
|
|
-``void (*TlsDeleteCallbackFunction_t)( int, void * )`` where the first parameter
|
|
|
-is the index number of the associated TLSP, and the second parameter is the
|
|
|
-TLSP itself.
|
|
|
+Critical Sections
|
|
|
+-----------------
|
|
|
|
|
|
-Deletion callbacks are set alongside TLSP by calling
|
|
|
-:cpp:func:`vTaskSetThreadLocalStoragePointerAndDelCallback`. Calling the vanilla
|
|
|
-FreeRTOS function :cpp:func:`vTaskSetThreadLocalStoragePointer` will simply set the
|
|
|
-TLSP's associated Deletion Callback to `NULL` meaning that no callback will be
|
|
|
-called for that TLSP during task deletion. If a deletion callback is `NULL`,
|
|
|
-users should manually free the memory pointed to by the associated TLSP before
|
|
|
-task deletion in order to avoid memory leak.
|
|
|
+API Changes
|
|
|
+^^^^^^^^^^^
|
|
|
|
|
|
-For more details see :doc:`FreeRTOS API reference<../api-reference/system/freertos>`.
|
|
|
+Vanilla FreeRTOS implements critical sections by disabling interrupts, This prevents preemptive context switches and the servicing of ISRs during a critical section. Thus a task/ISR that enters a critical section is guaranteed to be the sole entity to access a shared resource. Critical sections in Vanilla FreeRTOS have the following API:
|
|
|
|
|
|
+- ``taskENTER_CRITICAL()`` enters a critical section by disabling interrupts
|
|
|
+- ``taskEXIT_CRITICAL()`` exits a critical section by reenabling interrupts
|
|
|
+- ``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
|
|
|
|
|
|
-.. _esp-idf-freertos-configuration:
|
|
|
+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:
|
|
|
|
|
|
-Configuring ESP-IDF FreeRTOS
|
|
|
-----------------------------
|
|
|
+- Spinlocks are of ``portMUX_TYPE`` (**not to be confused to FreeRTOS mutexes**)
|
|
|
+- ``taskENTER_CRITICAL(&mux)`` enters a critical from a task context
|
|
|
+- ``taskEXIT_CRITICAL(&mux)`` exits a critical section from a task context
|
|
|
+- ``taskENTER_CRITICAL_ISR(&mux)`` enters a critical section from an interrupt context
|
|
|
+- ``taskEXIT_CRITICAL_ISR(&mux)`` exits a critical section from an interrupt context
|
|
|
|
|
|
-The ESP-IDF FreeRTOS can be configured in the project configuration menu
|
|
|
-(``idf.py menuconfig``) under ``Component Config/FreeRTOS``. The following section
|
|
|
-highlights some of the ESP-IDF FreeRTOS configuration options. For a full list of
|
|
|
-ESP-IDF FreeRTOS configurations, see :doc:`FreeRTOS <../api-reference/kconfig>`
|
|
|
+.. 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 dead locking when entering critical sections recursively.
|
|
|
|
|
|
-.. only:: not CONFIG_FREERTOS_UNICORE
|
|
|
+Implementation
|
|
|
+^^^^^^^^^^^^^^
|
|
|
|
|
|
- :ref:`CONFIG_FREERTOS_UNICORE` will run ESP-IDF FreeRTOS only
|
|
|
- on **PRO_CPU**. Note that this is **not equivalent to running vanilla
|
|
|
- FreeRTOS**. Note that this option may affect behavior of components other than
|
|
|
- :component:`freertos`. For more details regarding the
|
|
|
- effects of running ESP-IDF FreeRTOS on a single core, search for
|
|
|
- occurences of ``CONFIG_FREERTOS_UNICORE`` in the ESP-IDF components.
|
|
|
+In ESP-IDF FreeRTOS, the process of a particular core entering and exiting a critical section is as follows:
|
|
|
|
|
|
-.. only:: CONFIG_FREERTOS_UNICORE
|
|
|
+- For ``taskENTER_CRITICAL(&mux)`` (or ``taskENTER_CRITICAL_ISR(&mux)``)
|
|
|
+
|
|
|
+ #. The core disables its interrupts (or interrupt nesting) up to ``configMAX_SYSCALL_INTERRUPT_PRIORITY``
|
|
|
+ #. The core then spins on the spinlock using an atomic compare-and-set instruction until it acquires the lock. A lock is acquired when the core is able to set the lock's owner value to the core's ID.
|
|
|
+ #. Once the spinlock is acquired, the function returns. The remainder of the critical section runs with interrupts (or interrupt nesting) disabled.
|
|
|
+
|
|
|
+- For ``taskEXIT_CRITICAL(&mux)`` (or ``taskEXIT_CRITICAL_ISR(&mux)``)
|
|
|
+
|
|
|
+ #. The core releases the spinlock by clearing the spinlock's owner value
|
|
|
+ #. The core re-enables interrupts (or interrupt nesting)
|
|
|
+
|
|
|
+Restrictions and Considerations
|
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
+
|
|
|
+Given that interrupts (or interrupt nesting) are disabled during a critical section, there are multiple restrictions regarding what can be done within a critical sections. During a critical section, users should keep the following restrictions and considerations in mind:
|
|
|
+
|
|
|
+- Critical sections should be as kept as short as possible
|
|
|
+
|
|
|
+ - The longer the critical section lasts, the longer a pending interrupt can be delayed.
|
|
|
+ - A typical critical section should only access a few data structures and/or hardware registers
|
|
|
+ - If possible, defer as much processing and/or event handling to the outside of critical sections.
|
|
|
+
|
|
|
+- FreeRTOS API should not be called from within a critical section
|
|
|
+- Users should never call any blocking or yielding functions within a critical section
|
|
|
+
|
|
|
+
|
|
|
+.. ------------------------------------------------------ Misc ---------------------------------------------------------
|
|
|
+
|
|
|
+Misc
|
|
|
+----
|
|
|
+
|
|
|
+Floating Point Usage
|
|
|
+^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
- As {IDF_TARGET_NAME} is a single core SoC, the config item :ref:`CONFIG_FREERTOS_UNICORE` is
|
|
|
- always set. This means ESP-IDF only runs on the single CPU. Note that this is **not
|
|
|
- equivalent to running vanilla FreeRTOS**. Behaviors of multiple components in ESP-IDF
|
|
|
- will be modified. For more details regarding the effects of running ESP-IDF FreeRTOS
|
|
|
- on a single core, search for occurences of ``CONFIG_FREERTOS_UNICORE`` in the ESP-IDF components.
|
|
|
+Usually, when a context switch occurs:
|
|
|
+
|
|
|
+- the current state of a CPU's registers are saved to the stack of task being switch out
|
|
|
+- the previously saved state of the CPU's registers are loaded from the stack of the task being switched in
|
|
|
+
|
|
|
+However, ESP-IDF FreeRTOS implements Lazy Context Switching for the FPU (Floating Point Unit) 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 are not immediately saved to the stack of the task getting switched out (e.g., Task A). The FPU's registers are left untouched until:
|
|
|
+
|
|
|
+- A different task (e.g., Task B) runs on the same core and uses the FPU. This will trigger an exception that will save the FPU registers to Task A's stack.
|
|
|
+- Task A get's scheduled to the same core and continues execution. Saving and restoring the FPU's registers is not necessary in this case.
|
|
|
+
|
|
|
+However, given that tasks can be unpinned thus can be scheduled on different cores (e.g., Task A switches to CPU1), it is unfeasible to copy and restore the FPU's registers across cores. Therefore, when a task utilizes the 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 uses the FPU are always pinned to a particular core.
|
|
|
+
|
|
|
+Furthermore, ESP-IDF FreeRTOS by default does not support the usage of the FPU within an interrupt context given that the FPU's register state is tied to a particular task.
|
|
|
+
|
|
|
+.. only: esp32
|
|
|
+
|
|
|
+ .. 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.
|
|
|
+
|
|
|
+.. 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``.
|
|
|
+
|
|
|
+
|
|
|
+.. -------------------------------------------------- Single Core -----------------------------------------------------
|
|
|
+
|
|
|
+.. _freertos-smp-single-core:
|
|
|
+
|
|
|
+ESP-IDF FreeRTOS Single Core
|
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
-:ref:`CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION` will trigger a halt in
|
|
|
-particular functions in ESP-IDF FreeRTOS which have not been fully tested
|
|
|
-in an SMP context.
|
|
|
+Although ESP-IDF FreeRTOS is an SMP scheduler, some ESP targets are single core (such as the 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).
|
|
|
|
|
|
-:ref:`CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER` will enclose all task functions
|
|
|
-within a wrapper function. In the case that a task function mistakenly returns
|
|
|
-(i.e. does not call :cpp:func:`vTaskDelete`), the call flow will return to the
|
|
|
-wrapper function. The wrapper function will then log an error and abort the
|
|
|
-application, as illustrated below::
|
|
|
+For multicore targets (such as the ESP32 and ESP32-S3), :ref:`CONFIG_FREERTOS_UNICORE` can also be set. This will result in ESP-IDF FreeRTOS only running on CPU0, and all other cores will be inactive.
|
|
|
|
|
|
- E (25) FreeRTOS: FreeRTOS task should not return. Aborting now!
|
|
|
- abort() was called at PC 0x40085c53 on core 0
|
|
|
+.. note::
|
|
|
+ Users should bear in mind that enabling :ref:`CONFIG_FREERTOS_UNICORE` **is NOT equivalent to running Vanilla FreeRTOS**. The additional API of ESP-IDF FreeRTOS can still be called, and the behavior changes of ESP-IDF FreeRTOS will incur a small amount of overhead even when compiled for only a single core.
|