The source code of the task function includes the following parts:
Priority handling
Task creation
Starting the first task
Task switching
Task blocking/waking
This article will not cover all the above points, but will focus on:1. Priority handling2. Creation and starting of the first task1. Priority handlingFreeRTOS tasks are divided into 1-5 different priority levels, where higher priority tasks can preempt lower priority tasks.The following two macros are used to record the highest priority and find the highest priority task:
#define taskRECORD_READY_PRIORITY( uxPriority ) \ { if( ( uxPriority ) > uxTopReadyPriority ) { \ uxTopReadyPriority = ( uxPriority ); \ // Find the highest priority task } \ } /* taskRECORD_READY_PRIORITY */
Additionally, there is
#define taskSELECT_HIGHEST_PRIORITY_TASK() \ { \ UBaseType_t uxTopPriority = uxTopReadyPriority; \ \ /* Find the highest priority queue that contains ready tasks. */ \ while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \ { \ configASSERT( uxTopPriority ); // Error if highest priority equals 0 \ --uxTopPriority; \ } \ \ /* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of \ * the same priority get an equal share of the processor time. */ \ listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \ uxTopReadyPriority = uxTopPriority; \ } /* taskSELECT_HIGHEST_PRIORITY_TASK */
Key points:
-
<span>listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );</span>Extracts the next task from the current highest priority ready queue and updates
<span>pxCurrentTCB</span>(current task control block). -
<span>uxTopReadyPriority = uxTopPriority;</span>Updates the global variable
<span>uxTopReadyPriority</span>, recording the currently running priority.
The following macro modifies the total priority when the highest priority task is empty.
# define taskRESET_READY_PRIORITY( uxPriority ) \ { \ if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) { portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ) }}
-
<span>listCURRENT_LIST_LENGTH(...) == 0</span>Checks if the ready task list for the specified priority
<span>uxPriority</span>is empty. -
<span>portRESET_READY_PRIORITY(...)</span>Hardware optimization If the list is empty, calls the hardware-accelerated method to update
<span>uxTopReadyPriority</span>(e.g., clearing the corresponding bit in the priority bitmap). Ensures that the scheduler can correctly find the new highest priority when it runs next.
This macro can add a task to the ready list.
#define prvAddTaskToReadyList( pxTCB ) \ traceMOVED_TASK_TO_READY_STATE( pxTCB ); \ taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \ vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \ tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
Step-by-step explanation:
-
<span>traceMOVED_TASK_TO_READY_STATE( pxTCB );</span>
- Calls a debug hook function to record the task state changing to ready (optional feature, must enable
<span>configUSE_TRACE_FACILITY</span><span>).</span>
<span>taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority );</span>
- Calls the macro
<span>taskRECORD_READY_PRIORITY</span>, updating the global variable<span>uxTopReadyPriority</span><span> (higher priority).</span>
<span>vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );</span>
<span>pxReadyTasksLists[priority]</span>: The ready task list with priority<span>priority</span>.<span>pxTCB->xStateListItem</span>: The linked list node embedded in the task control block (TCB).
- Core operation: Inserts the task’s list item
<span>xStateListItem</span>at the end of the corresponding priority ready list. - Parameters
<span> pxReadyTasksLists[priority]: The ready task list with priority </span><span>priority</span>. pxTCB->xStateListItem: The linked list node embedded in the task control block (TCB).
2.Task Creation
1. TCB
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */{ volatile StackType_t * pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */ #if ( portUSING_MPU_WRAPPERS == 1 ) xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */ #endif ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */ ListItem_t xEventListItem; /*< Used to reference a task from an event list. */ UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */ StackType_t * pxStack; /*< Points to the start of the stack. */ char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ #if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) ) StackType_t * pxEndOfStack; /*< Points to the highest valid address for the stack. */ #endif #if ( portCRITICAL_NESTING_IN_TCB == 1 ) UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */ #endif #if ( configUSE_TRACE_FACILITY == 1 ) UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */ UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */ #endif #if ( configUSE_MUTEXES == 1 ) UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */ UBaseType_t uxMutexesHeld; #endif #if ( configUSE_APPLICATION_TASK_TAG == 1 ) TaskHookFunction_t pxTaskTag; #endif #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ]; #endif #if ( configGENERATE_RUN_TIME_STATS == 1 ) uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */ #endif #if ( configUSE_NEWLIB_REENTRANT == 1 ) /* Allocate a Newlib reent structure that is specific to this task. * Note Newlib support has been included by popular demand, but is not * used by the FreeRTOS maintainers themselves. FreeRTOS is not * responsible for resulting newlib operation. User must be familiar with * newlib and must provide system-wide implementations of the necessary * stubs. Be warned that (at the time of writing) the current newlib design * implements a system-wide malloc() that must be provided with locks. * * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html * for additional information. */ struct _reent xNewLib_reent; #endif #if ( configUSE_TASK_NOTIFICATIONS == 1 ) volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; #endif /* See the comments in FreeRTOS.h with the definition of * tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */ #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */ uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */ #endif #if ( INCLUDE_xTaskAbortDelay == 1 ) uint8_t ucDelayAborted; #endif #if ( configUSE_POSIX_ERRNO == 1 ) int iTaskErrno; #endif} tskTCB;
Overall planning:

There are actually only a few important points:
(a).
<span>pxTopOfStack</span> |
<span>StackType_t*</span> |
Stack top pointer: Points to the current top of the task stack (must be the first member of the TCB structure, as assembly code relies on this offset). |
This records the position of the task stack, and to execute this task, you can useBX TCB【1】.(b).
<span>xStateListItem</span> |
<span>ListItem_t</span> |
State list item: Used to mount the task to the <span>Ready</span>/<span>Blocked</span>/<span>Suspended</span> state lists (such as <span>pxReadyTasksLists</span><code><span>).</span> |
Records the task state, which can be used to call the functionvListInsert to mount the task.(c).
<span>xEventListItem</span> |
<span>ListItem_t</span> |
Event list item: Used to mount the task to the event list (such as the waiting list for semaphores and queues). |
Similar to (b), but the mounting position is different.
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ const configSTACK_DEPTH_TYPE usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask ) { TCB_t * pxNewTCB; BaseType_t xReturn; /* If the stack grows down then allocate the stack then the TCB so the stack * does not grow into the TCB. Likewise if the stack grows up then allocate * the TCB then the stack. */ #if ( portSTACK_GROWTH > 0 ) { /* Allocate space for the TCB. Where the memory comes from depends on * the implementation of the port malloc function and whether or not static * allocation is being used. */ pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); if( pxNewTCB != NULL ) { /* Allocate space for the stack used by the task being created. * The base of the stack memory stored in the TCB so the task can * be deleted later if required. */ pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ if( pxNewTCB->pxStack == NULL ) { /* Could not allocate the stack. Delete the allocated TCB. */ vPortFree( pxNewTCB ); pxNewTCB = NULL; } } } #else /* portSTACK_GROWTH */ { StackType_t * pxStack; /* Allocate space for the stack used by the task being created. */ pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */ if( pxStack != NULL ) { /* Allocate space for the TCB. */ pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */ if( pxNewTCB != NULL ) { /* Store the stack location in the TCB. */ pxNewTCB->pxStack = pxStack; } else { /* The stack cannot be used as the TCB was not created. Free * it again. */ vPortFree( pxStack ); } } else { pxNewTCB = NULL; } } #endif /* portSTACK_GROWTH */ if( pxNewTCB != NULL ) { #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */ { /* Tasks can be created statically or dynamically, so note this * task was created dynamically in case it is later deleted. */ pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB; } #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */ prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL ); prvAddNewTaskToReadyList( pxNewTCB ); xReturn = pdPASS; } else { xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; } return xReturn; }
This function is the task creation function:
Explaining some key points:
StackType_t *pxStack = pvPortMalloc(usStackDepth * sizeof(StackType_t)); // First allocate stackif (pxStack != NULL) { pxNewTCB = pvPortMalloc(sizeof(TCB_t)); // Then allocate TCBif (pxNewTCB != NULL) { pxNewTCB->pxStack = pxStack; // Associate stack with TCB } else { vPortFree(pxStack); // TCB allocation failed, free stack } }
According to the user’s requested size, allocate a stack, then allocate the TCB and associate them. If any step fails, all are released.Dynamic tasks and static tasks are different.
#if (tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0) pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;#endif
Dynamic tasks require the scheduler to release memory, while static tasks require the user to release it (the user fully designs the TCB and stack).e.g.
// Dynamic task creationTaskHandle_t xDynamicTaskHandle; xTaskCreate(vTaskFunction, "Dynamic", 128, NULL, 1, &xDynamicTaskHandle);// Static task creationstatic StaticTask_t xTaskTCB; // User statically allocates TCBstatic StackType_t xTaskStack[128]; // User statically allocates stackTaskHandle_t xStaticTaskHandle = xTaskCreateStatic(vTaskFunction, "Static", 128, NULL, 1,xTaskStack, &xTaskTCB );
It can be seen that static tasks require the user to malloc and free space.Initializing the TCB and stack:
prvInitialiseNewTask( pxTaskCode, pcName, (uint32_t)usStackDepth,pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL);
<span><span>prvInitialiseNewTask</span></span> is the core function used by FreeRTOS to initialize the new task control block (TCB) and task stack, responsible for converting “bare” memory into a task that can be managed by the scheduler. Its significance can be likened to the “task incubator” of an operating system, with specific functions including:
TCB initialization: Filling in task name, priority, list items, and other metadata.Stack space preparation: Setting the stack pointer and simulating the interrupt context so that the task can correctly jump to the entry function when it first runs.
Joining the ready list
prvAddNewTaskToReadyList(pxNewTCB);
Mounts the task to the corresponding priority ready list (<span>pxReadyTasksLists[uxPriority]</span>), and if the new task’s priority is higher than the current task, triggers a task switch (if the scheduler has been started).Let’s discuss prvInitialiseNewTask in detail.
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ const uint32_t ulStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask, TCB_t * pxNewTCB, const MemoryRegion_t * const xRegions ){ StackType_t * pxTopOfStack; UBaseType_t x; #if ( portUSING_MPU_WRAPPERS == 1 ) /* Should the task be created in privileged mode? */ BaseType_t xRunPrivileged; if( ( uxPriority & portPRIVILEGE_BIT ) != 0U ) { xRunPrivileged = pdTRUE; } else { xRunPrivileged = pdFALSE; } uxPriority &= ~portPRIVILEGE_BIT; #endif /* portUSING_MPU_WRAPPERS == 1 */ /* Avoid dependency on memset() if it is not required. */ #if ( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 ) { /* Fill the stack with a known value to assist debugging. */ ( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) ); } #endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */ /* Calculate the top of stack address. This depends on whether the stack * grows from high memory to low (as per the 80x86) or vice versa. * portSTACK_GROWTH is used to make the result positive or negative as required * by the port. */ #if ( portSTACK_GROWTH < 0 ) { pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] ); pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 !e9033 !e9078 MISRA exception. Avoiding casts between pointers and integers is not practical. Size differences accounted for using portPOINTER_SIZE_TYPE type. Checked by assert(). */ /* Check the alignment of the calculated top of stack is correct. */ configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) ); #if ( configRECORD_STACK_HIGH_ADDRESS == 1 ) { /* Also record the stack's high address, which may assist * debugging. */ pxNewTCB->pxEndOfStack = pxTopOfStack; } #endif /* configRECORD_STACK_HIGH_ADDRESS */ } #else /* portSTACK_GROWTH */ { pxTopOfStack = pxNewTCB->pxStack; /* Check the alignment of the stack buffer is correct. */ configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) ); /* The other extreme of the stack space is required if stack checking is * performed. */ pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 ); } #endif /* portSTACK_GROWTH */ /* Store the task name in the TCB. */ if( pcName != NULL ) { for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ) { pxNewTCB->pcTaskName[ x ] = pcName[ x ]; /* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter than * configMAX_TASK_NAME_LEN characters just in case the memory after the * string is not accessible (extremely unlikely). */ if( pcName[ x ] == ( char ) 0x00 ) { break; } else { mtCOVERAGE_TEST_MARKER(); } } /* Ensure the name string is terminated in the case that the string length * was greater or equal to configMAX_TASK_NAME_LEN. */ pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0'; } else { /* The task has not been given a name, so just ensure there is a NULL * terminator when it is read out. */ pxNewTCB->pcTaskName[ 0 ] = 0x00; } /* This is used as an array index so must ensure it's not too large. First * remove the privilege bit if one is present. */ if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES ) { uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U; } else { mtCOVERAGE_TEST_MARKER(); } pxNewTCB->uxPriority = uxPriority; #if ( configUSE_MUTEXES == 1 ) { pxNewTCB->uxBasePriority = uxPriority; pxNewTCB->uxMutexesHeld = 0; } #endif /* configUSE_MUTEXES */ vListInitialiseItem( &( pxNewTCB->xStateListItem ) ); vListInitialiseItem( &( pxNewTCB->xEventListItem ) ); /* Set the pxNewTCB as a link back from the ListItem_t. This is so we can get * back to the containing TCB from a generic item in a list. */ listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB ); /* Event lists are always in priority order. */ listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */ listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB ); #if ( portCRITICAL_NESTING_IN_TCB == 1 ) { pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U; } #endif /* portCRITICAL_NESTING_IN_TCB */ #if ( configUSE_APPLICATION_TASK_TAG == 1 ) { pxNewTCB->pxTaskTag = NULL; } #endif /* configUSE_APPLICATION_TASK_TAG */ #if ( configGENERATE_RUN_TIME_STATS == 1 ) { pxNewTCB->ulRunTimeCounter = 0UL; } #endif /* configGENERATE_RUN_TIME_STATS */ #if ( portUSING_MPU_WRAPPERS == 1 ) { vPortStoreTaskMPUSettings( &( pxNewTCB->xMPUSettings ), xRegions, pxNewTCB->pxStack, ulStackDepth ); } #else { /* Avoid compiler warning about unreferenced parameter. */ ( void ) xRegions; } #endif #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 ) { memset( ( void * ) &( pxNewTCB->pvThreadLocalStoragePointers[ 0 ] ), 0x00, sizeof( pxNewTCB->pvThreadLocalStoragePointers ) ); } #endif #if ( configUSE_TASK_NOTIFICATIONS == 1 ) { memset( ( void * ) &( pxNewTCB->ulNotifiedValue[ 0 ] ), 0x00, sizeof( pxNewTCB->ulNotifiedValue ) ); memset( ( void * ) &( pxNewTCB->ucNotifyState[ 0 ] ), 0x00, sizeof( pxNewTCB->ucNotifyState ) ); } #endif #if ( configUSE_NEWLIB_REENTRANT == 1 ) { /* Initialise this task's Newlib reent structure. * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html * for additional information. */ _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) ); } #endif #if ( INCLUDE_xTaskAbortDelay == 1 ) { pxNewTCB->ucDelayAborted = pdFALSE; } #endif /* Initialize the TCB stack to look as if the task was already running, * but had been interrupted by the scheduler. The return address is set * to the start of the task function. Once the stack has been initialised * the top of stack variable is updated. */ #if ( portUSING_MPU_WRAPPERS == 1 ) { /* If the port has capability to detect stack overflow, * pass the stack end address to the stack initialization * function as well. */ #if ( portHAS_STACK_OVERFLOW_CHECKING == 1 ) { #if ( portSTACK_GROWTH < 0 ) { pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters, xRunPrivileged ); } #else /* portSTACK_GROWTH */ { pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters, xRunPrivileged ); } #endif /* portSTACK_GROWTH */ } #else /* portHAS_STACK_OVERFLOW_CHECKING */ { pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged ); } #endif /* portHAS_STACK_OVERFLOW_CHECKING */ } #else /* portUSING_MPU_WRAPPERS */ { /* If the port has capability to detect stack overflow, * pass the stack end address to the stack initialization * function as well. */ #if ( portHAS_STACK_OVERFLOW_CHECKING == 1 ) { #if ( portSTACK_GROWTH < 0 ) { pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxStack, pxTaskCode, pvParameters ); } #else /* portSTACK_GROWTH */ { pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxNewTCB->pxEndOfStack, pxTaskCode, pvParameters ); } #endif /* portSTACK_GROWTH */ } #else /* portHAS_STACK_OVERFLOW_CHECKING */ { pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters ); } #endif /* portHAS_STACK_OVERFLOW_CHECKING */ } #endif /* portUSING_MPU_WRAPPERS */ if( pxCreatedTask != NULL ) { /* Pass the handle out in an anonymous way. The handle can be used to * change the created task's priority, delete the created task, etc.*/ *pxCreatedTask = ( TaskHandle_t ) pxNewTCB; } else { mtCOVERAGE_TEST_MARKER(); }}
This code is used to initialize a task’s TCB and stack. Although it seems long, there are only a few very important points.1. Finding the stack top
pxTopOfStack = &( pxNewTCB->pxStack[ulStackDepth - 1]);pxTopOfStack = (StackType_t*)((uintptr_t)pxTopOfStack & ~portBYTE_ALIGNMENT_MASK);configASSERT(...); // Verify alignment#if( configRECORD_STACK_HIGH_ADDRESS == 1 ) pxNewTCB->pxEndOfStack = pxTopOfStack; // Record stack boundary#endif
Key pointThe stack top address is aligned according to hardware requirements (e.g., 8 bytes).<span>pxEndOfStack records the highest address of the stack for debugging or overflow detection.</span>2. Copying the task nameCopies the name into the TCB.3. Stack frame initialization (simulating interrupt context)
#if( portUSING_MPU_WRAPPERS == 1 ) pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged);#else pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, pxTaskCode, pvParameters);#endif
- Core function The hardware-related function
<span>pxPortInitialiseStack</span><span> simulates the interrupt stack frame, allowing the task to correctly jump to the entry function when it first runs:</span><span>PC points to </span><code><span>pxTaskCode</span><span> (task entry).</span><span>R0 carries </span><code><span>pvParameters</span><span> (task parameters).</span><span>LR points to the task exit function (such as </span><code><span>vTaskExit</span><span>).</span><span>This part of the code calls pxPortInitialiseStack in port.c to simulate the context (stack).</span>
4. List item initialization (2 types)
(1) State list item (<span>xStateListItem</span>)
vListInitialiseItem(&(pxNewTCB->xStateListItem)); // Initialize list itemlistSET_LIST_ITEM_OWNER(&(pxNewTCB->xStateListItem), pxNewTCB); // Set owner
Function
Initializes the task’s state list item (<span>xStateListItem</span><span>) as an empty node.</span><span>By using </span><code><span>listSET_LIST_ITEM_OWNER</span><span>, the owner of the list item is set to the current task's TCB, allowing for reverse access to the task handle from the list item.</span><span>Used to mount the task to the </span><strong><span>ready list</span></strong><span>, </span><strong><span>blocked list</span></strong><span> or </span><strong><span>suspended list</span></strong><span> (such as </span><code><span>pxReadyTasksLists[priority]</span><span>).</span><h4><strong><span>(2) Event list item (</span><code><span>xEventListItem</span>)
vListInitialiseItem(&(pxNewTCB->xEventListItem)); // Initialize list itemlistSET_LIST_ITEM_VALUE(&(pxNewTCB->xEventListItem), (TickType_t)configMAX_PRIORITIES - (TickType_t)uxPriority); // Set priority encoding valuelistSET_LIST_ITEM_OWNER(&(pxNewTCB->xEventListItem), pxNewTCB); // Set owner
- Initializes the event list item and sets its value to
<span>configMAX_PRIORITIES - uxPriority</span><span>.</span><span> Binds the list item owner to the TCB. (FreeRTOS event lists are sorted in ascending order, this operation ensures that high-priority tasks are at the front of the event list.)</span> - Purpose Used to mount the task to the event waiting list (such as the waiting list for semaphores and queues).
Starting the first taskTasks are sorted and run in several lists according to different priorities.
static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB ){ /* Ensure interrupts don't access the task lists while the lists are being * updated. */ taskENTER_CRITICAL(); { uxCurrentNumberOfTasks++; if( pxCurrentTCB == NULL ) { /* There are no other tasks, or all the other tasks are in * the suspended state - make this the current task. */ pxCurrentTCB = pxNewTCB; if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ) { /* This is the first task to be created so do the preliminary * initialisation required. We will not recover if this call * fails, but we will report the failure. */ prvInitialiseTaskLists(); } else { mtCOVERAGE_TEST_MARKER(); } } else { /* If the scheduler is not already running, make this task the * current task if it is the highest priority task to be created * so far. */ if( xSchedulerRunning == pdFALSE ) { if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ) { pxCurrentTCB = pxNewTCB; } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } } uxTaskNumber++; #if ( configUSE_TRACE_FACILITY == 1 ) { /* Add a counter into the TCB for tracing only. */ pxNewTCB->uxTCBNumber = uxTaskNumber; } #endif /* configUSE_TRACE_FACILITY */ traceTASK_CREATE( pxNewTCB ); prvAddTaskToReadyList( pxNewTCB ); portSETUP_TCB( pxNewTCB ); } taskEXIT_CRITICAL(); if( xSchedulerRunning != pdFALSE ) { /* If the created task is of a higher priority than the current task * then it should run now. */ if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ) { taskYIELD_IF_USING_PREEMPTION(); } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); }}
This function tells us what happens when a task becomes ready:
taskENTER_CRITICAL();
Any scheduling behavior must enter a critical section (disable interrupts).
if( pxCurrentTCB == NULL) { pxCurrentTCB = pxNewTCB; // Set the new task as the current running task if( uxCurrentNumberOfTasks == 1) { prvInitialiseTaskLists(); // Initialize task lists (such as ready list, delayed list) } }
If there are currently no tasks, directly execute this task.
Priority handling when the scheduler is not running
if( xSchedulerRunning == pdFALSE ) {if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ) { pxCurrentTCB = pxNewTCB; // New task has a higher priority, replace with current task }}
If the scheduler is not started (<span>xSchedulerRunning == pdFALSE</span><span>).</span><span> If the new task's priority is not lower than the current task, immediately switch the current task to the new task.</span><span><span>Otherwise, trigger task scheduling (preemptive scheduling).</span></span><pre><code class="language-c">if( xSchedulerRunning != pdFALSE ) {if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ) { taskYIELD_IF_USING_PREEMPTION(); // Trigger task switch }}
- If the scheduler has started (
<span>xSchedulerRunning == pdTRUE</span><span>) and the new task's priority is higher than the current task.</span><span> Call </span><code><span>taskYIELD()</span><span>, triggering a PendSV exception to force the scheduler to reselect the highest priority task to run.</span>
Finally, insert into the list.
prvAddTaskToReadyList( pxNewTCB );
Discussing task startup, let’s also talk about task deletion.
void vTaskDelete( TaskHandle_t xTaskToDelete ) { TCB_t * pxTCB; taskENTER_CRITICAL(); { /* If null is passed in here then it is the calling task that is * being deleted. */ pxTCB = prvGetTCBFromHandle( xTaskToDelete ); /* Remove task from the ready/delayed list. */ if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) { taskRESET_READY_PRIORITY( pxTCB->uxPriority ); } else { mtCOVERAGE_TEST_MARKER(); } /* Is the task waiting on an event also? */ if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL ) { ( void ) uxListRemove( &( pxTCB->xEventListItem ) ); } else { 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( pxTCB == pxCurrentTCB ) { /* A task is deleting itself. 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->xStateListItem ) ); /* 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. */ ++uxDeletedTasksWaitingCleanUp; /* Call the delete hook before portPRE_TASK_DELETE_HOOK() as * portPRE_TASK_DELETE_HOOK() does not return in the Win32 port. */ traceTASK_DELETE( pxTCB ); /* 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 ); } else { --uxCurrentNumberOfTasks; traceTASK_DELETE( pxTCB ); prvDeleteTCB( pxTCB ); /* Reset the next expected unblock time in case it referred to * the task that has just been deleted. */ prvResetNextTaskUnblockTime(); } } taskEXIT_CRITICAL(); /* Force a reschedule if it is the currently running task that has just * been deleted. */ if( xSchedulerRunning != pdFALSE ) { if( pxTCB == pxCurrentTCB ) { configASSERT( uxSchedulerSuspended == 0 ); portYIELD_WITHIN_API(); } else { mtCOVERAGE_TEST_MARKER(); } } }
This segment of code seems long, but it revolves around a hook function called
traceTASK_DELETE(pxTCB);
Of course, we need to mount this task to the termination list to reclaim resources.
vListInsertEnd(&xTasksWaitingTermination, &(pxTCB->xStateListItem));
Before this, we also need to remove the task from the event list and waiting list.
if( uxListRemove(&(pxTCB->xStateListItem)) == 0) { taskRESET_READY_PRIORITY(pxTCB->uxPriority);}
<span>uxListRemove</span>Removes the task’s<span>xStateListItem</span>from its current list (ready list or blocked list). The return value indicates the remaining number of tasks in the original list. If it is 0, it means the ready list for that priority is empty.<span>taskRESET_READY_PRIORITY</span>If the task’s priority’s ready list is empty, clears the corresponding bit in the priority bitmap to optimize the scheduler’s search efficiency.
Removing tasks from the event waiting list
if( listLIST_ITEM_CONTAINER(&(pxTCB->xEventListItem)) != NULL) {(void)uxListRemove(&(pxTCB->xEventListItem));}
Checks if the task is waiting for an event (such as a semaphore or queue). If <span>xEventListItem</span><span> is mounted to an event list (such as </span><code><span>xSemaphore->xTasksWaitingToReceive</span><span>), then remove it.</span><span> Note that these two functions are not symmetric.</span><p><span><span><span>To summarize, the creation and startup of the first task is not fundamentally different from subsequent tasks:</span></span></span></p><p><span>First call </span><code><span>xTaskCreate()</span><span> or </span><code><span>xTaskCreateStatic()</span><span> to create the task.</span>
- First initialize the task control block (TCB), then
<span>prvInitialiseNewTask()</span><span> sets the stack, priority, task name, etc.</span> - Then add the task to the ready list using
<span>prvAddNewTaskToReadyList()</span><span>, which will:</span><span> Insert the task into the corresponding priority ready list through </span><code><span>prvAddTaskToReadyList()</span><span>. If the new task's priority is higher than the current task and the scheduler is started, mark it for scheduling (</span><code><span>taskYIELD_IF_USING_PREEMPTION()</span><span>).</span><span> Key code:</span>
// If it is the first task and the scheduler is not running, initialize global variablesif( pxCurrentTCB == NULL) { pxCurrentTCB = pxNewTCB; // Set as the current running taskprvInitialiseTaskLists(); // Initialize task lists (line number: about `1400-1500`) } // If the scheduler has started and the new task's priority is higher, trigger schedulingif( xSchedulerRunning == pdTRUE ) { if( pxNewTCB->uxPriority > pxCurrentTCB->uxPriority ) { taskYIELD_IF_USING_PREEMPTION(); } }
However, there are still some differences:
The execution of the first task depends on hardware exceptions (SVC), while subsequent tasks switch through PendSV. (When starting the first task, the OS automatically starts the scheduler and executes prvStartFirstTask and svc).taskYIELD_IF_USING_PREEMPTION() is only effective after the scheduler starts and preemption is enabled.All task creation logic is consistent, with the only differences being the scheduler state and initial context handling.
In summary, this article mainly discusses some of the task.c priority handling mechanisms, as well as task settings and processing:
Priority handling mechanism
- Efficient scheduling design using bitmap + priority list
- Quickly locating the highest priority task through uxTopReadyPriorityTask creation and startup process
- Dynamic/static allocation strategy for TCB and task stack
- Design of stack frame initialization (simulating interrupt context)
- Special startup path for the first task (via SVC exception)
- Task insertion list and deletion handling