#endif
+//Function to call the Thread Local Storage Pointer Deletion Callbacks. Will be
+//called during task deletion before prvDeleteTCB is called.
+#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) && ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS )
+ static void prvDeleteTLS( TCB_t *pxTCB );
+#endif
+
/*
* Used only by the idle task. This checks to see if anything has been placed
* in the list of tasks waiting to be deleted. If so the task is cleaned up
/*-----------------------------------------------------------*/
#if ( INCLUDE_vTaskDelete == 1 )
+
void vTaskDelete( TaskHandle_t xTaskToDelete )
{
+ //The following vTaskDelete() is backported from FreeRTOS v9.0.0 and modified for SMP.
+ //v9.0.0 vTaskDelete() will immediately free task memory if the task being deleted is
+ //NOT currently running and not pinned to the other core. Otherwise, freeing of task memory
+ //will still be delegated to the Idle Task.
+
TCB_t *pxTCB;
+ int core = xPortGetCoreID(); //Current core
+ UBaseType_t free_now; //Flag to indicate if task memory can be freed immediately
+
taskENTER_CRITICAL(&xTaskQueueMutex);
{
/* If null is passed in here then it is the calling task that is
being deleted. */
pxTCB = prvGetTCBFromHandle( xTaskToDelete );
- /* Remove task from the ready list and place in the termination list.
- This will stop the task from be scheduled. The idle task will check
- the termination list and free up any memory allocated by the
- scheduler for the TCB and stack. */
+ /* Remove task from the ready list. */
if( uxListRemove( &( pxTCB->xGenericListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
mtCOVERAGE_TEST_MARKER();
}
- vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xGenericListItem ) );
+ /* Increment the uxTaskNumber also so kernel aware debuggers can
+ detect that the task lists need re-generating. This is done before
+ portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will
+ not return. */
+ uxTaskNumber++;
- /* Increment the ucTasksDeleted variable so the idle task knows
- there is a task that has been deleted and that it should therefore
- check the xTasksWaitingTermination list. */
- ++uxTasksDeleted;
+ //If task to be deleted is currently running on either core or is pinned to the other core. Let Idle free memory
+ if( pxTCB == pxCurrentTCB[ core ] ||
+ (portNUM_PROCESSORS > 1 && pxTCB == pxCurrentTCB[ !core ]) ||
+ (portNUM_PROCESSORS > 1 && pxTCB->xCoreID == (!core)) )
+ {
+ /* Deleting a currently running task. This cannot complete
+ within the task itself, as a context switch to another task is
+ required. Place the task in the termination list. The idle task
+ will check the termination list and free up any memory allocated
+ by the scheduler for the TCB and stack of the deleted task. */
+ vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xGenericListItem ) );
+
+ /* Increment the ucTasksDeleted variable so the idle task knows
+ there is a task that has been deleted and that it should therefore
+ check the xTasksWaitingTermination list. */
+ ++uxTasksDeleted;
- /* Increment the uxTaskNumberVariable also so kernel aware debuggers
- can detect that the task lists need re-generating. */
- uxTaskNumber++;
+ /* The pre-delete hook is primarily for the Windows simulator,
+ in which Windows specific clean up operations are performed,
+ after which it is not possible to yield away from this task -
+ hence xYieldPending is used to latch that a context switch is
+ required. */
+ portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
+
+ free_now = pdFALSE; //Let Idle Task free task memory
+ }
+ else //Task is not currently running and not pinned to the other core
+ {
+ --uxCurrentNumberOfTasks;
+
+ /* Reset the next expected unblock time in case it referred to
+ the task that has just been deleted. */
+ prvResetNextTaskUnblockTime();
+ free_now = pdTRUE; //Set flag to free task memory immediately
+ }
traceTASK_DELETE( pxTCB );
}
taskEXIT_CRITICAL(&xTaskQueueMutex);
+ if(free_now == pdTRUE){ //Free task memory. Outside critical section due to deletion callbacks
+ #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) && ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS )
+ prvDeleteTLS( pxTCB ); //Run deletion callbacks before deleting TCB
+ #endif
+ prvDeleteTCB( pxTCB ); //Must only be called after del cb
+ }
+
/* Force a reschedule if it is the currently running task that has just
been deleted. */
if( xSchedulerRunning != pdFALSE )
{
//No mux; no harm done if this misfires. The deleted task won't get scheduled anyway.
- if( pxTCB == pxCurrentTCB[ xPortGetCoreID() ] )
+ if( pxTCB == pxCurrentTCB[ core ] ) //If task was currently running on this core
{
- configASSERT( uxSchedulerSuspended[ xPortGetCoreID() ] == 0 );
+ configASSERT( uxSchedulerSuspended[ core ] == 0 );
/* The pre-delete hook is primarily for the Windows simulator,
in which Windows specific clean up operations are performed,
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending[xPortGetCoreID()] );
portYIELD_WITHIN_API();
}
- else if ( portNUM_PROCESSORS > 1 && pxTCB == pxCurrentTCB[ !xPortGetCoreID() ] )
+ else if ( portNUM_PROCESSORS > 1 && pxTCB == pxCurrentTCB[ !core] ) //If task was currently running on the other core
{
/* if task is running on the other CPU, force a yield on that CPU to take it off */
- vPortYieldOtherCore( !xPortGetCoreID() );
+ vPortYieldOtherCore( !core );
}
else
{
- /* Reset the next expected unblock time in case it referred to
- the task that has just been deleted. */
- taskENTER_CRITICAL(&xTaskQueueMutex);
- {
- prvResetNextTaskUnblockTime();
- }
- taskEXIT_CRITICAL(&xTaskQueueMutex);
+ mtCOVERAGE_TEST_MARKER();
}
}
}
#if ( INCLUDE_vTaskDelete == 1 )
{
BaseType_t xListIsEmpty;
+ int core = xPortGetCoreID();
/* ucTasksDeleted is used to prevent vTaskSuspendAll() being called
too often in the idle task. */
while(uxTasksDeleted > ( UBaseType_t ) 0U )
{
TCB_t *pxTCB = NULL;
+
taskENTER_CRITICAL(&xTaskQueueMutex);
{
xListIsEmpty = listLIST_IS_EMPTY( &xTasksWaitingTermination );
- }
-
- if( xListIsEmpty == pdFALSE )
- {
+ if( xListIsEmpty == pdFALSE )
{
- pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) );
/* We only want to kill tasks that ran on this core because e.g. _xt_coproc_release needs to
- be called on the core the process is pinned on, if any */
- if( pxTCB->xCoreID == tskNO_AFFINITY || pxTCB->xCoreID == xPortGetCoreID()) {
- ( void ) uxListRemove( &( pxTCB->xGenericListItem ) );
+ be called on the core the process is pinned on, if any */
+ ListItem_t *target = listGET_HEAD_ENTRY(&xTasksWaitingTermination);
+ for( ; target != listGET_END_MARKER(&xTasksWaitingTermination); target = listGET_NEXT(target) ){
+ int coreid = (( TCB_t * )listGET_LIST_ITEM_OWNER(target))->xCoreID;
+ if(coreid == core || coreid == tskNO_AFFINITY){ //Find first item not pinned to other core
+ pxTCB = ( TCB_t * )listGET_LIST_ITEM_OWNER(target);
+ break;
+ }
+ }
+ if(pxTCB != NULL){
+ ( void ) uxListRemove( target ); //Remove list item from list
--uxCurrentNumberOfTasks;
--uxTasksDeleted;
- } else {
- /* Need to wait until the idle task on the other processor kills that task first. */
- taskEXIT_CRITICAL(&xTaskQueueMutex);
- break;
}
}
}
- taskEXIT_CRITICAL(&xTaskQueueMutex);
+ taskEXIT_CRITICAL(&xTaskQueueMutex); //Need to call deletion callbacks outside critical section
- if (pxTCB != NULL) {
- #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) && ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS )
- int x;
- for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
- {
- if (pxTCB->pvThreadLocalStoragePointersDelCallback[ x ] != NULL)
- {
- pxTCB->pvThreadLocalStoragePointersDelCallback[ x ](x, pxTCB->pvThreadLocalStoragePointers[ x ]);
- }
- }
- #endif
- prvDeleteTCB( pxTCB );
+ if (pxTCB != NULL) { //Call deletion callbacks and free TCB memory
+ #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) && ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS )
+ prvDeleteTLS( pxTCB );
+ #endif
+ prvDeleteTCB( pxTCB );
}
else
{
mtCOVERAGE_TEST_MARKER();
+ break; //No TCB found that could be freed by this core, break out of loop
}
}
}
#if ( INCLUDE_vTaskDelete == 1 )
-
static void prvDeleteTCB( TCB_t *pxTCB )
{
/* Free up the memory allocated by the scheduler for the task. It is up
#endif /* INCLUDE_vTaskDelete */
/*-----------------------------------------------------------*/
+#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) && ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS )
+
+ static void prvDeleteTLS( TCB_t *pxTCB )
+ {
+ configASSERT( pxTCB );
+ for( int x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
+ {
+ if (pxTCB->pvThreadLocalStoragePointersDelCallback[ x ] != NULL) //If del cb is set
+ {
+ pxTCB->pvThreadLocalStoragePointersDelCallback[ x ](x, pxTCB->pvThreadLocalStoragePointers[ x ]); //Call del cb
+ }
+ }
+ }
+
+#endif /* ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) && ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS ) */
+/*-----------------------------------------------------------*/
+
static void prvResetNextTaskUnblockTime( void )
{
TCB_t *pxTCB;
+/*
+ * Test backported deletion behavior by creating tasks of various affinities and
+ * check if the task memory is freed immediately under the correct conditions.
+ *
+ * The behavior of vTaskDelete() has been backported form FreeRTOS v9.0.0. This
+ * results in the immediate freeing of task memory and the immediate execution
+ * of deletion callbacks under the following conditions...
+ * - When deleting a task that is not currently running on either core
+ * - When deleting a task that is pinned to the same core (with respect to
+ * the core that calls vTaskDelete()
+ *
+ * If the two conditions are not met, freeing of task memory and execution of
+ * deletion callbacks will still be carried out by the Idle Task.
+ */
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
-#include "freertos/semphr.h"
-#include "freertos/queue.h"
-#include "freertos/event_groups.h"
+#include "esp_heap_caps.h"
+
#include "unity.h"
-static void task_delete_self(void *param)
+#define NO_OF_TSKS 3
+#define DELAY_TICKS 2
+#define HEAP_CAPS (MALLOC_CAP_INTERNAL|MALLOC_CAP_DEFAULT)
+
+
+static void tsk_self_del(void *param)
{
- printf("Task %p running on core %d. Deleting shortly...\n", xTaskGetCurrentTaskHandle(), xPortGetCoreID());
- vTaskDelay(5);
- vTaskDelete(NULL);
+ vTaskDelete(NULL); //Deleting self means deleting currently running task
+}
+
+static void tsk_extern_del(void *param)
+{
+ vTaskDelay(portMAX_DELAY); //Await external deletion
}
TEST_CASE("FreeRTOS Delete Tasks", "[freertos]")
{
+/* -------------- Test vTaskDelete() on currently running tasks ----------------*/
uint32_t before_count = uxTaskGetNumberOfTasks();
-
- xTaskCreatePinnedToCore(task_delete_self, "tsk_self_a", 4096, NULL, configMAX_PRIORITIES - 1, NULL, 0);
- xTaskCreatePinnedToCore(task_delete_self, "tsk_self_a", 4096, NULL, configMAX_PRIORITIES - 1, NULL, 0);
- TEST_ASSERT_EQUAL(before_count + 2, uxTaskGetNumberOfTasks());
- vTaskDelay(200 / portTICK_PERIOD_MS);
+ uint32_t before_heap = heap_caps_get_free_size(HEAP_CAPS);
+ for(int i = 0; i < portNUM_PROCESSORS; i++){
+ for(int j = 0; j < NO_OF_TSKS; j++){
+ TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(tsk_self_del, "tsk_self", 1024, NULL, configMAX_PRIORITIES - 1, NULL, i));
+ }
+ }
+ vTaskDelay(DELAY_TICKS); //Minimal delay to see if Idle task cleans up all tasks awaiting deletion in a single tick
TEST_ASSERT_EQUAL(before_count, uxTaskGetNumberOfTasks());
+ TEST_ASSERT_EQUAL(before_heap, heap_caps_get_free_size(HEAP_CAPS));
+
+/* ------------- Test vTaskDelete() on not currently running tasks ------------ */
+ TaskHandle_t handles[NO_OF_TSKS];
+ before_heap = heap_caps_get_free_size(HEAP_CAPS);
+ //Create task pinned to the same core that will not run during task deletion
+ for(int j = 0 ; j < NO_OF_TSKS; j++){
+ TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(tsk_extern_del, "tsk_extern", 4096, NULL, configMAX_PRIORITIES - 1, &handles[j], xPortGetCoreID()));
+ }
+ TEST_ASSERT_NOT_EQUAL(before_heap, heap_caps_get_free_size(HEAP_CAPS)); //Check tasks have been created
+ //Delete the tasks, memory should be freed immediately
+ for(int j = 0; j < NO_OF_TSKS; j++){
+ vTaskDelete(handles[j]);
+ }
+ TEST_ASSERT_EQUAL(before_heap, heap_caps_get_free_size(HEAP_CAPS));
+
}
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.
-:ref:`deletion-callbacks`: ESP-IDF FreeRTOS has
-backported the Thread Local Storage Pointers feature. However they have the
-extra feature of deletion callbacks. Deletion callbacks are used to
-automatically free memory used by Thread Local Storage Pointers during the task
-deletion. Call ``vTaskSetThreadLocalStoragePointerAndDelCallback()``
-to set Thread Local Storage Pointers and deletion callbacks.
+: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:`task-deletion`: Task deletion behavior has been backported from FreeRTOS
+v9.0.0 and modified to be SMP compatible. Task memory will be freed immediately
+when `vTaskDelete()` is called to delete a task that is not currently running
+and not pinned to the other core. Otherwise, freeing of task memory will still
+be delegated to the Idle Task.
+
+:ref:`deletion-callbacks`: ESP-IDF FreeRTOS has backported the Thread Local
+Storage Pointers (TLSP) feature. However the extra feature of Deletion Callbacks has been
+added. Deletion callbacks are called automatically during task deletion and are
+used to free memory pointed to by TLSP. Call
+``vTaskSetThreadLocalStoragePointerAndDelCallback()`` to set TLSP and Deletion
+Callbacks.
:ref:`FreeRTOS Hooks<hooks_api_reference>`: Vanilla FreeRTOS Hooks were not designed for SMP.
ESP-IDF provides its own Idle and Tick Hooks in addition to the Vanilla FreeRTOS
matter.
+.. _floating-points:
+
+Floating Point Aritmetic
+------------------------
+
+The ESP32 supports hardware acceleration of single precision floating point
+arithmetic (`float`) via Floating Point Units (FPU, also known as coprocessors)
+attached to each core. The use of the FPUs imposes some behavioral restrictions
+on ESP-IDF FreeRTOS.
+
+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, interrupt service
+routines must also not use `float`.
+
+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
+-------------
+
+FreeRTOS task deletion prior to v9.0.0 delegated the freeing of task memory
+entirely to the Idle Task. Currently, the freeing of task memory will occur
+immediately (within `vTaskDelete()`) if the task being deleted is not currently
+running or is not pinned to the other core (with respect to the core
+`vTaskDelete()` is called on). TLSP deletion callbacks will also run immediately
+if the same conditions are met.
+
+However, calling `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.
+
+
.. _deletion-callbacks:
Thread Local Storage Pointers & Deletion Callbacks
--------------------------------------------------
-Thread Local Storage Pointers are pointers stored directly in the TCB which
-allows each task to have a pointer to a data structure containing that is
-specific to that task. However vanilla FreeRTOS provides no functionality to
-free the memory pointed to by the Thread Local Storage Pointers. Therefore if
-the memory pointed to by the Thread Local Storage Pointers is not explicitly
-freed by the user before a task is deleted, memory leak will occur.
-
-ESP-IDF FreeRTOS provides the added feature of deletion callbacks. These
-deletion callbacks are used to automatically free the memory pointed to by the
-Thread Local Storage Pointers when a task is deleted. Each Thread Local Storage
-Pointer can have its own call back, and these call backs are called when the
-Idle tasks cleans up a deleted tasks.
-
-Vanilla FreeRTOS sets a Thread Local Storage Pointers using
-``vTaskSetThreadLocalStoragePointer()`` whereas ESP-IDF FreeRTOS sets a Thread
-Local Storage Pointers and Deletion Callbacks using
-``vTaskSetThreadLocalStoragePointerAndDelCallback()`` which accepts a pointer
-to the deletion call back as an extra parameter of type
-```TlsDeleteCallbackFunction_t``. Calling the vanilla FreeRTOS API
-``vTaskSetThreadLocalStoragePointer()`` is still valid however it is internally
-defined to call ``vTaskSetThreadLocalStoragePointerAndDelCallback()`` with a
-``NULL`` pointer as the deletion call back. This results in the selected Thread
-Local Storage Pointer to have no deletion call back.
-
-In IDF the FreeRTOS thread local storage at index 0 is reserved and is used to implement
-the pthreads API thread local storage (pthread_getspecific() & pthread_setspecific()).
-Other indexes can be used for any purpose, provided
-:ref:`CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS` is set to a high enough value.
+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 :ref:`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.
+
+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.
+
+Deletion callbacks are set alongside TLSP by calling
+``vTaskSetThreadLocalStoragePointerAndDelCallback()``. Calling the vanilla
+FreeRTOS function ``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.
+
+:ref:`CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS` in menuconfig can be used
+to configure the number TLSP and Deletion Callbacks a TCB will have.
For more details see :component_file:`freertos/include/freertos/task.h`
void unityTask(void *pvParameters)
{
- vTaskDelay(30); /* Delay a bit to let the main task be deleted */
+ vTaskDelay(2); /* Delay a bit to let the main task be deleted */
unity_run_menu(); /* Doesn't return */
}