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:
Ivan Grokhotkov 2017-12-07 11:55:10 +08:00
commit 2b92119840
4 changed files with 247 additions and 94 deletions

View file

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

View file

@ -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 "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));
}

View file

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

View file

@ -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 */
}