diff --git a/components/freertos/tasks.c b/components/freertos/tasks.c index ff549183a..ff5c46bb4 100644 --- a/components/freertos/tasks.c +++ b/components/freertos/tasks.c @@ -541,6 +541,12 @@ static portTASK_FUNCTION_PROTO( prvIdleTask, pvParameters ); #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 @@ -1201,19 +1207,25 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode /*-----------------------------------------------------------*/ #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 ); @@ -1233,29 +1245,67 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode mtCOVERAGE_TEST_MARKER(); } - 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. */ + /* 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++; + //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; + + /* 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, @@ -1265,20 +1315,14 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode 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(); } } } @@ -3583,52 +3627,48 @@ static void prvCheckTasksWaitingTermination( void ) #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 } } } @@ -3831,7 +3871,6 @@ BaseType_t xTaskGetAffinity( TaskHandle_t xTask ) #if ( INCLUDE_vTaskDelete == 1 ) - static void prvDeleteTCB( TCB_t *pxTCB ) { /* Free up the memory allocated by the scheduler for the task. It is up @@ -3886,6 +3925,23 @@ BaseType_t xTaskGetAffinity( TaskHandle_t xTask ) #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; diff --git a/components/freertos/test/test_freertos_task_delete.c b/components/freertos/test/test_freertos_task_delete.c index 68a6683fc..72f5cc85a 100644 --- a/components/freertos/test/test_freertos_task_delete.c +++ b/components/freertos/test/test_freertos_task_delete.c @@ -1,26 +1,66 @@ +/* + * 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 #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)); + } diff --git a/docs/api-guides/freertos-smp.rst b/docs/api-guides/freertos-smp.rst index 6d0c29bc4..16d4c139b 100644 --- a/docs/api-guides/freertos-smp.rst +++ b/docs/api-guides/freertos-smp.rst @@ -50,12 +50,25 @@ 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. -: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`: Vanilla FreeRTOS Hooks were not designed for SMP. ESP-IDF provides its own Idle and Tick Hooks in addition to the Vanilla FreeRTOS @@ -375,39 +388,83 @@ mutex is provided upon entering and exiting, the type of call should not 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. +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. 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. +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. -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. +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. -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. +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` diff --git a/tools/unit-test-app/main/app_main.c b/tools/unit-test-app/main/app_main.c index b3d249fc5..a7a7e8754 100644 --- a/tools/unit-test-app/main/app_main.c +++ b/tools/unit-test-app/main/app_main.c @@ -6,7 +6,7 @@ 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 */ }