freertos/fix SMP bug with Idle task clean up

This commit backports vTaskDelete() behavior from FreeRTOS v9.0.0  which
allows for the immediate freeing of task memory if the task being deleted
is not currently running and not pinned to the other core. This commit also
fixes a bug in prvCheckTasksWaitingTermination which prevented the
Idle Task from cleaning up all tasks awaiting deletion. Each iteration of the Idle
Task should traverse the xTasksWaitingTermination list and clean up all tasks
not pinned to the other core. The previous implementation would cause
prvCheckTasksWaitingTermination to return when encountering a task
pinned to the other core whilst traversing the xTasksWaitingTermination list.

The test case for vTaskDelete() has been updated to test for the bugfix and
backported deletion behavior.
This commit is contained in:
Darian Leung
2017-11-23 22:35:54 +08:00
parent 0554bc59a2
commit 38afa32cfb
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;