FreeRTOS-Kernel icon indicating copy to clipboard operation
FreeRTOS-Kernel copied to clipboard

[BUG] SMP FreeRTOS scheduler start critical section race condition

Open Dazza0 opened this issue 3 years ago • 0 comments

Description

  • Core 0 has already started the scheduler (via vTaskStartScheduler())
  • Core 1 has not called xPortStartScheduler() yet
  • Core 0 triggers a yield on core 1 (e.g., by creating a new task with core 1 affinity)
  • If core 1 attempts to enter a critical section (e.g., via calling xTaskGetSchedulerState()), core 1 will assert in the following line within prvCheckForRunStateChange():
configASSERT( pxThisTCB->xTaskRunState != taskTASK_YIELDING );

Cause

  • Core 0 calling vTaskStartScheduler() should assign one of the idle tasks as core 1's current task
  • Core 0 triggering a yield will cause core 1's current task's state to be taskTASK_YIELDING
  • When core 1 enters a critical section, it will call prvCheckForRunStateChange() because the scheduler has already been started by core 0
  • In prvCheckForRunStateChange(), core 1 calls portENABLE_INTERRUPTS() expecting a context switch to occur, but core 1 cannot switch contexts because it has not called xPortStartScheduler() yet.
  • No context switch occurs, thus core 1's current task's state is still taskTASK_YIELDING.

Possible Fixes

Restrict usage of FreeRTOS API before the core has started the scheduler

One way to avoid this race condition is restrict a core from calling any FreeRTOS API before the core has called xPortStartScheduler(). However this may be inconvenient if the port has particular routines that are used both before and after the scheduler has started, and that those routines call taskENTER_CRITICAL().

Each core has their own xSchedulerRunning

Each core could individually track whether they have started their scheduler as such:

PRIVILEGED_DATA static volatile BaseType_t xSchedulerRunnings[ configNUM_CORES ] = { pdFALSE };
#define xSchedulerRunning xSchedulerRunnings[ portGET_CORE_ID() ]

However, since vTaskStartScheduler() is only called from one core (e.g., core 0), all other cores will need a separate function to set their own xSchedulerRunnings[x] variable to pdTRUE when starting the scheduler, such as a vTaskStartSchedulerOtherCores() function that is called by all other cores before they call xPortStartScheduler().

Dazza0 avatar Jun 27 '22 05:06 Dazza0