0% found this document useful (0 votes)
446 views26 pages

17 - Internal Structure of FreeRTOS PDF

Uploaded by

Bengt Hörberg
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
446 views26 pages

17 - Internal Structure of FreeRTOS PDF

Uploaded by

Bengt Hörberg
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 26

17

Internal Structure of FreeRTOS

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

This chapter presents the internal structure of FreeRTOS [13]. It is hence


related to Chapter 8, which describes the features and the applications pro-
gramming interface of the same real-time operating system. It is also useful
to read it together with Chapter 18, which discusses the internal structure of
a couple of Linux real-time extensions.
The comparison shows how the target hardware architecture, design prin-
ciple, backward compatibility, standard conformance, as well as the number
and degree of sophistication of the features to be provided are all aspects
that have significant effects on the internal structure of an operating system,
leading to very different solutions to the same problems.

17.1 Task Scheduler/Context Switch


The FreeRTOS scheduler is extremely simple but also very effective from a
real-time execution standpoint. It is a fixed-priority scheduler, and hence, it
can directly support both the Rate Monotonic (RM) and Deadline Monotonic
(DM) scheduling algorithms discussed in the previous chapters.
Figure 17.1 depicts the main data structures handled by the FreeRTOS
scheduler. Most of them are built upon a simpler data structure called xList,
a doubly linked list that implements an ordered queue. Individual xList items
hold a pointer to another data structure that represents the information as-
sociated with the item.
Even if, in principle, the data structure linked to an xList item can be
chosen at will, in most cases it is a Task Control Block (TCB). This is the
main data structure used by the operating system to represent a task and
to store any information associated with it. The list header also contains a

375

© 2012 by Taylor & Francis Group, LLC


376 Real-Time Embedded Systems—Open-Source Operating Systems Perspective

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

PendingReadyList TCB of ready


task
2

Each element contains a pointer to the


TCB of a task that became ready when the
scheduler was suspended
DelayedTasksList and
OverflowDelayedTaskList
TCB of
delayed task
1

Each element contains a


0
SuspendedTasksList * pointer to the TCB of a
task delayed up to a
maximum number of ticks

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

© 2012 by Taylor & Francis Group, LLC


Internal Structure of FreeRTOS 377

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

• ReadyTaskLists[] is an array of xList data structures, one for each pri-


ority level configured in the system. The xList corresponding to a certain
priority links together all tasks that have that priority and are ready for ex-
ecution. This is the main data structure consulted by the scheduler when it
is about to run a task. It should be noted that, for convenience, the running
task is linked to the ready list corresponding to its priority, too.

• 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.

• Both the DelayedTaskList and the OverflowDelayedTaskList contain


tasks that are delayed, that is, they are waiting until some instant in the
future, expressed in ticks. Both are ordered by increasing time so that the
tasks nearer to the front of the lists have their timeouts expiring first. The
need for two lists stems from the fact that the operating system tick counter
is an unsigned integer with a limited number of bits and will necessarily
overflow with time.
Therefore, DelayedTaskList contains the tasks with a timeout within
the current tick counter span before the next overflow, whereas
OverflowDelayedTaskList holds the tasks with a timeout beyond the
next tick counter overflow. Those timeouts belong to the future even if
their numeric value is lower than the current value of the tick counter, so
putting them in the same list as the others would lead to confusion.

• 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

© 2012 by Taylor & Francis Group, LLC


378 Real-Time Embedded Systems—Open-Source Operating Systems Perspective

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

xGenericListItem Link to the scheduler’s lists -


xEventListItem Link to an event wait list -
uxCriticalNesting Interrupt disable nesting level ∗
ulRunTimeCounter CPU time consumed ∗
pxTaskTag User-defined, per-task pointer ∗
xMPUSettings Memory protection information ∗

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

© 2012 by Taylor & Francis Group, LLC


Internal Structure of FreeRTOS 379

