Merge branch 'bugfix/freertos_check_task_waiting_termination' into 'master'
Freertos Task Deletion backport and FPU documentation See merge request !1591
This commit is contained in:
commit
2b92119840
|
@ -541,6 +541,12 @@ static portTASK_FUNCTION_PROTO( prvIdleTask, pvParameters );
|
||||||
|
|
||||||
#endif
|
#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
|
* 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
|
* 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 )
|
#if ( INCLUDE_vTaskDelete == 1 )
|
||||||
|
|
||||||
void vTaskDelete( TaskHandle_t xTaskToDelete )
|
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;
|
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);
|
taskENTER_CRITICAL(&xTaskQueueMutex);
|
||||||
{
|
{
|
||||||
/* If null is passed in here then it is the calling task that is
|
/* If null is passed in here then it is the calling task that is
|
||||||
being deleted. */
|
being deleted. */
|
||||||
pxTCB = prvGetTCBFromHandle( xTaskToDelete );
|
pxTCB = prvGetTCBFromHandle( xTaskToDelete );
|
||||||
|
|
||||||
/* Remove task from the ready list and place in the termination list.
|
/* Remove task from the ready 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. */
|
|
||||||
if( uxListRemove( &( pxTCB->xGenericListItem ) ) == ( UBaseType_t ) 0 )
|
if( uxListRemove( &( pxTCB->xGenericListItem ) ) == ( UBaseType_t ) 0 )
|
||||||
{
|
{
|
||||||
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
|
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
|
||||||
|
@ -1233,6 +1245,22 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode
|
||||||
mtCOVERAGE_TEST_MARKER();
|
mtCOVERAGE_TEST_MARKER();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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 ) );
|
vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xGenericListItem ) );
|
||||||
|
|
||||||
/* Increment the ucTasksDeleted variable so the idle task knows
|
/* Increment the ucTasksDeleted variable so the idle task knows
|
||||||
|
@ -1240,22 +1268,44 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode
|
||||||
check the xTasksWaitingTermination list. */
|
check the xTasksWaitingTermination list. */
|
||||||
++uxTasksDeleted;
|
++uxTasksDeleted;
|
||||||
|
|
||||||
/* Increment the uxTaskNumberVariable also so kernel aware debuggers
|
/* The pre-delete hook is primarily for the Windows simulator,
|
||||||
can detect that the task lists need re-generating. */
|
in which Windows specific clean up operations are performed,
|
||||||
uxTaskNumber++;
|
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 );
|
traceTASK_DELETE( pxTCB );
|
||||||
}
|
}
|
||||||
taskEXIT_CRITICAL(&xTaskQueueMutex);
|
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
|
/* Force a reschedule if it is the currently running task that has just
|
||||||
been deleted. */
|
been deleted. */
|
||||||
if( xSchedulerRunning != pdFALSE )
|
if( xSchedulerRunning != pdFALSE )
|
||||||
{
|
{
|
||||||
//No mux; no harm done if this misfires. The deleted task won't get scheduled anyway.
|
//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,
|
/* The pre-delete hook is primarily for the Windows simulator,
|
||||||
in which Windows specific clean up operations are performed,
|
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()] );
|
portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending[xPortGetCoreID()] );
|
||||||
portYIELD_WITHIN_API();
|
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 */
|
/* if task is running on the other CPU, force a yield on that CPU to take it off */
|
||||||
vPortYieldOtherCore( !xPortGetCoreID() );
|
vPortYieldOtherCore( !core );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Reset the next expected unblock time in case it referred to
|
mtCOVERAGE_TEST_MARKER();
|
||||||
the task that has just been deleted. */
|
|
||||||
taskENTER_CRITICAL(&xTaskQueueMutex);
|
|
||||||
{
|
|
||||||
prvResetNextTaskUnblockTime();
|
|
||||||
}
|
|
||||||
taskEXIT_CRITICAL(&xTaskQueueMutex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3583,52 +3627,48 @@ static void prvCheckTasksWaitingTermination( void )
|
||||||
#if ( INCLUDE_vTaskDelete == 1 )
|
#if ( INCLUDE_vTaskDelete == 1 )
|
||||||
{
|
{
|
||||||
BaseType_t xListIsEmpty;
|
BaseType_t xListIsEmpty;
|
||||||
|
int core = xPortGetCoreID();
|
||||||
|
|
||||||
/* ucTasksDeleted is used to prevent vTaskSuspendAll() being called
|
/* ucTasksDeleted is used to prevent vTaskSuspendAll() being called
|
||||||
too often in the idle task. */
|
too often in the idle task. */
|
||||||
while(uxTasksDeleted > ( UBaseType_t ) 0U )
|
while(uxTasksDeleted > ( UBaseType_t ) 0U )
|
||||||
{
|
{
|
||||||
TCB_t *pxTCB = NULL;
|
TCB_t *pxTCB = NULL;
|
||||||
|
|
||||||
taskENTER_CRITICAL(&xTaskQueueMutex);
|
taskENTER_CRITICAL(&xTaskQueueMutex);
|
||||||
{
|
{
|
||||||
xListIsEmpty = listLIST_IS_EMPTY( &xTasksWaitingTermination );
|
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
|
/* 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 */
|
be called on the core the process is pinned on, if any */
|
||||||
if( pxTCB->xCoreID == tskNO_AFFINITY || pxTCB->xCoreID == xPortGetCoreID()) {
|
ListItem_t *target = listGET_HEAD_ENTRY(&xTasksWaitingTermination);
|
||||||
( void ) uxListRemove( &( pxTCB->xGenericListItem ) );
|
for( ; target != listGET_END_MARKER(&xTasksWaitingTermination); target = listGET_NEXT(target) ){
|
||||||
--uxCurrentNumberOfTasks;
|
int coreid = (( TCB_t * )listGET_LIST_ITEM_OWNER(target))->xCoreID;
|
||||||
--uxTasksDeleted;
|
if(coreid == core || coreid == tskNO_AFFINITY){ //Find first item not pinned to other core
|
||||||
} else {
|
pxTCB = ( TCB_t * )listGET_LIST_ITEM_OWNER(target);
|
||||||
/* Need to wait until the idle task on the other processor kills that task first. */
|
|
||||||
taskEXIT_CRITICAL(&xTaskQueueMutex);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
taskEXIT_CRITICAL(&xTaskQueueMutex);
|
|
||||||
|
|
||||||
if(pxTCB != NULL){
|
if(pxTCB != NULL){
|
||||||
|
( void ) uxListRemove( target ); //Remove list item from list
|
||||||
|
--uxCurrentNumberOfTasks;
|
||||||
|
--uxTasksDeleted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
taskEXIT_CRITICAL(&xTaskQueueMutex); //Need to call deletion callbacks outside critical section
|
||||||
|
|
||||||
|
if (pxTCB != NULL) { //Call deletion callbacks and free TCB memory
|
||||||
#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) && ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS )
|
#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) && ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS )
|
||||||
int x;
|
prvDeleteTLS( pxTCB );
|
||||||
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
|
#endif
|
||||||
prvDeleteTCB( pxTCB );
|
prvDeleteTCB( pxTCB );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mtCOVERAGE_TEST_MARKER();
|
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 )
|
#if ( INCLUDE_vTaskDelete == 1 )
|
||||||
|
|
||||||
|
|
||||||
static void prvDeleteTCB( TCB_t *pxTCB )
|
static void prvDeleteTCB( TCB_t *pxTCB )
|
||||||
{
|
{
|
||||||
/* Free up the memory allocated by the scheduler for the task. It is up
|
/* 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 */
|
#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 )
|
static void prvResetNextTaskUnblockTime( void )
|
||||||
{
|
{
|
||||||
TCB_t *pxTCB;
|
TCB_t *pxTCB;
|
||||||
|
|
|
@ -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 <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
#include "freertos/semphr.h"
|
#include "esp_heap_caps.h"
|
||||||
#include "freertos/queue.h"
|
|
||||||
#include "freertos/event_groups.h"
|
|
||||||
#include "unity.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());
|
vTaskDelete(NULL); //Deleting self means deleting currently running task
|
||||||
vTaskDelay(5);
|
}
|
||||||
vTaskDelete(NULL);
|
|
||||||
|
static void tsk_extern_del(void *param)
|
||||||
|
{
|
||||||
|
vTaskDelay(portMAX_DELAY); //Await external deletion
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("FreeRTOS Delete Tasks", "[freertos]")
|
TEST_CASE("FreeRTOS Delete Tasks", "[freertos]")
|
||||||
{
|
{
|
||||||
|
/* -------------- Test vTaskDelete() on currently running tasks ----------------*/
|
||||||
uint32_t before_count = uxTaskGetNumberOfTasks();
|
uint32_t before_count = uxTaskGetNumberOfTasks();
|
||||||
|
uint32_t before_heap = heap_caps_get_free_size(HEAP_CAPS);
|
||||||
xTaskCreatePinnedToCore(task_delete_self, "tsk_self_a", 4096, NULL, configMAX_PRIORITIES - 1, NULL, 0);
|
for(int i = 0; i < portNUM_PROCESSORS; i++){
|
||||||
xTaskCreatePinnedToCore(task_delete_self, "tsk_self_a", 4096, NULL, configMAX_PRIORITIES - 1, NULL, 0);
|
for(int j = 0; j < NO_OF_TSKS; j++){
|
||||||
TEST_ASSERT_EQUAL(before_count + 2, uxTaskGetNumberOfTasks());
|
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(tsk_self_del, "tsk_self", 1024, NULL, configMAX_PRIORITIES - 1, NULL, i));
|
||||||
vTaskDelay(200 / portTICK_PERIOD_MS);
|
}
|
||||||
TEST_ASSERT_EQUAL(before_count, uxTaskGetNumberOfTasks());
|
}
|
||||||
|
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));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
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.
|
the calling core has released the mutex by exiting the critical section.
|
||||||
|
|
||||||
:ref:`deletion-callbacks`: ESP-IDF FreeRTOS has
|
:ref:`floating-points`: The ESP32 supports hardware acceleration of single
|
||||||
backported the Thread Local Storage Pointers feature. However they have the
|
precision floating point arithmetic (`float`). However the use of hardware
|
||||||
extra feature of deletion callbacks. Deletion callbacks are used to
|
acceleration leads to some behavioral restrictions in ESP-IDF FreeRTOS.
|
||||||
automatically free memory used by Thread Local Storage Pointers during the task
|
Therefore, tasks that utilize `float` will automatically be pinned to a core if
|
||||||
deletion. Call ``vTaskSetThreadLocalStoragePointerAndDelCallback()``
|
not done so already. Furthermore, `float` cannot be used in interrupt service
|
||||||
to set Thread Local Storage Pointers and deletion callbacks.
|
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.
|
: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
|
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.
|
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:
|
.. _deletion-callbacks:
|
||||||
|
|
||||||
Thread Local Storage Pointers & Deletion Callbacks
|
Thread Local Storage Pointers & Deletion Callbacks
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
|
||||||
Thread Local Storage Pointers are pointers stored directly in the TCB which
|
Thread Local Storage Pointers (TLSP) are pointers stored directly in the TCB.
|
||||||
allows each task to have a pointer to a data structure containing that is
|
TLSP allow each task to have its own unique set of pointers to data structures.
|
||||||
specific to that task. However vanilla FreeRTOS provides no functionality to
|
However task deletion behavior in vanilla FreeRTOS does not automatically
|
||||||
free the memory pointed to by the Thread Local Storage Pointers. Therefore if
|
free the memory pointed to by TLSP. Therefore if the memory pointed to by
|
||||||
the memory pointed to by the Thread Local Storage Pointers is not explicitly
|
TLSP is not explicitly freed by the user before task deletion, memory leak will
|
||||||
freed by the user before a task is deleted, memory leak will occur.
|
occur.
|
||||||
|
|
||||||
ESP-IDF FreeRTOS provides the added feature of deletion callbacks. These
|
ESP-IDF FreeRTOS provides the added feature of Deletion Callbacks. Deletion
|
||||||
deletion callbacks are used to automatically free the memory pointed to by the
|
Callbacks are called automatically during task deletion to free memory pointed
|
||||||
Thread Local Storage Pointers when a task is deleted. Each Thread Local Storage
|
to by TLSP. Each TLSP can have its own Deletion Callback. Note that due to the
|
||||||
Pointer can have its own call back, and these call backs are called when the
|
to :ref:`task-deletion` behavior, there can be instances where Deletion
|
||||||
Idle tasks cleans up a deleted tasks.
|
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
|
Deletion callbacks are of type
|
||||||
``vTaskSetThreadLocalStoragePointer()`` whereas ESP-IDF FreeRTOS sets a Thread
|
``void (*TlsDeleteCallbackFunction_t)( int, void * )`` where the first parameter
|
||||||
Local Storage Pointers and Deletion Callbacks using
|
is the index number of the associated TLSP, and the second parameter is the
|
||||||
``vTaskSetThreadLocalStoragePointerAndDelCallback()`` which accepts a pointer
|
TLSP itself.
|
||||||
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
|
Deletion callbacks are set alongside TLSP by calling
|
||||||
the pthreads API thread local storage (pthread_getspecific() & pthread_setspecific()).
|
``vTaskSetThreadLocalStoragePointerAndDelCallback()``. Calling the vanilla
|
||||||
Other indexes can be used for any purpose, provided
|
FreeRTOS function ``vTaskSetThreadLocalStoragePointer()`` will simply set the
|
||||||
:ref:`CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS` is set to a high enough value.
|
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`
|
For more details see :component_file:`freertos/include/freertos/task.h`
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
void unityTask(void *pvParameters)
|
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 */
|
unity_run_menu(); /* Doesn't return */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue