Fix SMP scheduler evicting task when preemption is disabled

In the SMP time-slicing path of xTaskIncrementTick(), xYieldPendings[]
was set unconditionally without checking xPreemptionDisable. Although
the yield-processing block correctly skipped acting on it for cores with
preemption disabled, the stale flag remained and was later misinterpreted
by FromISR functions (xTaskGenericNotifyFromISR, xTaskResumeFromISR,
etc.) that use xYieldPendings as a proxy for whether prvYieldForTask()
decided to yield. This caused *pxHigherPriorityTaskWoken to be
erroneously set to pdTRUE, triggering portYIELD_FROM_ISR() and
ultimately evicting the preemption-disabled task via
prvSelectHighestPriorityTask().

Three-layer fix:

1. Root cause: Guard the time-slicing xYieldPendings[] assignment in
   xTaskIncrementTick() with a configUSE_TASK_PREEMPTION_DISABLE check
   so the flag is never set for cores running preemption-disabled tasks.

2. Defense in depth: In the four functions that check xYieldPendings[]
   after prvYieldForTask() (xTaskResumeFromISR, xTaskRemoveFromEventList,
   xTaskGenericNotifyFromISR, vTaskGenericNotifyGiveFromISR), save the
   value before the call and only report a yield if it transitioned from
   pdFALSE to pdTRUE, eliminating false positives from stale flags.

3. Safety net: Add a preemption-disable check in the SMP
   vTaskSwitchContext(), analogous to the existing uxSchedulerSuspended
   check. If a context switch is somehow triggered for a core whose
   current task has preemption disabled, the switch is deferred by
   setting xYieldPendings and will execute when vTaskPreemptionEnable()
   is called.

Closes #1376

Made-with: Cursor
This commit is contained in:
Harsh Abasaheb Chavan 2026-03-15 04:12:12 +05:30
parent f1043c49d5
commit c926adcf6e

37
tasks.c
View file

@ -3524,9 +3524,12 @@ static void prvInitialiseNewTask( TaskFunction_t pxTaskCode,
#if ( ( configNUMBER_OF_CORES > 1 ) && ( configUSE_PREEMPTION == 1 ) )
{
BaseType_t xYieldPendingBefore = xYieldPendings[ portGET_CORE_ID() ];
prvYieldForTask( pxTCB );
if( xYieldPendings[ portGET_CORE_ID() ] != pdFALSE )
if( ( xYieldPendingBefore == pdFALSE ) &&
( xYieldPendings[ portGET_CORE_ID() ] != pdFALSE ) )
{
xYieldRequired = pdTRUE;
}
@ -4890,7 +4893,12 @@ BaseType_t xTaskIncrementTick( void )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCBs[ xCoreID ]->uxPriority ] ) ) > 1U )
{
xYieldPendings[ xCoreID ] = pdTRUE;
#if ( configUSE_TASK_PREEMPTION_DISABLE == 1 )
if( pxCurrentTCBs[ xCoreID ]->xPreemptionDisable == pdFALSE )
#endif
{
xYieldPendings[ xCoreID ] = pdTRUE;
}
}
else
{
@ -5222,6 +5230,16 @@ BaseType_t xTaskIncrementTick( void )
* SMP port. */
configASSERT( portGET_CRITICAL_NESTING_COUNT( xCoreID ) == 0 );
#if ( configUSE_TASK_PREEMPTION_DISABLE == 1 )
if( pxCurrentTCBs[ xCoreID ]->xPreemptionDisable != pdFALSE )
{
/* The current task has preemption disabled - do not allow a
* context switch. The yield will be performed when preemption
* is re-enabled. */
xYieldPendings[ xCoreID ] = pdTRUE;
}
else
#endif /* #if ( configUSE_TASK_PREEMPTION_DISABLE == 1 ) */
if( uxSchedulerSuspended != ( UBaseType_t ) 0U )
{
/* The scheduler is currently suspended - do not allow a context
@ -5479,9 +5497,12 @@ BaseType_t xTaskRemoveFromEventList( const List_t * const pxEventList )
#if ( configUSE_PREEMPTION == 1 )
{
BaseType_t xYieldPendingBefore = xYieldPendings[ portGET_CORE_ID() ];
prvYieldForTask( pxUnblockedTCB );
if( xYieldPendings[ portGET_CORE_ID() ] != pdFALSE )
if( ( xYieldPendingBefore == pdFALSE ) &&
( xYieldPendings[ portGET_CORE_ID() ] != pdFALSE ) )
{
xReturn = pdTRUE;
}
@ -8190,9 +8211,12 @@ TickType_t uxTaskResetEventItemValue( void )
{
#if ( configUSE_PREEMPTION == 1 )
{
BaseType_t xYieldPendingBefore = xYieldPendings[ portGET_CORE_ID() ];
prvYieldForTask( pxTCB );
if( xYieldPendings[ portGET_CORE_ID() ] == pdTRUE )
if( ( xYieldPendingBefore == pdFALSE ) &&
( xYieldPendings[ portGET_CORE_ID() ] == pdTRUE ) )
{
if( pxHigherPriorityTaskWoken != NULL )
{
@ -8324,9 +8348,12 @@ TickType_t uxTaskResetEventItemValue( void )
{
#if ( configUSE_PREEMPTION == 1 )
{
BaseType_t xYieldPendingBefore = xYieldPendings[ portGET_CORE_ID() ];
prvYieldForTask( pxTCB );
if( xYieldPendings[ portGET_CORE_ID() ] == pdTRUE )
if( ( xYieldPendingBefore == pdFALSE ) &&
( xYieldPendings[ portGET_CORE_ID() ] == pdTRUE ) )
{
if( pxHigherPriorityTaskWoken != NULL )
{