(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,

1. it links the task to one of the ReadyTaskLists[] when the task


is ready or running;
2. it links the task to the TasksWaitingTermination list when the
task is finished but its memory has not been freed yet;
3. it links the task to either the DelayedTaskList or the
OverflowDelayedTaskList when the task is being delayed for a
certain number of ticks;
4. it links the task to the SuspendedTaskList when the task is
suspended for an undetermined number of ticks.

• The xEventListItem field is used in a similar way when a task is waiting


for an event to occur on an intertask synchronization/communication ob-
ject and must hence be linked to two distinct lists at the same time. For
example, when a task waits to receive a message from an empty message
queue, its xEventListItem field links it to one of the waiting lists associ-
ated to the message queue, the one that groups all tasks that are waiting
for a message to arrive.
At the same time, its xGenericListItem field links it 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 xEventListItem field is also used to temporarily link the
task to the PendingReadyList. This is done when a task becomes ready
while the scheduler is suspended and is hence impossible to put it back into
one of the ReadyTaskLists[] directly.

© 2012 by Taylor & Francis Group, LLC


380 Real-Time Embedded Systems—Open-Source Operating Systems Perspective

• 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

© 2012 by Taylor & Francis Group, LLC


Internal Structure of FreeRTOS 381

Stack of task A Stack of task B

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.

the task itself is executing. On most architectures, if task A performs a func-


tion call, the function arguments and program counter are pushed onto the
stack, and the stack pointer is moved down. When the function returns to the
caller, this information is popped from the stack and the stack pointer goes
up to its original place.
The TCB of the running task, A in this case, can be reached from the
CurrentTCB pointer. Since the TCB does not hold a valid stack state at this
moment, its pxTopOfStack field has no particular meaning and is shown as a
black dot.
The situation is different for what concerns task B because it is indeed
ready for execution but not running. First of all, its TCB is linked to one
of the ReadyTaskLists[], the one pertaining to its priority, by means of an
xList element. Since B is not being executed at the moment, its processor
state does not reside in the CPU, as it was for task A. Instead, most of it
has been pushed onto its own stack when its context was last saved. The only
exception is the stack pointer, which is stored in B’s TCB instead, namely, in
the pxTopOfStack field.
Let us now assume that the operating system is about to reevaluate the
scheduling algorithm. This happens for a variety of reasons already discussed
in Chapter 12. For example, a task with a priority higher than the running
task may become ready for execution.
This event must not disrupt the execution of the running task, A. There-
fore, as shown in Figure 17.3, the operating system must first of all save the
context of task A onto its own stack and then update its TCB so that the

© 2012 by Taylor & Francis Group, LLC


382 Real-Time Embedded Systems—Open-Source Operating Systems Perspective

Dedicated scheduler Stack of task A Stack of task B


stack (not shown)

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-

© 2012 by Taylor & Francis Group, LLC


Internal Structure of FreeRTOS 383

Dedicated scheduler Stack of task A Stack of task B


stack (not shown)

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.

17.2 Synchronization Primitives


The most basic intertask communication and synchronization object provided
by FreeRTOS is the message queue. All other objects, for instance semaphores,
are built upon it. A message queue is represented by the xQUEUE data type,
linked to a separate message storage zone. Table 17.2 gives a detailed list of
the data structure contents, while Figure 17.6 shows a simplified summary of
the state of a message queue in two distinct scenarios:

1. when it contains 3 messages out of a maximum capacity of 6, and


there are no tasks waiting to send or receive messages;
2. when it is completely empty and there are two tasks waiting to
receive a message from it.
All xQUEUE fields are always present regardless of the FreeRTOS configuration.
Their purpose is

• Fields uxLength and uxItemSize indicate the “geometry” of the message


queue, that is, what is the maximum number of messages that it can hold,
and the size of each message in bytes, respectively.

© 2012 by Taylor & Francis Group, LLC


384 Real-Time Embedded Systems—Open-Source Operating Systems Perspective

Stack of task A Stack of task B

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.

© 2012 by Taylor & Francis Group, LLC


Internal Structure of FreeRTOS 385

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

xTasksWaitingToSend List of tasks waiting to send a message


xTasksWaitingToReceive List of tasks waiting to receive a message
xRxLock Send queue lock flag and message counter
xTxLock Receive queue lock flag and message counter

• uxMessagesWaiting counts how many messages are currently stored in the


queue.
• The xTasksWaitingToSend field is an xList that links together all the
tasks waiting to send a message into the queue when that operation cannot
be performed immediately because the queue is completely full. The tasks
are arranged in priority order so that the highest-priority task is awakened
first when a free message slot becomes available.
• The xTasksWaitingToReceive field is an xList that has the same purpose
as xTasksWaitingToSend but for tasks waiting to receive a message from
an empty queue.
• In FreeRTOS, Interrupt Service Routines (ISRs) can use message queues
to send messages to regular tasks, and receive messages from them, by
means of special, nonblocking functions. In some cases—namely, if the
ISR is executed while a task is working on the xQUEUE—these functions
are still allowed to send and receive messages, but must not update the
waiting task lists associated to the queue, xTasksWaitingToSend and
xTasksWaitingToReceive. This is necessary to ensure that the data struc-
tures just mentioned remain consistent.
When this is the case, the fields xRxLock (for the receive part) and xTxLock
(for the send part) are set to a special value to indicate that the queue is
“locked.” When the queue is locked, the same fields are also used to count
how many messages have been received from, and sent to, the queue by an
ISR without updating the waiting task lists. The value is used, as soon as
the queue is unlocked, to bring the queue data structure back to consistency.
As an example, let us see what happens when the running task invokes a
receive operation on an empty message queue. The following sequence of events
takes place:

© 2012 by Taylor & Francis Group, LLC


386 Real-Time Embedded Systems—Open-Source Operating Systems Perspective

Queue holding 3 Message storage area


messages out of 6
maximum

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

Empty queue with 2 Message storage area


tasks waiting to receive

Tail

WriteTo

ReadFrom

Head ItemSize

TasksWaitingToSend 0

TasksWaitingToReceive 2
No messages currently in
xQUEUE the queue,
uxMessagesWaiting=0

TCB of waiting TCB of waiting


task task

FIGURE 17.6
Simplified depiction of a FreeRTOS message queue.

• Within a critical section, protected by disabling interrupts, the task checks


if the value of the uxMessagesWaiting field is greater than zero. If this
is the case, at least one message is already stored in the queue, and the
task can retrieve it immediately without blocking. During the check, neither

© 2012 by Taylor & Francis Group, LLC


Internal Structure of FreeRTOS 387

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

look like a useless complication. However, as it will become clearer from


the following description, the operations contained in the weaker critical
section require a relatively long execution time. Hence, especially in a real-
time system, it is important to keep interrupts enabled while they are
carried out, even at the expense of making the code more involved.

• 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

© 2012 by Taylor & Francis Group, LLC


388 Real-Time Embedded Systems—Open-Source Operating Systems Perspective

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.

• Likely, the pcTail field—now called pxMutexHolder—is used to store a


TCB pointer. The pointer may be either NULL, to signify that the mutual
exclusion semaphore is currently free, or refer to the TCB of the task that

© 2012 by Taylor & Francis Group, LLC


Internal Structure of FreeRTOS 389

currently owns the mutex. In this context, owning the mutex means that
the task is currently within a critical region controlled by that semaphore.

• Moreover, for a recursive mutual exclusion semaphore, it is necessary to


hold a count of how many nested critical regions, controlled by a certain
semaphore, have been entered by the task owning that semaphore but have
not been exited yet. This count is stored in the uxRecursiveCallCount
field, which now takes the place of the pcReadFrom pointer.
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017

17.3 Porting FreeRTOS to a New Architecture


To enhance their portability to different processor architectures, software de-
velopment systems (also known as toolchains), and hardware platforms, most
modern operating systems, including FreeRTOS, specify a well-defined in-
terface between the operating system modules that do not depend on the
architecture, and the architecture-dependent modules, often called Hardware
Abstraction Layer (HAL).
As their name suggests, those modules must be rewritten when the op-
erating system is ported to a new architecture, and must take care of all its
peculiarities. Moreover, they often include driver support for a limited set of
devices needed by the operating system itself and the language support library.
For instance, FreeRTOS needs a periodic timer interrupt to work properly;
moreover, an Input/Output console can be very useful when applications are
tested and debugged.
We will now discuss the main contents of the FreeRTOS architecture-
dependent modules, referring to the ARM Cortex-M3 port of FreeRTOS when
concrete examples and code excerpts are needed. More information about the
architecture can be found in References [6, 7]. This family of microcontrollers
has been chosen because it is a typical representative of contemporary, low-
cost components for embedded applications and, at the same time, it is simple
enough so that the reader can gain a general understanding of architecture-
dependent modules without studying the architecture in detail beforehand.
In most cases, the bulk of the port to a new architecture is done by defining
a set of C preprocessor macros in the architecture-dependent file portmacro.h.
During the build, the contents of this file are incorporated by means of a condi-
tional #include directive contained in the FreeRTOS header file portable.h.
In turn, the conditional inclusion is controlled by an architecture and
toolchain-dependent preprocessor symbol, GCC ARMCM3, for the Cortex-M3 and
the GNU toolchain. The final result is that that the correct header for the ar-
chitecture being targeted by the build and the toolchain being used is included.
The first thing to be found in portmacro.h is a mapping of some abstract

© 2012 by Taylor & Francis Group, LLC


390 Real-Time Embedded Systems—Open-Source Operating Systems Perspective

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

As can be seen, the definition is both architecture dependent (through


the macros portSHORT and portLONG) and configuration dependent (through
the configuration option configUSE 16 BIT TICKS). Since the definition of
portTickType affects the maximum relative delay in ticks that can be rep-
resented by the operating system and used by applications, the fragment of
code also defines the portMAX DELAY macro accordingly.
More architecture-dependent information is conveyed by means of the fol-
lowing, additional definitions:
1 # define p o r t S T A C K _ G R O W T H ( -1 )
2 # define p o r t T I C K _ R A T E _ M S ( ( p o r t T i c k T y p e ) 1000 / c o n f i g T I C K _ R A T E _ H Z )
3 # define p o r t B Y T E _ A L I G N M E N T 8

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:

© 2012 by Taylor & Francis Group, LLC


Internal Structure of FreeRTOS 391

1 extern void v P o r t Y i e l d F r o m I S R( void );


2 # define p o r t Y I E L D() v P o r t Y i e l d F r o m I S R()

For some architectures, the code to be executed for a context switch is


not always the same, as in this case, but depends on the execution context
it is invoked from. In this case, the additional macros portYIELD FROM ISR
and portYIELD WITHIN API must be defined. They are used to ask for a con-
text switch from an ISR or the FreeRTOS applications programming interface
(API) functions, respectively.
The last set of architecture-dependent definitions found in portmacro.h
are a bit more involved because they are concerned with interrupt handling:
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017

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-

© 2012 by Taylor & Francis Group, LLC


392 Real-Time Embedded Systems—Open-Source Operating Systems Perspective

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

On the Cortex-M3 architecture, this is unnecessary, and therefore,


the same code is used in both cases. The rather counterintuitive def-
initions found at lines 17–21 of the listing stem from the fact that
portSET INTERRUPT MASK FROM ISR is expected to return a value that will
be passed to the matching portCLEAR INTERRUPT MASK FROM ISR as an ar-
gument. This simplifies their implementation on some architectures be-
cause it makes possible the passing of some information from one macro
to the other, but it is unnecessary for the Cortex-M3. As a conse-
quence, portSET INTERRUPT MASK FROM ISR returns a dummy zero value, and
portCLEAR INTERRUPT MASK FROM ISR ignores its argument.
The last two functions related to interrupt handling, to be defined here, are
portENTER CRITICAL and portEXIT CRITICAL. They are used within FreeR-
TOS to delimit very short critical regions of code that are executed in a task
context, and must be protected by disabling interrupts.
Since these critical regions can be nested into each other, it is
not enough to map them directly into portDISABLE INTERRUPTS and
portENABLE INTERRUPTS. If this were the case, interrupts would be incor-
rectly reenabled at the end of the innermost nested critical region instead
of the outermost one. Hence, a slightly more complex approach is in order.
For the Cortex-M3, the actual implementation is delegated to the functions
vPortEnterCritical and vPortExitCritical. They are defined in another
architecture-dependent module.
Last, portmacro.h contains an empty definition for the macro portNOP, a
macro that must “do nothing.” For the Cortex-M3 architecture, it is in fact
defined to be empty:
1 # define portNOP ()

Contrary to appearance, portNOP is not as useless as it seems to be. Its


typical use within FreeRTOS, and other real-time operating systems as well, is
to split up critical regions executed with interrupt disabled into smaller pieces
when their execution time as a single unit would introduce an unacceptable
latency in responding to interrupt requests.
To alleviate this issue, FreeRTOS temporarily reenables interrupts within
the critical region (in a place where it is safe to do so), invokes portNOP,
and disables them again. However, on some architectures—most notably the
Intel
R
64 and IA-32 architecture [45]—the instruction that enables interrupts

© 2012 by Taylor & Francis Group, LLC


Internal Structure of FreeRTOS 393

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 }

The global variable uxCriticalNesting contains the critical region nest-


ing level of the current task. Its initial value 0xaaaaaaaa is invalid, to catch
errors during startup. It is set to zero, its proper value, when the operating
system is about to begin the execution of the first task.
The two functions are rather simple: vPortEnterCritical disables in-
terrupts by means of the portDISABLE INTERRUPTS macro discussed before.
Then, it increments the critical region nesting counter because one more crit-
ical region has just been entered. The function vPortExitCritical, called
at the end of a critical region, first decrements the nesting counter and then
reenables interrupts by calling portENABLE INTERRUPTS only if the count is

© 2012 by Taylor & Francis Group, LLC


394 Real-Time Embedded Systems—Open-Source Operating Systems Perspective

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 }

On the Cortex-M3, rescheduling is performed by an exception handler


triggered by a software interrupt request, called PendSV. Hence, the function
vPortYieldFromISR simply sends a PendSV interrupt request to the interrupt

© 2012 by Taylor & Francis Group, LLC


Internal Structure of FreeRTOS 395

Saved by the processor on


Current task stack exception entry
RTOS stack

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-

© 2012 by Taylor & Francis Group, LLC


396 Real-Time Embedded Systems—Open-Source Operating Systems Perspective

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.

Going back to the listing of xPortPendSVHandler, the function now in-


vokes the operating system scheduling algorithm, that is, the function
vTaskSwitchContext (lines 21–27). To avoid race conditions, interrupt
sources that may interact with FreeRTOS are disabled during the execution
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017

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 }

© 2012 by Taylor & Francis Group, LLC


Internal Structure of FreeRTOS 397

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

function, points to the task parameter block pvParameters.


• The other registers are not initialized.
We have already examined the architecture-dependent functions that switch
the processor from one task to another. Starting the very first task is somewhat
an exception to this general behavior.
1 void v P o r t S t a r t F i r s t T a s k( void )
2 {
3 __asm volatile (
4 " ldr r0 , =0 x E 0 0 0 E D 0 8 \n"
5 " ldr r0 , [ r0 ] \n"
6 " ldr r0 , [ r0 ] \n"
7 " msr msp , r0 \n"
8 " svc 0 \n"
9 );
10 }
11
12 void v P o r t S V C H a n d l e r( void )
13 {
14 __asm volatile (
15 " ldr r3 , p x C u r r e n t T C B C o n s t 2 \n"
16 " ldr r1 , [ r3 ] \n"
17 " ldr r0 , [ r1 ] \n"
18 " ldmia r0 ! , { r4 - r11 } \n"
19 " msr psp , r0 \n"
20 " mov r0 , #0 \n"
21 " msr basepri , r0 \n"
22 " orr r14 , #0 xd \n"
23 " bx r14 \n"
24 " \n"
25 " . align 2 \n"
26 " p x C u r r e n t T C B C o n s t 2: . word p x C u r r e n t T C B \n"
27 );
28 }

The function vPortStartFirstTask is called by FreeRTOS to start the


very first task after setting CurrentTCB to point to its TCB. It first fetches
the operating system stack address from the first element of the exception
vector table and stores it into MSP (lines 4–7).
In the Cortex-M3 architecture, the first 32-bit element of the exception
vector table is not used as a real exception vector. It holds instead the ini-
tial value automatically loaded into the processor’s stack pointer upon reset.
FreeRTOS picks it up as the top of its own stack. The actual assembly lan-
guage code to retrieve this value consists of a double dereference at address

© 2012 by Taylor & Francis Group, LLC


398 Real-Time Embedded Systems—Open-Source Operating Systems Perspective

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

This software interrupt request is handled by the exception handler


vPortSVCHandler; its code is very similar to xPortPendSVHandler, but it
only restores the context of the new task pointed by CurrentTCB without
saving the context of the previous task beforehand. This is correct because
there is no previous task at all. As before, the processor base priority mask
basepri is reset to zero (lines 20–21) to enable all interrupt sources as soon
as the exception handling function ends.
Before returning from the exception with a bx instruction, the contents of
the link register LR (a synonym of R14) are modified (line 22) to ensure that the
processor returns to the so-called “thread mode,” regardless of what its mode
was. When handling an exception, the Cortex-M3 processor automatically
enters “handler mode” and starts using the dedicated operating system stack
mentioned earlier.
When the execution of a task is resumed, it is therefore necessary to restore
the state from that task’s stack and keep using the same task stack to con-
tinue with the execution. This is exactly what the exception return instruction
does when it goes back to thread mode. A similar, automatic processor mode
switch for exception handling is supported by most other modern processors,
too, although the exact names given to the various execution modes may be
different.
1 # define portNVIC_SYSTICK_LOAD ( ( volatile unsigned long *) 0 x e 0 0 0 e 0 1 4 )
2 # define portNVIC_SYSTICK_CTRL ( ( volatile unsigned long *) 0 x e 0 0 0 e 0 1 0 )
3 # define portNVIC_SYSTICK_CLK 0 x00000004
4 # define portNVIC_SYSTICK_INT 0 x00000002
5 # define portNVIC_SYSTICK_ENABLE 0 x00000001
6
7 void p r v S e t u p T i m e r I n t e r r u p t( void )
8 {
9 *( p o r t N V I C _ S Y S T I C K _ L O A D) =
10 ( c o n f i g C P U _ C L O C K _ H Z / c o n f i g T I C K _ R A T E _ H Z ) - 1 UL ;
11 *( p o r t N V I C _ S Y S T I C K _ C T R L) =
12 portNVIC_SYSTICK_CLK | portNVIC_SYSTICK_INT
13 | p o r t N V I C _ S Y S T I C K _ E N A B L E;
14 }
15
16 void x P o r t S y s T i c k H a n d l e r( void )
17 {
18 unsigned long ulDummy ;
19
20 # if c o n f i g U S E _ P R E E M P T I O N == 1
21 *( 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;
22 # endif

© 2012 by Taylor & Francis Group, LLC


Internal Structure of FreeRTOS 399

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

configuration variable and starts it.

• The function xPortSysTickHandler handles the interrupt requests coming


from the timer:

1. If the FreeRTOS scheduler has been configured to support pre-


emption, the function asks for a rescheduling to be performed as
soon as possible (lines 20–22). Unsurprisingly, the code is iden-
tical to the body of vPortYieldFromISR.
2. The FreeRTOS function vTaskIncrementTick is called, within a
critical region (lines 24–28). It takes care of all aspects related to
the tick timer, such as, for example, updating the current time,
checking whether some task timeouts have expired, and so on.

1 # define portNVIC_SYSPRI2 ( ( volatile unsigned long *) 0 x e 0 0 0 e d 2 0 )


2 # define portNVIC_PENDSV_PRI \
3 ( ( ( unsigned long ) c o n f i g K E R N E L _ I N T E R R U P T _ P R I O R I T Y ) << 16 )
4 # define portNVIC_SYSTICK_PRI \
5 ( ( ( unsigned long ) c o n f i g K E R N E L _ I N T E R R U P T _ P R I O R I T Y ) << 24 )
6
7 p o r t B A S E _ T Y P E x P o r t S t a r t S c h e d u l e r( void )
8 {
9 *( p o r t N V I C _ S Y S P R I 2) |= p o r t N V I C _ P E N D S V _ P R I;
10 *( p o r t N V I C _ S Y S P R I 2) |= p o r t N V I C _ S Y S T I C K _ P R I;
11
12 p r v S e t u p T i m e r I n t e r r u p t ();
13
14 u x C r i t i c a l N e s t i n g = 0;
15
16 v P o r t S t a r t F i r s t T a s k ();
17 return 0;
18 }

The very last function to be discussed here is xPortStartScheduler.


It is called during FreeRTOS startup and, as its name suggests, must per-
form all architecture-dependent activities related to starting the scheduler. In
particular,
• It sets the priority of the two interrupt sources used by FreeRTOS
(the PendSV software interrupt and the SYSTICK timer) to the value
configKERNEL INTERRUPT PRIORITY taken from the FreeRTOS configura-
tion (lines 9–10).

© 2012 by Taylor & Francis Group, LLC


400 Real-Time Embedded Systems—Open-Source Operating Systems Perspective

• It initializes the uxCriticalNesting variable to zero. As previously dis-


cussed, this value indicates that no critical regions, based on disabling
interrupts, are currently in effect.

• It starts the first stack, previously selected by the upper operating system
layers, by calling vPortStartFirstTask.

Under normal conditions, and as long as the operating system is running,


vPortStartFirstTask never returns to the caller, and xPortStartScheduler
is not expected to return, either, unless FreeRTOS is stopped completely by
calling vTaskEndScheduler. However, this capability is not currently sup-
Downloaded by [Lund University Libraries (master)] at 04:48 31 August 2017

ported by the current version of the Cortex-M3 port.

17.4 Summary
Looking at how a real-time operating system, like FreeRTOS, really works
inside is useful for at least two reasons:

1. To refine concepts such as concurrency, interprocess communica-


tion, mutual exclusion, and synchronization by filling the gap be-
tween their abstract definition and their concrete implementation.
These additional details may seem tedious at a first sight but are
nonetheless necessary for software developers to fully grasp them.
2. To better tell apart the general behavior of operating system prim-
itives from the peculiarities and limitations of a specific operating
system and API. In this way, programmers are faster and more pro-
ficient when they go from one operating system to another, and it
is easier for them to produce portable code.
In this chapter we briefly explored the supporting data structures used by
the FreeRTOS task scheduler and its main interprocess communication mech-
anism, the message queue. Then we showed that, at a closer look, even the
real-world implementation of a task context switch—arguably one of the most
secluded operating system mechanisms—is not as exotic as it may seem when
the concept is contemplated from far away.
A short discussion of how a simple operating system can be ported from
one architecture to another, and what an HAL must contain, concluded the
chapter. Due to lack of space, the presentation is far from being exhaustive
but can be used as a starting point for readers willing to adapt an operating
system to the hardware architecture they are working on.

© 2012 by Taylor & Francis Group, LLC

You might also like