The basic unit of work in FreeRTOS is the task. FreeRTOS uses a Task Control Block (TCB) to represent each task.
Task Control Block (TCB)
The TCB is defined in tasks.c like this:
typedef struct tskTaskControlBlock {
volatile portSTACK_TYPE *pxTopOfStack; /* Points to the location of
the last item placed on
the tasks stack. THIS
MUST BE THE FIRST MEMBER
OF THE STRUCT. */
xListItem xGenericListItem; /* List item used to place
the TCB in ready and
blocked queues. */
xListItem xEventListItem; /* List item used to place
the TCB in event lists.*/
unsigned portBASE_TYPE uxPriority; /* The priority of the task
where 0 is the lowest
priority. */
portSTACK_TYPE *pxStack; /* Points to the start of
the stack. */
signed char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* Descriptive name given
to the task when created.
Facilitates debugging
only. */
#if ( portSTACK_GROWTH > 0 )
portSTACK_TYPE *pxEndOfStack; /* Used for stack overflow
checking on architectures
where the stack grows up
from low memory. */
#endif
#if ( configUSE_MUTEXES == 1 )
unsigned portBASE_TYPE uxBasePriority; /* The priority last
assigned to the task -
used by the priority
inheritance mechanism. */
#endif
} tskTCB;- The TCB stores the address of the stack start address in
pxStackand the current top of stack inpxTopOfStack. - It also stores a pointer to the end of the stack in
pxEndOfStackto check for stack overflow if the stack grows “up” to higher addresses. If the stack grows “down” to lower addresses then stack overflow is checked by comparing the current top of stack against the start of stack memory inpxStack. - The TCB stores the initial priority of the task in
uxPriorityanduxBasePriority. A task is given a priority when it is created, and a task’s priority can be changed. If FreeRTOS implements priority inheritance then it usesuxBasePriorityto remember the original priority while the task is temporarily elevated to the “inherited” priority. (See the discussion about mutexes below for more on priority inheritance.)
Each task has two list items for use in FreeRTOS’s various scheduling lists. When a task is inserted into a list FreeRTOS doesn’t insert a pointer directly to the TCB. Instead, it inserts a pointer to either the TCB’s xGenericListItem or xEventListItem. These xListItem variables let the FreeRTOS lists be smarter than if they merely held a pointer to the TCB.
The task can be in one of four states:
You might expect each task to have a variable that tells FreeRTOS what state it’s in, but it doesn’t. Instead, FreeRTOS tracks task state implicitly by putting tasks in the appropriate list: ready list, suspended list, etc. The presence of a task in a particular list indicates the task’s state. As a task changes from one state to another, FreeRTOS simply moves it from one list to another.
Task Setup
We’ve already touched on how a task is selected and scheduled with the pxReadyTasksLists array.
A task is created when the xTaskCreate() function is called. FreeRTOS uses a newly allocated TCB object to store the name, priority, and other details for a task, then allocates the amount of stack the user requests (assuming there’s enough memory available) and remembers the start of the stack memory in TCB’s pxStack member.
The stack is initialized to look as if the new task is already running and was interrupted by a context switch. This way the scheduler can treat newly created tasks exactly the same way as it treats tasks that have been running for a while; the scheduler doesn’t need any special case code for handling new tasks.
The way that a task’s stack is made to look like it was interrupted by a context switch depends on the architecture FreeRTOS is running on, but this ARM Cortex-M3 processor’s implementation is a good example:
unsigned int *pxPortInitialiseStack( unsigned int *pxTopOfStack,
pdTASK_CODE pxCode,
void *pvParameters )
{
/* Simulate the stack frame as it would be created by a context switch interrupt. */
pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on
entry/exit of interrupts. */
*pxTopOfStack = portINITIAL_XPSR; /* xPSR */
pxTopOfStack--;
*pxTopOfStack = ( portSTACK_TYPE ) pxCode; /* PC */
pxTopOfStack--;
*pxTopOfStack = 0; /* LR */
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( portSTACK_TYPE ) pvParameters; /* R0 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}- The ARM Cortex-M3 processor pushes registers on the stack when a task is interrupted.
pxPortInitialiseStack()modifies the stack to look like the registers were pushed even though the task hasn’t actually started running yet.- Known values are stored to the stack for the ARM registers
xPSR, PC, LR,andR0. - The remaining registers
R1-R12get stack space allocated for them by decrementing the top of stack pointer, but no specific data is stored in the stack for those registers. The ARM architecture says that those registers are undefined at reset, so a (non-buggy) program will not rely on a known value.
After the stack is prepared, the task is almost ready to run. First though, FreeRTOS disables interrupts: We’re about to start mucking with the ready lists and other scheduler structures and we don’t want anyone else changing them underneath us.
If this is the first task to ever be created, FreeRTOS initializes the scheduler’s task lists. FreeRTOS’s scheduler has an array of ready lists, pxReadyTasksLists[], which has one ready list for each possible priority level. FreeRTOS also has a few other lists for tracking tasks that have been suspended, killed, and delayed. These are all initialized now as well.
After any first-time initialization is done, the new task is added to the ready list at its specified priority level. Interrupts are re-enabled and new task creation is complete.