17 - Internal Structure of FreeRTOS PDF
17 - Internal Structure of FreeRTOS PDF
CONTENTS
17.1 Task Scheduler/Context Switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375
17.2 Synchronization Primitives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
17.3 Porting FreeRTOS to a New Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
17.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400
375
CurrentTCB
TCB of
running task
TCB of ready
3 task
ReadyTasksLists[]
(one for each priority level)
0
Each element contains a
pointer to the TCB of a
1 task ready for execution
All lists are of type xList;
their head also stores the
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
...
number of elements in the list List elements are of
type xListItem
2
1
TCB of
suspended
Each element points to the TCB of task
a task that is finished, but whose Each element contains a pointer to
memory has not been freed yet the TCB of a task suspended for an
undetermined number of ticks
TasksWaitingTermination * TCB of
2 terminating
task
FIGURE 17.1
Simplified view of the data structures used by the FreeRTOS scheduler. The
elements marked with ∗ are optional; they may or may not be present, de-
pending on the FreeRTOS configuration.
count of how many elements currently are in the list, to speed up common
operations like checking whether a list is empty or not.
In Figure 17.1, an xList is represented by a sequence of ordered grey boxes
connected by arrows. The leftmost box is the list header, and the number
within it indicates how many elements currently belong to the list. The next
boxes represent list elements; each of them points to a TCB, although, for
clarity, not all of them are shown in the figure.
It should also be noted that the actual implementation of an xList is
slightly more complicated than what has been described so far. That is, it
actually is a circular list and incorporates a guard element to delimit its end.
However, those additional details are mainly related to compactness and effi-
ciency, and do not significantly change the underlying idea.
The main components of the scheduler data structures are
• The CurrentTCB pointer designates the TCB of the running task. There is
only one instance of this pointer, because FreeRTOS only supports single-
processor systems, where at the most, one process can be running at a
time.
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
• A task may become ready for execution while the scheduler is sus-
pended and the ReadyTaskLists[] cannot be manipulated directly. In
this case, the task is temporarily “parked,” by linking its TCB to the
PendingReadyList list. The elements of this list are moved into the proper
position of ReadyTaskLists[], depending on their priority, as soon as the
scheduler becomes operational again and before any scheduling decision is
taken.
• The SuspendedTaskList holds the TCBs of all tasks that are currently
suspended, that is, those that are waiting for an undetermined number of
clock ticks. This list is needed only if FreeRTOS has been configured to
support task suspension. Such a configuration is needed to support infinite
timeouts in interprocess communication as well as explicit task suspension.
• Last, the TasksWaitingTermination list collects all tasks that are finished
TABLE 17.1
Contents of a FreeRTOS Task Control Block (TCB)
Field Purpose Optional
pcTaskName[] Human-readable task name -
uxTCBNumber Task identification number ∗
uxPriority Active priority -
uxBasePriority Baseline priority ∗
pxStack Lowest task stack address -
pxEndOfStack Highest task stack address ∗
pxTopOfStack Current top of the task stack -
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
but that have not yet been removed from the system because the memory
associated with them has not yet been freed. For a variety of reasons, this
last operation in the lifetime of a task is accomplished by the idle task,
running at the minimum priority in the system. Hence, finished tasks may
spend a nonnegligible amount of time in this list if the system is busy.
As said before, in FreeRTOS each task is represented by a data structure
called TCB, containing a number of fields. Some of them are always present;
others are optional, in order to save space, because they are needed only for
certain operating system configurations. As shown in Table 17.1, the TCB
contains many of the elements that were discussed in the previous chapters,
namely,
• pcTaskName holds the human-readable task name as a character string.
This information is not directly used in any way by the scheduler but may
be useful to identify the task for debugging purposes.
• Its machine-readable counterpart is the uxTCBNumber. This field is present
only if the FreeRTOS runtime trace facility has been configured, and is a
unique number that represents the task.
• uxPriority represents the current, or active, priority of the task used by
the scheduling algorithm.
• When the system has been configured to support mutual exclusion
semaphores with priority inheritance, the previous field is complemented
by uxBasePriority, which represents the baseline priority of the task.
• pxStack points to the area of memory used to store the task stack. Re-
gardless of the direction in which the stack grows on a certain architecture
(toward higher or lower addresses), this field always points to the base of
the area, that is, its lowest address.
• For architectures in which task stacks grow upward, that is, toward higher
addresses, pxEndOfStack points to the highest, legal stack address. This
information is required to perform stack occupancy checks.
• pxTopOfStack points to the top of the task stack. This information is used
for two distinct, but related, purposes:
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
1. When the task context is saved, most of the task state informa-
tion is pushed onto its stack; the pointer is used to later retrieve
this information.
2. The value of the stack pointer itself is part of the task state, and
hence, the pointer is also used to restore the task stack pointer
to the right value during context restoration.
• The xGenericListItem field is used to link the task control block to one
of the lists managed by the scheduler, depending on the task state. In
particular,
• The interrupt disable nesting level indicates how many nested critical
regions protected by disabling interrupts are currently in effect for a
given task. It is used to properly reenable interrupts when the outer-
most critical region concludes, that is, when the nesting level goes back
to zero. In some architectures, this datum is held within the TCB in the
uxCriticalNesting field.
• The ulRunTimeCounter is present in the TCB only when FreeRTOS has
been configured to collect runtime statistics. It represents how much time
has been spent running the task from its creation. It should be noted that
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
its value is not derived from the operating system tick but from a separate,
architecture-dependent timer. Hence, its resolution and unit of measure-
ment may not be the same.
• pxTaskTag holds a pointer that can be uniquely associated with the task
by the user. It is useful, for example, to store a pointer to a data structure
holding additional user-defined, per-task information besides what is held
in the TCB itself.
• If the architecture supports memory protection among tasks, the
xMPUSettings points to an architecture-dependent data structure. Its con-
tents are used during context switch to reprogram the Memory Protection
Unit (MPU) according to the requirements of the task to be executed next.
An interesting omission in the FreeRTOS TCB is the task state, that is, its
location in the process state diagram. However, this information can easily
be inferred, by looking at which lists the TCB is currently linked to, through
xGenericListItem and xEventListItem. The TCB of the running task can
be reached directly through the CurrentTCB pointer.
Another thing that is seemingly missing is the processor state information
pertaining to the task, that is, the value of the program counter, general
registers, and so on. In FreeRTOS, this information is pushed onto the task
stack when its context is saved. Therefore, even if it is not stored in the TCB
directly, it can still be retrieved indirectly because the TCB does contain a
pointer to the top of the stack, pxTopOfStack. This is the situation shown for
task B in Figure 17.2.
We can now start discussing how FreeRTOS implements a context switch
in practice. In particular, let us assume that task A is currently running and
the operating system is about to switch to task B, which is ready for execution.
The status of the main data structures involved in this context switch
before it begins is shown in Figure 17.2. Since task A is being executed, its
processor state (depicted as a dark grey block in the figure) is actually within
the CPU itself. The CPU stack pointer points somewhere within task A’s stack
and delimits the portion of stack currently in use by the task (the light grey
zone) from the free stack space (the white zone). For the sake of the example,
stacks are assumed to grow downward in the figure.
While task A is running, the stack pointer value evolves according to what
downward
All stacks
Processor state of
Processor state of
grow
task B, except SP
task A
Free stack space
SP
Free stack space
CPU
CurrentTCB
TopOfStack
TCB of task A
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
TopOfStack
TCB of task B
ReadyTaskLists[]
xList element
FIGURE 17.2
State of the main FreeRTOS data structures involved in a context switch when
it is executing task A and is about to switch to task B.
downward
All stacks
Processor state of
Scheduler state
grow
Processor state of task B, except SP
task A, except SP
SP Free stack space Free stack space
CPU
CurrentTCB
TopOfStack
TCB of task A
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
TopOfStack
TCB of task B
ReadyTaskLists[]
xList element
FIGURE 17.3
State of the main FreeRTOS data structures involved in a context switch when
the context of task A has been saved and the scheduler is about to run.
pxTopOfStack field points to the information just saved. In this way, the saved
task context is made accessible from the TCB itself.
At this point, the processor stack pointer is also switched to a dedicated
kernel stack, and hence, the processor can safely be used to execute the
scheduling algorithm without fear of damaging the context of any task in
the system. The final result of the scheduling algorithm is an update of the
scheduler data structures, namely, to the CurrentTCB pointer.
In particular, as shown in Figure 17.4, if we suppose that the scheduling al-
gorithm chooses B as the next task to be executed, it updates the CurrentTCB
pointer so that it refers to the TCB of task B.
It should also be noted that immediately before the context switch takes
place, further updates to the data structures may be necessary, depending on
the reason of the context switch itself. The figure refers to the simplest case,
in which a context switch is needed due to the readiness of a higher-priority
task (B) and the currently executing task (A) is still ready for execution.
In this case, the TCB of A should, in principle, be linked back to one of
the ReadyTaskLists[] according to its priority. Actually, as an optimization,
FreeRTOS never removes a task from its ready list when it becomes running,
so this operation is unnecessary. More complex scenarios involve intertask
synchronization or communication and will be discussed in Section 17.2.
The last step of the context switch is to restore the context of task B to
resume its execution. The final state of the system is depicted in Figure 17.5.
After context restoration, the processor state of task B has been loaded into
the processor, and the processor stack pointer has been brought back exactly
where it was when the context of B was saved. Indeed, by comparing Fig-
downward
All stacks
Processor state of
Scheduler state
grow
Processor state of task B, except SP
task A, except SP
SP Free stack space Free stack space
CPU
CurrentTCB
TopOfStack
TCB of task A
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
TopOfStack
TCB of task B
ReadyTaskLists[]
xList element
FIGURE 17.4
State of the main FreeRTOS data structures involved in a context switch when
the scheduling algorithm has chosen B as the next task to be executed.
ures 17.2 and 17.5, it can be seen that they are exactly equivalent, with the
roles of tasks A and B interchanged.
downward
All stacks
Processor state of
grow
Processor state of
task B task A, except SP
Free stack space
SP Free stack space
CPU
CurrentTCB
TopOfStack
TCB of task A
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
TopOfStack
TCB of task B
ReadyTaskLists[]
xList element
FIGURE 17.5
State of the main FreeRTOS data structures involved in a context switch after
the context of task B has been restored.
• pcHead and pcTail delimit the message storage zone associated with the
queue. In particular, pcHead points to the base, that is, the lowest address
of the memory area, and pcTail points to one byte more than the highest
address of the area.
A separate message storage zone is used, instead of embedding it into the
xQUEUE, so that the main xQUEUE data structure always has the same length
and layout regardless of how many messages can be stored into it, and their
size.
• pcReadFrom and pcWriteTo delineate the full portion of the message stor-
age zone, which currently contains messages, and separate it from the free
message storage space. It should be remarked that the meaning of the
pcReadFrom differs from the meaning of pcWriteTo in a slightly counter-
intuitive way: while pcWriteTo points to the first free slot in the message
storage zone, pcReadFrom points to the element that was last read from the
queue. As a consequence, the oldest message in the queue is not pointed
directly by pcReadFrom but resides one element beyond that.
These pointers are used by tasks to know where the oldest message cur-
rently stored in the queue starts (at the location pointed by pcReadFrom
plus the item size) and where the next message must be written (at the
location pointed by pcWriteTo).
Overall, the message storage zone is managed as a circular buffer to avoid
moving messages from one location to another within the storage area when
performing a send or receive operation. Hence, both pointers wrap back to
pcHead whenever they reach pcTail.
TABLE 17.2
Contents of a FreeRTOS message queue data structure (xQUEUE)
Field Purpose
uxLength Maximum queue capacity (# of messages)
uxItemSize Message size in bytes
pcHead Lowest address of message storage zone
pcTail Highest address of message storage zone +1
pcReadFrom Address of oldest full element -uxItemSize
pcWriteTo Address of next free element
uxMessagesWaiting # of messages currently in the queue
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
Tail
WriteTo
addresses
Higher
ReadFrom
Head ItemSize
TasksWaitingToSend 0
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
TasksWaitingToReceive 0
3 messages currently in
xQUEUE the queue,
uxMessagesWaiting=3
Tail
WriteTo
ReadFrom
Head ItemSize
TasksWaitingToSend 0
TasksWaitingToReceive 2
No messages currently in
xQUEUE the queue,
uxMessagesWaiting=0
FIGURE 17.6
Simplified depiction of a FreeRTOS message queue.
other tasks nor ISRs are allowed to operate on the queue because interrupts
are disabled in order to guarantee its consistency.
• If the queue is empty, the task exits from the “strong” critical section just
discussed and enters a “weaker” critical section, protected by disabling the
operating system scheduler. Within this critical section, the running task
cannot be preempted but interrupts are enabled again, and hence, the task
locks the queue against further updates from ISRs by means of the fields
xRxLock and xTxLock.
At first sight, having two distinct critical sections arranged in this way may
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
• If the timeout of the receive operation has already expired at this point, the
queue is unlocked and the operation is concluded with an error indication.
• If the timeout of the receive operation is not yet expired (or no timeout was
specified) and the queue is still empty—some messages could have been
sent to the queue between the two critical sections—the task is blocked
by removing it from the element of ReadyTaskLists[] it belongs to and
then linked to either one of the delayed task lists (if the task specified
a finite timeout for the receive operation) or to the suspended task list
(if no timeout was specified). In addition, the task is also linked to the
xTasksWaitingToReceive list associated to the queue.
• At this point, the queue is unlocked and the scheduler is reenabled. If the
current task was blocked in the previous step, this also forces a context
switch to occur.
Moreover, unlocking the queue may also wake up some tasks blocked on
either xTasksWaitingToReceive or xTasksWaitingToSend. This is neces-
sary because ISRs are allowed to send and receive messages from the queue
while it is locked, but they are not allowed to update the waiting task lists.
This update is therefore delayed and performed as soon as the queue is
unlocked.
The whole sequence outlined above is repeated to retry the receive operation
whenever the task is awakened. This may happen either because the receive
timeout expired or more messages were sent to the queue. In the first case,
the next receive attempt will necessarily fail because the timeout expiration
will definitely be detected.
However, in the second case, the receive operation is not necessarily bound
to succeed on the next attempt because other, higher-priority tasks may
“steal” all the messages sent to the queue before the current task had a chance
TABLE 17.3
xQUEUE fields that have a different meaning when the message queue supports
a mutual exclusion semaphore with priority inheritance
Original name New name Purpose
pcHead uxQueueType Queue type
pcTail pxMutexHolder Task owning the mutex
pcReadFrom uxRecursiveCallCount Critical region nesting counter
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
of running. In this case, the task will find that the queue is empty and block
again.
All other communication and synchronization objects provided by FreeR-
TOS are directly layered on message queues. For example, a counting
semaphore with an initial value of x and a maximum value of y is implemented
as a message queue that can hold at most y zero-byte messages, with x dummy
messages stored into the queue during initialization. Binary semaphores are
handled as a special case of counting semaphores, with y = 1 and either x = 0
or x = 1.
Mutual exclusion semaphores are an important exception because FreeR-
TOS implements the priority inheritance algorithm for them and supports
the recursive lock and unlock feature for them. As a consequence, the message
queue mechanism just described cannot be applied as it is. Just to make an
example, task priorities are obeyed but never modified by the message queue
operations discussed so far.
On the one hand, to implement the priority inheritance algorithm, more
information is needed than it is provided by the xQUEUE data structure dis-
cussed so far. On the other hand, several fields in the same data structure
are unneeded when it is used to support a mutual exclusion semaphore rather
than a true message queue.
Hence, as shown in Table 17.3, several xQUEUE fields get a different name
and meaning in this case, such as:
• As seen in Table 17.2, for regular message queues, the pcHead field holds
the lowest address of the message storage zone associated with the queue.
However, as discussed before, message queues used to build semaphores
hold zero-size messages, and thus, no memory at all is actually needed to
store them; only their count is important.
For this reason, the pcHead field—now renamed uxQueueType—is initial-
ized to a NULL pointer to indicate that the message queue is indeed a mutual
exclusion semaphore.
currently owns the mutex. In this context, owning the mutex means that
the task is currently within a critical region controlled by that semaphore.
data types required by FreeRTOS into the corresponding data types supported
by the compiler:
1 # define portCHAR char
2 # define portFLOAT float
3 # define portDOUBLE double
4 # define portLONG long
5 # define portSHORT short
6 # define portSTACK_TYPE unsigned portLONG
7 # define portBASE_TYPE long
For example, the code excerpt shown above states that, for the Cortex-
M3, the FreeRTOS data type portCHAR (an 8-bit character) corresponds to
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
the C language data type char. Even more importantly, it also states that
portBASE TYPE, the most “natural” integer data type of the architecture,
which usually corresponds to a machine word, is a long integer. Similarly,
the portSTACK TYPE is used as the base type for the task stacks, and its cor-
rect definition is crucial for correct stack alignment.
Then, the data type used by FreeRTOS to represent time, expressed in
ticks, must be defined. This data type is called portTickType and it is defined
as follows:
1 # if ( c o n f i g U S E _ 1 6 _ B I T _ T I C K S == 1 )
2 typedef unsigned p o r t S H O R T p o r t T i c k T y p e;
3 # define p o r t M A X _ D E L A Y ( p o r t T i c k T y p e ) 0 xffff
4 # else
5 typedef unsigned portLONG p o r t T i c k T y p e;
6 # define p o r t M A X _ D E L A Y ( p o r t T i c k T y p e ) 0 x f f f f f f f f
7 # endif
The first definition states that, on this architecture, stacks grow downward.
The macro can also be defined as ( +1 ) to denote that they grow upward
instead. The second definition determines the length of a tick in milliseconds,
starting from the configuration option configTICK RATE HZ. The last one ex-
presses the strongest memory alignment constraint of the architecture for any
kind of object in bytes. In this case, the value 8 means that a memory address
that is a multiple of 8 bytes is good for storing any kind of object.
The next definition concerns portYIELD, the function or macro invoked by
FreeRTOS to perform a context switch from the current task to a new one
chosen by the scheduling algorithm. In this case, this activity is delegated to
the architecture-dependent function vPortYieldFromISR:
1 # define p o r t S E T _ I N T E R R U P T _ M A S K () \
2 __asm volatile \
3 ( \
4 " mov r0 , %0 \n" \
5 " msr basepri , r0 \n" \
6 :: " i " ( c o n f i g M A X _ S Y S C A L L _ I N T E R R U P T _ P R I O R I T Y ): " r0 " \
7 )
8
9 # define p o r t C L E A R _ I N T E R R U P T _ M A S K () \
10 __asm volatile \
11 ( \
12 " mov r0 , #0 \n" \
13 " msr basepri , r0 \n" \
14 ::: " r0 " \
15 )
16
17 # define p o r t S E T _ I N T E R R U P T _ M A S K _ F R O M _ I S R () \
18 0; p o r t S E T _ I N T E R R U P T _ M A S K ()
19
20 # define p o r t C L E A R _ I N T E R R U P T _ M A S K _ F R O M _ I S R( x ) \
21 p o r t C L E A R _ I N T E R R U P T _ M A S K ();( void ) x
22
23 extern void v P o r t E n t e r C r i t i c a l( void );
24 extern void v P o r t E x i t C r i t i c a l( void );
25
26 # define p o r t D I S A B L E _ I N T E R R U P T S () p o r t S E T _ I N T E R R U P T _ M A S K()
27 # define p o r t E N A B L E _ I N T E R R U P T S () p o r t C L E A R _ I N T E R R U P T _ M A S K()
28 # define p o r t E N T E R _ C R I T I C A L() v P o r t E n t e r C r i t i c a l ()
29 # define p o r t E X I T _ C R I T I C A L() v P o r t E x i t C r i t i c a l ()
The first two definitions are not used directly by FreeRTOS; rather, they
act as a building block for the following ones. portSET INTERRUPT MASK
unconditionally disables all interrupt sources that may interact with
FreeRTOS by setting the basepri processor register to the value
configMAX SYSCALL INTERRUPT PRIORITY.
This is accomplished with the help of an assembly language insert (in-
troduced by the GCC-specific keyword asm) because the basepri register
can be accessed only by means of the specialized msr instruction instead of a
standard mov.
The effect of the assignment is that all interrupt requests with a priority
lower than or equal to either the specified value or the current execution prior-
ity of the processor are not honored immediately but stay pending. Interrupt
requests with a higher priority are still handled normally, with the constraint
that they must not invoke any FreeRTOS function.
The portCLEAR INTERRUPT MASK macro does the opposite: it uncondition-
ally reenables all interrupt sources by resetting the basepri processor regis-
ter to zero, that is, the lowest possible priority. As a side effect, the proces-
sor will also handle immediately any interrupt request that was left pending
previously.
The two macros just mentioned are used directly to implement
portDISABLE INTERRUPT and portENABLE INTERRUPT, invoked by FreeRTOS
to disable and enable interrupts, respectively, from a task context. On the other
hand, FreeRTOS invokes two other macros, portSET INTERRUPT MASK FROM
ISR and portCLEAR INTERRUPT MASK FROM ISR, to do the same from an in-
terrupt service routine, as this distinction is needed on some architectures.
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
does not have any effect until after the instruction that follows it, whereas
the instruction that disables interrupts takes effect immediately.
Hence, on those architectures, enabling interrupts and disabling them
again in the next instruction—as it happens with the STI/CLI sequence in
the IntelR
64 and IA-32 architecture—prevents any interrupt requests from
actually being accepted by the processor. The most straightforward solution
is to insert something between the interrupt enable and disable instructions.
This something must not modify the machine state in any way but still count
as (at least) one instruction, and this is exactly what portNOP does.
Besides what has been discussed so far, portmacro.h may also contain
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
additional macro, data type, and function definitions that are not required by
FreeRTOS but are used by other architecture-dependent modules.
The portmacro.h header only contains data type and macro defini-
tions. We have seen that, in some cases, those macro definitions map func-
tion names used by FreeRTOS, like portYIELD, into architecture-dependent
function names, like vPortYieldFromISR. We shall therefore discuss how
the architecture-dependent functions described so far are actually imple-
mented, along with other functions not mentioned so far but still required
by FreeRTOS.
The implementation is done in one or more architecture-dependent mod-
ules. For the Cortex-M3 architecture, all of them are in the port.c source
file. The first couple of functions to be discussed implements (possibly nested)
critical regions by disabling interrupts:
1 static unsigned p o r t B A S E _ T Y P E u x C r i t i c a l N e s t i n g = 0 x a a a a a a a a;
2
3 void v P o r t E n t e r C r i t i c a l( void )
4 {
5 p o r t D I S A B L E _ I N T E R R U P T S ();
6 u x C r i t i c a l N e s t i n g ++;
7 }
8
9 void v P o r t E x i t C r i t i c a l( void )
10 {
11 u x C r i t i c a l Ne sti ng - -;
12 if ( u x C r i t i c a l N e s t i n g == 0 )
13 {
14 p o r t E N A B L E _ I N T E R R U P T S();
15 }
16 }
zero, that is, the calling task is about to exit from the outermost critical re-
gion. Incrementing and decrementing uxCriticalNesting does not pose any
concurrency issue on a single-processor system because these operations are
always performed with interrupts disabled.
It should also be noted that, although, in principle, uxCriticalNesting
should be part of each task context—because it holds per-task information—it
is not necessary to save it during a context switch. In fact, due to the way the
Cortex-M3 port has been designed, a context switch never occurs unless the
critical region nesting level of the current task is zero. This property implies
that the nesting level of the task targeted by the context switch must be zero,
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
too, because its context has been saved exactly in the same way. Then it is
assured that any context switch always saves and restores a critical nesting
level of zero, making this action redundant.
The next two functions found in port.c are used to request a processor
rescheduling (also called a yield) and perform it, respectively as follows:
1 # define p o r t N V I C _ I N T _ C T R L ( ( volatile unsigned long *) 0 x e 0 0 0 e d 0 4 )
2 # define p o r t N V I C _ P E N D S V S E T 0 x10000000
3
4 void v P o r t Y i e l d F r o m I S R( void )
5 {
6 *( p o r t N V I C _ I N T _ C T R L) = p o r t N V I C _ P E N D S V S E T;
7 }
8
9 void x P o r t P e n d S V H a n d l e r( void )
10 {
11 __asm volatile
12 (
13 " mrs r0 , psp \n"
14 " \n"
15 " ldr r3 , p x C u r r e n t T C B C o n s t \n"
16 " ldr r2 , [ r3 ] \n"
17 " \n"
18 " stmdb r0 ! , { r4 - r11 } \n"
19 " str r0 , [ r2 ] \n"
20 " \n"
21 " stmdb sp ! , { r3 , r14 } \n"
22 " mov r0 , %0 \n"
23 " msr basepri , r0 \n"
24 " bl v T a s k S w i t c h C o n t e x t \n"
25 " mov r0 , #0 \n"
26 " msr basepri , r0 \n"
27 " ldmia sp ! , { r3 , r14 } \n"
28 " \n"
29 " ldr r1 , [ r3 ] \n"
30 " ldr r0 , [ r1 ] \n"
31 " ldmia r0 ! , { r4 - r11 } \n"
32 " msr psp , r0 \n"
33 " bx r14 \n"
34 " \n"
35 " . align 2 \n"
36 " p x C u r r e n t T C B C o n s t: . word p x C u r r e n t T C B \n"
37 :: " i " ( c o n f i g M A X _ S Y S C A L L _ I N T E R R U P T _ P R I O R I T Y)
38 );
39 }
xPSR
PC
Saved by the context
Free stack space LR switch routine
R0-R3, R12
R4-R11
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
CurrentTCB
SP
TopOfStack
PSP
TCB of current
CPU task
FIGURE 17.7
Detailed stack layout during a FreeRTOS context switch on the ARM Cortex-
M3 architecture.
controller by means of its interrupt control register, portNVIC INT CTRL. The
priority assigned to this interrupt request is the lowest among all interrupt
sources. Thus, the corresponding exception handler is not necessarily executed
immediately.
When the processor eventually honors the interrupt request, it automat-
ically saves part of the execution context onto the task stack, namely, the
program status register (xPSR), the program counter and the link register (PC
and LR), as well as several other registers (R0 to R3 and R12). Then it switches
to a dedicated operating system stack and starts executing the exception han-
dling code, xPortPendSVHandler.
The handler first retrieves the task stack pointer PSP and stores it in the
R0 register (line 13). This does not clobber the task context because R0 has
already been saved onto the stack by hardware. Then, it puts into R2 a pointer
to the current TCB taken from the global variable pxCurrentTCB (lines 15–
16).
The handler is now ready to finish the context save initiated by hardware
by pushing onto the task stack registers R4 through R11 (line 18). At last,
the task stack pointer in R0 is stored into the first field of the TCB, that is,
the TopOfStack field (line 19). At this point, the stack layout is as shown in
Figure 17.7, which represents the specialization of Figure 17.3 for the Cortex-
M3 architecture. In particular,
• the stack pointer currently used by the processor, SP, points to the oper-
ating system stack;
• the PSP register points to where the top of the task stack was after excep-
tion entry, that is, below the part of task context saved automatically by
hardware;
• the TopOfStack field of the current task TCB points to the top of the task
stack after the context save has been concluded.
of this function by setting the processor base priority mask basepri appro-
priately. The main effect of vTaskSwitchContext is to update CurrentTCB so
that it points to the TCB of the task to be executed next.
Hence, vTaskSwitchContext dereferences CurrentTCB again (line 29) to
get a pointer to the new TCB. From there, it extracts the TopOfStack field
and stores it into R0 (line 30). Using R0 as a stack pointer, the function pops
registers R4 through R11, that is, the part of context previously saved by
software, from the stack of the new task (line 31). After that, the updated
stack pointer is stored into the task stack pointer register PSP (line 32).
The last step of context restoration is performed by asking the hardware
to restore the remaining part of the task context, which was automatically
saved on exception entry. This is done by the bx instruction at line 33. The
last action also restores the task PC, and thus execution continues from where
it was left off when the context was saved.
The next function to be discussed is pxPortInitialiseStack, invoked by
FreeRTOS when it is creating a new task. It should initialize the new task stack
so that its layout is identical to the layout of Figure 17.7, that is, the stack
layout after a context save operation. In this way, task execution can be started
in the most natural way, that is, by simply restoring its execution context.
It takes as arguments the task stack pointer pxTopOfStack, the address from
which task execution should begin pxCode, and a pointer to the task parameter
block pvParameters. The return value of the function is the new value of the
task pointer after the context has been saved.
1 # define p o r t I N I T I A L _ X P S R ( 0 x01000000 )
2
3 p o r t S T A C K _ T Y P E * p x P o r t I n i t i a l i s e S t a c k(
4 p o r t S T A C K _ T Y P E * pxTopOfStack ,
5 p d T A S K _ C O D E pxCode , void * p v P a r a m e t e r s )
6 {
7 pxTopOfStack - -;
8 * p x T o p O f S t a c k = p o r t I N I T I A L _ X P S R; /∗ xPSR ∗/
9 pxTopOfStack - -;
10 * p x T o p O f S t a c k = ( p o r t S T A C K _ T Y P E ) pxCode ; /∗ PC ∗/
11 pxTopOfStack - -;
12 * p x T o p O f S t a c k = 0; /∗ LR ∗/
13 p x T o p O f S t a c k -= 5; /∗ R12 , R3, R2 and R1. ∗/
14 * p x T o p O f S t a c k = ( p o r t S T A C K _ T Y P E ) p v P a r a m e t e r s; /∗ R0 ∗/
15 p x T o p O f S t a c k -= 8; /∗ R11 , R10 , R9, R8, R7, R6, R5 and R4. ∗/
16
17 return p x T o p O f S t a c k;
18 }
By comparing the listing with Figure 17.7, it can be seen that the initial
context is set up as follows:
• The initial Processor Status Register xPSR is the value of the macro
portINITIAL XPSR.
• The Program Counter PC comes from the pxCode argument.
• The Link Register LR is set to 0 so that any attempt of the task to return
from its main function causes a jump to that address and can be caught.
• Register R0, which holds the first (and only) argument of the task entry
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
0xE000ED08. This is the address of the VTOR register that points to the base
of the exception table.
It should be noted that the MSP (Main Stack Pointer) register being dis-
cussed here is not the same as the PSP (Process Stack Pointer) register we
talked about earlier. The Cortex-M3 architecture, in fact, specifies two dis-
tinct stack pointers. With FreeRTOS the PSP is used when a task is running
whereas the MSP is dedicated to exception handling. The processor switches
between them automatically as its operating mode changes.
The initial context restoration is performed by means of a synchronous
software interrupt request made by the svc instruction (line 8).
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
23
24 ulDummy = p o r t S E T _ I N T E R R U P T _ M A S K _ F R O M _ I S R ();
25 {
26 v T a s k I n c r e m e n t T i c k();
27 }
28 p o r t C L E A R _ I N T E R R U P T _ M A S K _ F R O M _ I S R( ulDummy );
29 }
The next two functions manage the interval timer internal to Cortex-M3
processors, also known as SYSTICK:
• The function prvSetupTimerInterrupt programs the timer to generate
periodic interrupt requests at the rate specified by the configTICK RATE HZ
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017
• It starts the first stack, previously selected by the upper operating system
layers, by calling vPortStartFirstTask.
17.4 Summary
Looking at how a real-time operating system, like FreeRTOS, really works
inside is useful for at least two reasons: