Formalizing Freertos: First Steps: August 2009
Formalizing Freertos: First Steps: August 2009
net/publication/221136366
CITATIONS READS
18 896
3 authors, including:
Some of the authors of this publication are also working on these related projects:
Automatic generation of tests for software component developed with the B method. View project
All content following this page was uploaded by David Déharbe on 02 June 2014.
Abstract. This paper presents the current state of the formal develop-
ment of FreeRTOS, a real-time operating system. The goal of this effort
is to address a scientific challenge and is realized within the scope of the
Grand Challenge on Verified Software. The development is realized with
the B method. A model of the main functionalities of the FreeRTOS is
now available and can be a starting point to establish an agreed formal
specification of FreeRTOS that can be used by the research community.
1 Introduction
Computer Science is a fairly young discipline, but has a dramatic impact on
our society and lifestyle. The pervasive nature of computing has given rise to a
very large number of sub-areas and has fragmented the efforts of the research
community. It seems now a good time for the community to pause and reflect
to define scientific challenges that provide the opportunity for these different
sub-areas to share and combine knowledge, efforts and results to achieve ground-
breaking results that attend to existing needs of our societies.
One such initiative has been undertaken by the Brazilian Computer Soci-
ety [1] and has identified five grand challenges. One such challenge is concerned
with the technological development of quality: dependable, scalable and ubiqui-
tous systems. Formal methods have shown to be a successful approach to build
dependable systems. They are currently employed in applications requiring a
high level of safety and integrity.
The work presented in this paper represents a small step in the direction of
this challenge, but it more specifically addresses another one: the International
Grand Challenge on Verified Software [2]. One of the activities associated to this
challenge consists in setting up case studies of increasing complexity to measure
and compare existing approaches to build verified software, to identify their
weaknesses and how they can be improved. It is in that context that real-time
operating system FreeRTOS has been proposed as a case study [3].
FreeRTOS is mainly written in C, with some parts in assembly language. It
is available as a library of types and functions to build real-time, multi-tasking,
embedded software. FreeRTOS is an interesting case study for many reasons.
First, it has a large community of users and its verification would have a strong
impact. Second, although FreeRTOS has a relatively large number of functions,
its source code has medium size. Third, it is easily available, as it is open source
and it is well documented. Finally, and most importantly, modeling and verifying
the kernel of an operating system is scientifically challenging [4]: for instance,
the code includes many complex pointer-based operations.
The verification of a software aims at showing that it is free from errors (or
to find some errors). In the context of this paper, we are interested in design
errors, resulting in a discrepancy between the system behavior and its require-
ments. In the case of FreeRTOS, the requirements are distributed throughout
the documentation, are expressed in natural language and are therefore not ade-
quate as a source for a formal verification effort. The first step towards verifying
FreeRTOS is thus to build a functional specification of its intended behavior.
The goal of this paper is to present the current state of the model of FreeRTOS,
which covers a significant, and essential, subset of the available functionalities.
This model is available for researchers interested in contributing to the challenge
of the verification of FreeRTOS.
Several formalisms are candidates to specify the functional requirements of
software. In this work, we have chosen the B method [5,6]: it provides not only
a notation, but also a framework for the verification of a specification and its
refinement towards an implementation. It is similar to other well-known formal
specification notations, such as VDM [7] and Z [8]. One important criterion to
choose the B method is that it has a solid tool support for all the development
stages.
Overview of the paper. Sections 2 and 3 lay the ground for this paper by pre-
senting respectively the main features of FreeRTOS and the B method. Section 4
then enumerates and describes the functionalities of FreeRTOS that have been
selected for modeling. The resulting functional specification is presented in Sec-
tion 5. Section 6 draws conclusions and presents future work.
2 FreeRTOS
3 The B method
The B method is a model-driven design methodology to build software com-
ponents reliably, in the sense that the programs produced are guaranteed to
implement the corresponding functional specification. The B method consists in
the following steps: first, a functional specification of the requirements, or part
thereof, is developed. In B, this initial specification is called a machine. This
initial specification is then subject to different kinds of analysis, including type
checking and theorem proving, to establish that it is implementable and that all
executions are safe, in the sense that they may not reach an invalid state.
Once the specification has been built, it is used as the starting point of a series
of refinements, each refinement resulting in an artifact providing a new model of
the system. In B, refinements are usually constructed by the modeler, although
automatic refinement support is now also possible [9]. Each such refinement may
capture new functional requirements or introduce a more concrete description
of the system, by introducing an algorithmic development or an implementable
data representation. The former is called an horizontal refinement and the latter
a vertical refinement [10]. Eventually, a sequence of horizontal and then vertical
refinements shall lead to a fully algorithmic artifact, called an implementation in
the B method. Such modules may then be translated to source code for programs
in imperative languages such as C, Ada or Java.
The theoretical underpinnings of the B method are first-order logic, integer
arithmetic, set theory, substitution calculus, and refinement theory. The different
modules are written in a language called abstract machine notation (or AMN).
An AMN module is divided into sections, each section being responsible for
defining an aspect of the specification, e.g. parameters, basic types, constant
values, state variables, initial states and transitions.
As an illustration, Figure 1 contains a module, called Kernel , specifying a
system which allows to include new tasks up to a maximum number of ten. The
MACHINE section identifies the nature (a functional specification) and the
name of the module. The section SETS introduces a new type of entities, namely
TASK . At this level, no further detail is provided on how this entity is going to be
implemented. The VARIABLES section enumerates the name of the different
state variables. Here, the state is composed of a single variable, named tasks. The
INVARIANT section defines the possible values of the state variables: it defines
their types and other restrictions that shall reflect the functional requirements
of the system. Next, the INITIALISATION section provides a definition of
the set of possible initial states of the system. Last is the OPERATIONS
section, which is where the different types of events that the system may execute
and the corresponding state transitions are defined. The example has a single
operation that takes a task as parameter and adds it to tasks. In B, operations
may have parameters (passed by value), results, and may change the value of
the state variables. Operations are defined in a language called the generalized
substitution language. The constructs of this language are syntactically similar to
that of imperative programming languages, and semantically, they are predicate
transformers.
In the B method, a machine must be verified to satisfy the correctness criteria
stating that it is implementable, that all the states that are reachable are valid
states (i.e. they satisfy the condition expressed in the invariant clause), and that
all expressions appearing in the specification are well-defined. This verification
consists in checking proof obligations that are automatically generated from the
text of the machine. The proof obligations are formulas of first-order logic and
the user is responsible for proving that they are valid.
MACHINE INVARIANT task add (task ) =
Kernel tasks ∈ P(TASK )∧ PRE
SETS card(tasks) ≤ 10 task ∈ TASK ∧ task 6∈ tasks ∧
TASK INITIALISATION card(tasks) < 10
VARIABLES tasks := ∅ THEN
tasks OPERATIONS tasks := tasks ∪{task }
END
Finally, it is necessary to show that all the expressions occurring in the spec-
ification are well-defined. In the case of the example, one must show that tasks
is a finite set in every context where the expression card(tasks) is evaluated.
The proof of these verification conditions is performed either by automatic
theorem provers, or manually, by issuing commands to an interactive theorem
prover. Typically, the automatic theorem prover manages to discharge a signifi-
cant part of the verification conditions. The remaining conditions are either valid
and the user must be able to build a proof of their validity, or are not valid. In
the former case, it might happen that the user cannot build the proof, as the
prover is inherently incomplete; he has then the choice of including additional
rules or to check the condition manually and take responsibility for the verdict.
In the latter case, the specification has some error and must be corrected or the
formula cannot be proved. In the case of an erroneous specification, the infor-
mation provided by the interactive theorem prover is often helpful to locate the
error. Eventually, the user shall reach a point where all verification conditions
have been proved and the refinement process may be initiated.
Note that the functional model may also be used to derive manually an im-
plementation in a programming language. Moreover the functional specification
may also be used as a reference to generate tests [11] of the implementation.
An example of refinement is presented in Figure 2. The state variable tasks
is no longer a set of tasks but a sequence of tasks. Sequences are pre-defined
in AMN and the operators ran and → return respectively the contents of the
sequence (as a set) and addition of an element to the end of the sequence.
The B method also defines a set of verification conditions which, when proved,
guarantee that the refinement is correct with respect to the initial specification.
4.1 Tasks
Functions manipulating tasks can be divided into the functions that manage the
tasks themselves and those that control the scheduler.
The task management functions that we have modeled are task creation
(xTaskCreate), task destruction (xTaskDelete), an accessor to get the priority
of a task (uxTaskPriorityGet), task suspension (vTaskSuspend), resumption
of a suspended task, taking it to a ready state (vTaskResume), changing the
priority of a task (vTaskPrioritySet), interruption of a task for a given time
period, starting from the moment the function was called (vTaskDelay), or from
the moment the task was resumed (vTaskDelayUntil).
With respect to the scheduling aspects, we have modeled functions
to: access the currently executing task (xTaskGetCurrentTaskHandle), ac-
cess to the state of the schedule, which may be executing, suspended
or uninitialized (xTaskGetSchedulerState), get the number of existing
tasks (uxTaskGetNumberOfTasks), get the time elapsed since the scheduler
was initialized (xTaskGetTickCount), initiate the scheduler and start the so-
called idle task (vTaskStartScheduler), finalize the activities of the sched-
uler and put it back in the uninitialized state, deleting all the entities created
(vTaskEndScheduler), suspend the scheduler (vTaskSuspendAll), and resume
the scheduler (xTaskResumeAll).
Once the basic funcionalities of the system have been identified (namely, tasks
and message queues), we identified modeling steps such that they could be de-
fined in an incremental fashion:
1. Basic model: In this first step, we considered mainly the behavior of the
functions related to the state of the tasks and the transitions between such
states. The notion of priority was, at this level, left abstract. Also, the state
of the scheduler was defined as well as the concept of elementary timing
events called ticks in FreeRTOS. Message queues were also modeled, and
their size was left abstract. In order to be able to abstract notions such as
queue size and message priority, operations depending on these were defined
non-deterministically.
2. Priority: In this second step, task priority was effectively taken into ac-
count in the model. The main consequence is that functions resulting in the
scheduling of a new task were refined into more deterministic versions.
3. Mutexes: The third stage will consist in specifying this mechanism, that
allows synchronizing tasks without provoking priority inversions.
4. Queue size: The fourth step shall consist in removing non-determinism
related to queue sizes and actually specify the behavior related to the re-
quirements with respect to full or empty queues.
5. Addition of non-elementary entities: We have already mentioned that a
semaphore can be viewed as a message queue. Modeling semaphores and the
related requirements will be performed last, by using the definitions already
available for message queues.
5.1 Tasks
The machine Task defines the entities and operations related to tasks (see ex-
cerpts in Figure 1). Several modeling approaches are possible: using disjoint sets,
or using a state function mapping tasks to an enumerated set. We chose the for-
mer, as it allows expressing the invariant using simple sets and is thus easier to
analyze using one of the available theorem provers.
The state variable active indicates whether the operating system is active or
not (i.e. it is in the initialization phase of an instance of the system). Variable
tasks represents all the created tasks. In addition, running, ready, blocked and
suspended represent respectively the currently scheduled task, the set of tasks
ready to be scheduled, the set of blocked tasks and the set of suspended tasks.
Finally, variable idle represents the idle system task.
VARIABLES
active, tasks, blocked , running, ready, suspended , idle
INVARIANT
active ∈ BOOL ∧ tasks ∈ F(TASK ) ∧ running ∈ TASK ∧idle ∈ TASK
∧ blocked ∈ F(TASK ) ∧ ready ∈ F(TASK ) ∧ suspended ∈ F(TASK )
In addition, the Task machine contains basic operations that model the el-
ementary changes to the system state with respect to tasks and the scheduler.
Such operations are used to specify the functions of FreeRTOS related to tasks.
There is a total of twelve such elementary functions, from which two will be
presented here1 .
The creation of a new task is specified by the operation t create. This oper-
ation can only be applied when the scheduler has not been initialized. It takes
1
The full models and the corresponding interactive proofs are freely available at http:
//code.google.com/p/freertosb/source/browse.
as parameter the priority of the task, which will be taken into account in a re-
finement, and creates and returns a new task entity which is initially ready to
execute:
result ←− t create(priority) = THEN
PRE tasks := {task} ∪ tasks k
priority ∈ PRIORITY ∧ ready := {task} ∪ ready k
active = FALSE result := task
THEN END
ANY task WHERE END;
task ∈ TASK ∧ task 6∈ tasks
The second operation shown here is t startScheduler that specifies the state
transition when the scheduler is activated. This corresponds to the change from
the initialisation phase to the execution phase of a FreeRTOS application. In
that phase, the system task idle is created and the scheduler chooses a task for
execution. Again, recall that the priority has not been taken into account at
this level of abstraction and is the subject of a further refinement. It is here left
non-deterministic:
t startScheduler = tasks := {idle task } ∪ tasks k
PRE idle := idle task k
active = FALSE ANY task WHERE
THEN task ∈ ready ∪{idle task }
active := TRUE k THEN
blocked , suspended := ∅, ∅ k running := task k
ANY idle task WHERE ready := (ready ∪{idle task }) − {task }
idle task ∈ TASK ∧ END
idle task 6∈ tasks END
THEN END;
The behavior of the task-related functions of FreeRTOS has then been mod-
eled using these elementary operations. Here, we only show the function specifi-
cation of the function xTaskCreate, that provides the task creation functionality
in FreeRTOS. This specification uses the previously presented operation t create.
The return value of the function xTaskCreate indicates if the operation succeeded
or failed (for instance due to a lack of available memory) and a handler to the
new task is asssigned to to parameter handler , passed by reference.
result, handle ←− CHOICE
xTaskCreate( code, name, handle ←
stackSize, params, t create(priority) k
priority) = result := pdPASS
PRE OR
code ∈ TASK CODE ∧ result := errMEMORY k
name ∈ NAME ∧ handle :∈ TASK
stackSize ∈ NATURAL∧ END
params ⊂ PARAMETER∧ END
priority ∈ PRIORITY ∧
scheduler = NOT STARTED∧
THEN
Note that in the notation of the B method, parameters are always passed by
value, and operations may return multiple values. In the C implementation of
FreeRTOS, operations such as xTaskCreate return more than one value and use
pointer typed parameters to store these additional results. Wherever this is the
case, the functional model includes an additional return parameter.
The Queue machine also contains operations that define the basic function-
ality to manipulate the message queues. There is a total of six such operations.
For instance, the operation sendItem defines the inclusion of a new message item
at position pos of queue, and destination task :
sendItem(queue, item, task , pos) = THEN
PRE items(queue) :=
queue ∈ queues∧ items(queue) ∪ {item} k
item ∈ ITEM ∧ receiving(queue) :=
task ∈ TASK ∧ receiving(queue) − {task }
pos ∈ COPY POSITION ∧ END
task ∈ receiving(queue)
The functional requirements state that the running task should have a priority
greater or equal than all the ready tasks. In order to take into account such
requirement, the invariant needs to be strengthened to define task priorities and
to specify the desired property. From the methodological viewpoint, starting
from the first version of the Task machine (described in Section 5.1), we can
either define a new version, or create a refinement. We chose the latter solution
and we describe it now.
We defined a refinement module called Task r . In it, we defined a type
PRIORITY that represents tasks priorities. The state variable prio maps each
task to its priority and the invariant states that when the scheduler has been
initialised, no ready task has a priority greater than the running task. We also
include restrictions on the priority of the idle task, which is the lowest possible
priority.
CONSTANTS INVARIANT
MAX P , IDLE P prio ∈ TASK → + PRIORITY ∧
PROPERTIES dom(prio) = tasks ∧
PRIORITY = 0..(MAX P − 1 )∧ (active = TRUE ⇒
MAX P > 0 ∧ IDLE P = 0 prio(idle) = IDLE P ∧
VARIABLES ∀t.(t ∈ ready ⇒ prio(t) ≤ prio(running))∧
prio ∀t.(t ∈ ready ⇒ IDLE P ≤ prio(t)))
Most of the operations of the Task machine involve defining a new running
task, and these operations need to be refined to maintain the new invariant. In
order to simplify the definition of these refined operations, a scheduling function
was introduced. It takes as input a set of tasks and a function mapping tasks to
their priorities, and it returns those given tasks that have the highest priority:
CONSTANTS
schedule p
PROPERTIES
schedule p : (F(TASK ) × (TASK → + PRIORITY )) →
+ F(TASK )∧
schedule p = λ(tasks, prio)•
(tasks : F(TASK ) ∧ prio : TASK →+ PRIORITY ∧ tasks 6= ∅ ∧ tasks ⊆ dom(prio)
| tasks ∩ prio −1 (max(prio[tasks]))))
One of the main features of the B method is the tool support for project man-
agement, syntactic verification, and semantic analysis of the produced artifacts.
In particular, the semantic analysis produces proof obligations the verification
of which guarantees: (1) all expressions appearing in the text of the different
artifacts are well-defined, (2) the logic consistency of the specification and its
refinements. The development environment thus includes support for the con-
struction of the proofs, by providing a number of theorem provers. However, due
to the incomplete nature of the specification logic, as well as the computational
complexity of finding proofs, human intervention is needed to establish part of
the proofs. This is a time-consuming activity that pays off in two ways. First,
when confronted with a proof obligation that is not valid, the developer has
access to the context where such proof obligation was generated and has clues
as where the artifact needs to be corrected. Second, when all proof obligations
have been successfully validated, then the user has a very strong confidence in
its models.
To give an idea of the effort needed to establish the correctness of the de-
velopment, Table 1 provides the number of proof obligations generated for each
artifact2 . This table does not include however the effort needed to reach consis-
tent models, as several iterations were needed to produce a correct definition of
the invariant and of the operations.
This paper presented the first steps of a formal modeling, using the B method,
of a significant part of the real-time operating system FreeRTOS. This model
provides a functional specification of the operations related to task management
and message queues. This effort was initiated in response to the challenge set by
Jim Woodcock to the Brazilian community on Formal Methods [3] to contribute
with this case study to the Verified Software Repository [13], as part of the Inter-
national Grand Challenge on Verified Software. Thus, a first contribution of this
work is the execution of a case study for the development of a verified model of
a moderately complex software library. We have already extended the presented
model to specify semaphores and related functions; next, we will include the def-
inition of functional specifications for mutexes and refine the scheduling policy
to take into account fairness requirements.
2
Professionals using the B method estimate that a seasoned practitioner averages
sixteen interactive proofs per day.
Module Size Proof obligations Interactive
Operations Lines W.D. Corr. Total proofs
Config 0 89 0 0 0 0
Types 0 103 1 1 2 1
Scheduler 5 90 0 0 0 0
Task 12 467 1 219 220 28
Queue 7 231 12 33 45 0
FreeRTOSBasic 19 562 37 46 83 2
FreeRTOS 19 562 43 3 49 0
Task r 12 432 42 100 142 18
Total 55 1974 136 402 538 49
Table 1. The table presents, for each module, the number of operations defined in
the module, the total number of lines (including comments), the number of proof
obligations (well-definedness lemas, correctness theorems, and total), and the number of
interactive proofs required to establish the correctness theorems. Most of our interactive
proofs have fewer than 10 steps. In the one case (lowering the priority of the running
task below that of at least one ready task), we needed more than a hundred steps. We
do not claim that we were able to find the shortest proofs.
References