Quantum Embedded Programming
Quantum Embedded Programming
Quantum Embedded Programming
in C/C++
Quantum Programmming
for Embedded Systems
Designations used by companies to distinguish their products are often claimed as trademarks. In
all instances where CMP Books is aware of a trademark claim, the product name appears in initial
capital letters, in all capital letters, or in accordance with the vendor’s capitalization preference.
Readers should contact the appropriate companies for more complete information on trademarks
and trademark registrations. All trademarks and registered trademarks in this book are the prop-
erty of their respective holders.
Copyright © 2002 by CMP Books, except where noted otherwise. Published by CMP Books, CMP
Media LLC. All rights reserved. Printed in the United States of America. No part of this publica-
tion may be reproduced or distributed in any form or by any means, or stored in a database or
retrieval system, without the prior written permission of the publisher.
The programs in this book are presented for instructional value. The programs have been carefully
tested, but are not guaranteed for any particular purpose. The publisher does not offer any war-
ranties and does not guarantee the accuracy, adequacy, or completeness of any information herein
and is not responsible for any errors or omissions. The publisher assumes no liability for damages
resulting from the use of the information in this book or for any infringement of the intellectual
property rights of third parties that would result from the use of this information.
Distributed to the book trade in the U.S. by: Distributed in Canada by:
Publishers Group West Jaguar Book Group
1700 Fourth Street 100 Armstrong Avenue
Berkeley, CA 94710 Georgetown, Ontario M6K 3E7 Canada
1-800-788-3123 905-877-4483
For individual orders and for information on special discounts for quantity orders, please contact:
CMP Books Distribution Center, 6600 Silacci Way, Gilroy, CA 95020
Tel: 1-800-500-6875 or 408-848-3854; fax: 408-848-5784
email: cmp@rushorder.com; Web: www.cmpbooks.com
02 03 04 05 06 5 4 3 2
ISBN: 1-57820-110-1
Table of Contents
Preface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii
P ART I STATECHARTS . . . . . . . . . . . . . . . . . . . . . . . . . 1
iii
iv Table of Contents
Bibliography . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .365
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .371
Almost two decades ago, David Harel invented statecharts as a powerful way to
describe complex reactive (event-driven) systems [Harel 87]. Subsequently, state-
charts have gained almost universal acceptance as a superior formalism and have
been adopted as components of many software methodologies, most notably as part
of the Unified Modeling Language (UML). Nevertheless, the use of statecharts in
everyday programming has grown slowly. Among the many reasons, the most impor-
tant is that statecharts have always been taught as the use of a particular tool, rather
than the way of design.
This heavy reliance on tools has affected the software community in three ways.
First, the aggressive marketing rhetoric of tool vendors has set unrealistically high
expectations for purely visual programming and fully automatic code generation.
Second, the rhetoric of the argument for automatic code synthesis depreciated the
role of manual coding. Finally, the accidental association between statecharts and
CASE (computer-aided software engineering) tools gave rise to a misconception that
the more advanced UML concepts, such as statecharts, are only viable in conjunction
with sophisticated code-synthesizing tools.
The reality is that CASE tools haven’t made manual coding go away. Even the
best-in-class code-synthesizing tools can generate only a fraction of the software (the
so-called housekeeping code [Douglass 99]). The difficult, application-specific code
vii
viii Preface
still must be written explicitly (although it is typically entered through the dialog
boxes of a tool rather than typed into a programming editor). This also means that
the models are not purely visual, but a mixture of diagrams and textual information
(mostly snippets of code in a concrete programming language).
Moreover, for many projects, a design automation tool is not the best solution.
The fundamental problem, as always, is the cost versus the return. Even if you ignore
the dollar cost of the tool, you must ask whether the benefits outweigh the com-
pounded complexity of the problem and the tool. The complete cost function must
also include training and adaptation of the existing infrastructure to the tool (e.g.,
the compiler/linker/debugger tool chain, the host and target operating systems, the
directory structure and file names, version control, and the software build process).
After weighing all the pros and cons and struggling with a tool for a while, many
teams notice that they spend more time fighting the tool than solving problems. For
many developers, the tool simply can’t pull its own weight and ends up as shelfware
or a not-so-simple drawing tool.
Mission
My primary mission in this book is to offer a simple, lightweight alternative to a
design automation tool by providing concrete, efficient, and proven implementations
of statecharts that every practitioner reasonably proficient in C or C++ can start
using within days.
To achieve these goals, I describe the major components of every typical code-syn-
thesizing tool.
• The techniques needed to implement and use UML statecharts — the main con-
structive element in the UML specification (presented in Part I).
• A real-time application framework — a complete software infrastructure for exe-
cuting statecharts, tailored to embedded real-time systems and based on active
objects and asynchronous event passing (presented in Part II).
At first glance, the approach can be classified as a set of common elaborative tech-
niques for implementing UML models. Even as such, it spares many practitioners
from reinventing the wheel. In this book, I present ready-to-use, generic, and efficient
elements that you can use to implement and execute hierarchical state machines; gen-
erate, queue, and dispatch events; integrate state machines with real-time operating
systems (RTOSs); and much more. These software elements vary little from system
to system but are hard to find in the literature. It’s even harder to make them work
well together. The value of this book is similar to that of a multivitamin pill: in one
fell swoop (or a few chapters in this case), you get all the necessary ingredients, well
balanced and complementing each other. If you use this book only in this manner,
my most important goal is already accomplished.
Why Quantum Programming? ix
1. For example, photons mediate the electromagnetic force, gluons the strong force, and bosons W±
and Zo the weak interactions.
x Preface
QP, which I discuss in Chapter 1. In this sense, QP builds on top of OOP, comple-
ments it, and extends it into the domain of reactive systems.
Most of the concepts that form QP are not new, but, rather, draw on a broad range
of long-known techniques and methodologies from programming and other disci-
plines such as quantum field theory. Most inspiring to me was the real-time object-ori-
ented modeling (ROOM) method described by Bran Selic and colleagues [Selic+ 94].
Specifically, the real-time framework, or Quantum Framework, first began as a radi-
cally simplified ROOM virtual machine. Other influences were the classical Gang of
Four design patterns [Gamma+ 95], the UML specification [OMG 01] (especially the
state machine package), the original works of Harel [Harel 87, Harel+ 98], and books
by Bruce Douglass [Douglas 99, Douglas 99a]. The Douglass writings in many ways
are on the opposite end of the spectrum from QP, because QP offers mostly alterna-
tive views and complementary techniques to those he describes. For example, the state
patterns he pioneered rely heavily on orthogonal regions, whereas QP shows how to
implement some of these more elegantly using state hierarchy (Chapter 5).
For over four years, I have been using and refining QP in real-life projects — for
example, in hard real-time implementations of GPS receivers. I am excited and
thrilled by the potential of this approach to the point that I wrote this book so I
could share QP with the largest audience I can reach. However, I am realistic — I do
not see QP as a silver bullet. QP does not promise you the royal road, as do some
design automation tools; however, it offers arguably the fastest road to better
designed, safer, and more efficient event-driven software, because nothing stands
between you and the solution. When you start using QP, you’ll find, as I did, that
your problems change. You no longer wrestle with convoluted if or switch state-
ments; rather, you spend more time thinking about how to apply state patterns and
how to partition your problem into active objects. Nonetheless, even with QP (or
any CASE tool, for that matter), programming reactive systems remains difficult
because it is by nature difficult. As Frederick Brooks [Brooks 87] notes in his classic
essay “No Silver Bullet,” you can only attack the accidental difficulties and can’t do
much about the essential ones, at least not in software alone. In this context, QP
exactly follows Brooks’ advice — to attack and remove the accidental difficulties
associated with developing event-driven software.
Agile Modeling. The basic philosophy behind the new approaches is best summa-
rized in the “Agile Manifesto” [Fowler 01], in which the “seventeen anarchists”
agree to value (1) individuals and interactions over processes and tools and (2) work-
ing software over comprehensive documentation.
In the context of QP, valuing individuals and interactions over processes and tools
means putting emphasis on understanding the underlying implementations and
mechanisms rather than on hiding the complexity behind a tool (the practice that
Anders Hejlsberg [Hejlsberg 01] denounced as “simplexity–complexity wrapped in
something simple”). Real-life experience has shown repeatedly that if an individual
understands the underlying implementation model, then he or she can code more
efficiently and work with greater confidence. For example, determining which
actions fire in which sequence in a nontrivial state transition is not something you
should guess at or discover by running a tool-supported animation of your state-
chart. The answer should come from your understanding of the underlying imple-
mentation (discussed in Chapters 3 and 4). Even if you decide to use a design
automation tool and even if your particular tool uses slightly different statechart
implementation techniques than those I discuss in this book, you will still be a better,
more productive, and confident user of the tool because of your understanding of the
fundamental mechanisms.
In addition to putting value on individuals and interactions by explaining low-
level fundamental software patterns, QP also offers powerful high-level metaphors,
such as the quantum-mechanical and object-oriented analogies. A metaphor is valu-
able because it promotes the conceptual integrity of a software product and provides
a common vocabulary, which dramatically improves communication among all of
the stakeholders. Agile methodologies recognize the importance of such metaphors
(e.g., XP proposes the development of a metaphor as a key practice).
As an elaborative approach, QP values working software over comprehensive
documentation. In fact, QP offers nothing but the working code. I have made every
attempt to provide only executable code, so that you can try out virtually every list-
ing and code snippet you find in this book, as well as the code available only on the
accompanying CD-ROM. Because only executable code is testable, this aspect of QP
goes hand-in-hand with the requirement for continuous testing, which is inherent to
all agile methodologies.
In addition to offering techniques for creating executable code, QP also offers
highly readable, self-documenting code. For example in Chapter 4, I give directions
on how to make the structure of a statechart clearly apparent from the code and
almost equivalent to a UML state diagram. This is not to say that QP abandons UML
diagrams or makes them obsolete. To the contrary, in this book, you will see quite a
few diagrams that follow UML notation strictly (although because I used a simple
drawing tool, they cannot be called UML-compliant). When it comes to diagrams
xii Preface
and other visual models, QP shares the commonsense view of Agile Modeling
[Ambler 01]. The most important role of visual models is to help you think through
the designs and communicate them to programmers, customers, or management. For
that purpose, simple tools like paper and pencil, whiteboard, or sticky notes are usu-
ally sufficient. It is also OK to discard the visual models after they have fulfilled their
purpose. The specific value of visual modeling lies in tapping the potential of high
bandwidth spatial intelligence, as opposed to lexical intelligence used with textual
information.
Incomplete, disposable visual models, however, can’t be used for code synthesis.
In this respect, the agile approach fails to take advantage of the constructive aspect
of some visual representations, such as UML statecharts. QP complements agile
methodologies by enabling high-level modeling directly at the code level. With the
concrete, ready-to-use building blocks provided by QP, you can construct, compile,
and execute concurrent state models rapidly, even if they are nothing more than
vastly incomplete skeletons. As you will see in Chapter 4, you can change the state
machine topology (e.g., add, remove, or rearrange states and transitions) at any
stage, even late in the process, by changing a few lines of code and recompiling. Then
you can test your executable model on the host or target environments. Plenty of
such executable models are included throughout this book. In that way, you can
quickly try out many alternatives before committing to any one of them. This pro-
cess is rightly called modeling, rather than coding, because your goal isn’t the gener-
ation of a final product or even a prototype, but rather the fast exploration of your
design space.
Admittedly with such lightweight modeling, you lose the benefits of spatial intelli-
gence. As mentioned earlier, modeling at the code level does not preclude using UML
diagrams or low-fidelity sticky notes as models of user interfaces. Indeed, spatial
intelligence is best at grasping high-level structures and patterns when the models are
relatively high level and uncluttered. As the models become more detailed, lexical
intelligence usually takes over anyway because, in the end, “programming is all
about text” [Hejlsberg 01].
Audience
This book is intended for the following computer professionals interested in reactive,
or event-driven, systems.
• Embedded programmers and consultants will find practical advice, explanations,
and plenty of code that they can use as is or modify to build event-driven soft-
ware.
• Real-time systems designers will find a lightweight alternative to heavyweight
CASE tools for modeling real-time systems. The Quantum Framework, combined
Guide to Readers xiii
with a preemptive RTOS, can provide deterministic behavior and can be embed-
ded in commercial products.
• Users of design automation tools will better understand the inner workings of
their tools, helping them to use the tools more efficiently and confidently.
• GUI developers, interactive Web page designers, and computer game program-
mers using C or C++ will find nontrivial, working examples of how to code and
integrate UML statecharts with GUI environments such as the Microsoft Win-
dows API.
• Hardware designers exploring the extension of C or C++ with class libraries to
model SoC (System on Chip) designs will find one of the most succinct and effi-
cient implementations of hierarchical state machines.
• Graduate-level students of Computer Science or Electrical Engineering will learn
many design patterns that are backed up by numerous examples and exercises.
This book is about extending object-oriented techniques to programming reactive
systems in C++ and C. I assume that you are familiar with fundamental object-ori-
ented concepts and are reasonably proficient in one of these two languages. To bene-
fit from Part II of the book, you should be familiar with fundamental real-time
concepts. I am not assuming that you have prior knowledge of the UML specification
in general or statecharts in particular, and I introduce these concepts in a crash
course in Chapter 2.
Guide to Readers
This book has two main parts. In Part I (Chapters 1–6), I describe state machines —
what they are, how to implement them, and the standard ways or patterns of using
them. This part is generally applicable to any event-driven system, such as user inter-
faces (graphical and otherwise), real-time systems (e.g., computer games), or embed-
ded systems. In Part II (Chapters 7–11), I describe the Quantum Framework, which
is a software architecture designed specifically for embedded real-time systems.
Surveys of programmers2 consistently indicate that C and C++ clearly dominate
embedded systems programming. The vast majority (some 80 percent) of embedded
systems developers use C; about 40 percent occasionally use C++. Consequently,
every practical book genuinely intended to help embedded systems programmers
should focus on C and C++. For that reason, I consistently present two complete sets
of code: C++ and C. The C++ implementations are typically more succinct and natu-
ral because the underlying designs are fundamentally object oriented. In Appendix A,
2. For example, the Embedded Systems Programming survey (published annually by ESP magazine) or
the Annual Salary Survey (published by SD magazine).
xiv Preface
Acknowledgments
I would like to thank Robert Ward, my acquisitions editor at CMP Books, in whom
I found an excellent editor, extremely knowledgeable software professional, and a
resonating mind. I feel very lucky that Robert accepted my book for publication and
offered his guidance throughout this book’s lengthy birthing. I couldn’t have
dreamed of a better editor for this book.
The team of CMP Books has been wonderful. It was a great pleasure to have
Michelle O’Neal as the managing editor. I’d like to thank Julie McNamee, Catherine
Janzen, and Rita Sooby, for putting up with my writing. I’m amazed how many times
they managed to turn my convoluted technical jargon into something actually read-
able. Reviewing their corrections was for me the best possible lesson in lucid writing.
I also thank Justin Fulmer, for polishing my drawings and integrating them into the
final version of the book.
I am indebted to Jeff Claar, for the technical review of the manuscript and for
scrutinizing the accompanying code. Jeff’s contributions include the complete C++
state machine implementation compliant with multiple inheritance, which is avail-
able on the companion CD-ROM.
I’d like to thank Michael Barr, for letting me publish my earlier article on the sub-
ject in the Embedded Systems Programming magazine, and for reviewing an early
copy of this book.
This book has had a long gestation, of which the actual writing was merely the
final step. I owe a lot to my former colleagues at GE Medical Systems, specifically to
John Zhang, for infecting me with his enthusiasm for design patterns and state
machines. In addition, I’d like to acknowledge my current team at IntegriNautics
Corporation–one of the highest concentrations of Ph.D.s per square foot in Silicon
Valley. I am particularly grateful to Paul Montgomery, for brainstorming many of
the ideas, unforgettable pair-programming sessions, and for patiently enduring all
my early iterations of the Quantum Framework.
xvi Preface
I wholeheartedly thank my parents, who from over 7,000 miles away were still
able to provide plenty of support and encouragement. The perseverance they instilled
in me was critical for completion of this project. Quite specially, I thank my sister
Barbara, for her faith in me and for always taking care of her “little brother”.
Most of all, however, I would like to thank my wife Kinga and our lovely daugh-
ter Marie for tolerating far too many nights and weekends, which I spent in front of
a computer rather than with them. Without their love, help, and continuous encour-
agement this book could never be finished. I love you so much!
Miro Samek
Palo Alto, California
April 2002
PART I
PART I
S TATECHARTS
State machines are a superb formalism for specifying and implementing event-driven
systems that must react to incoming events in a timely fashion. The UML statecharts
represent the current state of the art in state machine theory and notation.
Part I of this book introduces the concept of statecharts, describes concrete tech-
niques of coding statecharts directly in C and C++, and presents a small catalogue of
basic statechart-based design patterns. You will learn that statecharts are a powerful
way of design that you can use even without the assistance of sophisticated code-syn-
thesizing tools.
1
1
Chapter 1
Whirlwind Tour of
Quantum Programming
I have found out there ain’t no surer way to find out whether you like
people or hate them than to travel with them.
— Tom Sawyer Abroad [Mark Twain]
The triumph of the graphical user interface has been one of the most impressive
developments in software during the past three decades.1 Today the concept is so
familiar as to need no description. Although from the beginning, windows, icons,
menus, and pointing have been intuitive and easy to grasp for users, they remain a
challenge for programmers. The internal GUI architecture baffles many newcomers,
who often find it strange, backwards, mind-boggling, or weird. GUI programming is
different because unlike traditional data processing, it is entirely event-driven. Events
1. The concept of the windows, icons, menus, and pointing (WIMP) interface was first publicly displayed by Doug
Englebart and his team from the Stanford Research Institute at the Western Joint Computer Conference in 1968
[Englebart+ 68].
3
4 Chapter 1: Whirlwind Tour of Quantum Programming
can occur at any time in any order. The application always must be prepared to han-
dle them. GUI is an example of a complex reactive system.
You don’t need to look far to find other examples of reactive systems. In fact,
CPUs of all PCs, Macs, and other general-purpose computers consume only about 1
percent of the worldwide microprocessor production. The other 99 percent of micro-
processor chips sold every year end up in various embedded systems, which are pre-
dominantly reactive in nature. Yet, in spite of this ubiquity, the code found in most of
these systems is notoriously difficult to understand, fix, and maintain. Theoretical
foundations on how to construct such software have been around for more than a
decade; however, these ideas somehow could not make it into the mainstream.
Quantum Programming (QP) is an attempt to make the modern methods more
approachable for programmers. QP is a set of straightforward design patterns, idi-
oms, concrete implementations, and commonsense techniques that you can start
using immediately without investing in sophisticated tools.
event, first, in the context of the substate. If the substate cannot handle it, the state-
chart automatically passes the event to the next higher level (i.e., to the superstate).
Of course, states can nest deeper than one level. The simple rule of processing events
applies then recursively to an arbitrary level of nesting.
Harel’s semantics of state hierarchy is at the heart of the Ultimate Hook design
underlying GUI systems; in other words, the statechart supports the Ultimate Hook
pattern directly. This becomes obvious when renaming the superstate to GUI_system
and the substate to GUI_application, as shown in Figure 1.1b. Now this is an
interesting result: The fundamental Ultimate Hook design pattern turns out to be a
very simple statechart! This powerful statechart is unusually simple because, at this
level of abstraction, it does not contain any state transitions.
Traditionally, the hierarchy of states introduced in statecharts has been justified as
follows.
As it turns out, highly complex behavior cannot be easily described by simple, “flat”
state-transition diagrams. The reason is rooted in the unmanageable multitude of
states, which may result in an unstructured and chaotic state-transition diagram —
[Harel+ 98].
Certainly, hierarchical diagrams are often simpler and better structured than tra-
ditional flat diagrams. However, this is not the fundamental reason for the signifi-
cance of state hierarchy, merely one of the side effects. State hierarchy is
fundamentally important even without the multitude of states and transitions, as
demonstrated clearly by the GUI example. The powerful statechart shown in Figure
1.1b contains only two states and not a single state transition; yet, it is powerful. The
only essential feature is state hierarchy, in its pure form.
Figure 1.1b is so unusually simple because it shows only the highest level of
abstraction. All nontrivial GUI applications have many modes of operation (states)
with typically complex rules of switching between these modes (state transitions),
regardless of whether you use a statechart, a classical flat state transition diagram,
brute force, or any other method. However, designs based on the statechart formal-
ism seem to be the most succinct, robust, and elegant, if for no other reason than
their direct support for programming-by-difference (Ultimate Hook pattern).
6 Chapter 1: Whirlwind Tour of Quantum Programming
2. Actually, the statechart is modified slightly compared to the original Horrocks design.
A Better Way of Programming — A Calculator That Works 7
Exercise 1.1 After familiarizing yourself with the contents of the accompanying CD-
ROM (see Appendix C and the HTML browser on the disc) find the
Visual Basic Calculator example and launch it. Try the following
sequence of operations and watch the application crash: 1, /, –, =, 2, =.
Try the sequence 2, ×, CE, 2, =, and observe that Cancel Entry had no
effect, even though it appeared to cancel the ‘2’ entry from the display.
Try different ways of breaking the calculator or of producing misleading
results.
The Visual Basic Calculator often has problems dealing with negative numbers.
This is because the same button (–) is used to negate a number and to enter the sub-
traction operator. The correct interpretation of the ‘–’ event, therefore, depends on
the context, or mode, in which it occurs. Likewise, Cancel Entry (CE button) occa-
sionally works erroneously. Again, Cancel Entry makes sense only in a particular
context, namely, just after the user has entered a number or operator. As it turns out,
the calculator tries to handle it in other contexts as well. At this point, you probably
have noticed an emerging pattern. Just look for events that require different handling
depending on the context, and you can break the calculator in many more ways.
The faults just outlined are rooted in the standard bottom-up implementation of
this application. The context (state) of the computation is represented ambiguously
as a group of flags and variables, so it is difficult to tell precisely in which mode the
application is at any given time. There is no notion of any single mode of operation,
but rather tightly coupled and overlapping conditions of operation. For example, the
calculator uses DecimalFlag to indicate that a decimal point has been entered,
Figure 1.2 (a) Visual Basic Calculator GUI and (b) Quantum Calculator GUI
(a) (b)
8 Chapter 1: Whirlwind Tour of Quantum Programming
OpFlag to represent a pending operation, LastInput to indicate the type of the last
key press event, NumOps to denote the number of operands, and several more state
variables. With this representation, determining whether the ‘–’ key should be
treated as negation or subtraction requires the following conditional logic (in Visual
Basic).3
Select Case NumOps
Case 0
If Operator(Index).Caption = "-" And LastInput <> "NEG" Then
ReadOut = "-" & ReadOut
LastInput = "NEG"
End If
Case 1
Op1 = ReadOut
If Operator(Index).Caption = "-" And LastInput <> "NUMS" And
OpFlag <> "=" Then
ReadOut = "-"
LastInput = "NEG"
End If
...
Such an approach is fertile ground for bugs for at least two reasons: examining
the current mode requires evaluating a complex expression, and switching between
different modes requires modifying many variables, which can easily lead to incon-
sistencies. Expressions like these, scattered throughout the code, are not only unnec-
essarily complex but expensive to evaluate at run time. They are also notoriously
difficult to get right, even by experienced programmers, as the bugs still lurking in
the Visual Basic Calculator attest.
3. The complete Visual Basic source code for the calculator application is available on the accompanying CD-
ROM.
4. Here, I discuss a C++ implementation. However, the implementation in plain C is available on the accompany-
ing CD-ROM.
A Better Way of Programming — A Calculator That Works 9
Figure 1.3 Calculator statechart; the standard IDC_ Windows event prefixes are
omitted for clarity
C IDCANCEL
calc
entry/
ready negated1
result begin entry/
OPER[keyId==MINUS]
entry/ OPER[keyId ==
PLUS]/ CE
operand1
OPER
zero1 int1 frac1
1_9 0/ POINT 0/
1_9/ 1_9/
POINT
OPER
CE
0 1_9 POINT 0 1_9 POINT
CE
operand2
OPER
zero2 int2 frac2
1_9 0/ POINT 0/
1_9/ 1_9/
EQUALS
POINT
this statechart on more than one occasion in the following chapters, for a closer
study of statechart design, and to discuss concrete implementation techniques later.
The calculator statechart from Figure 1.3 contains 15 states5 (the topmost Win-
dows system state is not included) and handles 11 distinct events: IDC_0, IDC_1_9,
5. At first, you might be disappointed that the statechart for such a simple calculator is so complicated. After ana-
lyzing the problem, I feel that the diagram in Figure 1.3 represents the complexity of the problem just about
right. Section 2.3.1 in Chapter 2 explains in more detail the reasons for complexity in this case.
10 Chapter 1: Whirlwind Tour of Quantum Programming
Exercise 1.2 Find and launch the Quantum Calculator application (try both the C++
and C implementations). Try to understand the behavior of the applica-
tion by comparing it with the statechart from Figure 1.3 (you might find
the current state display handy). Try to break it or produce misleading
results. Test the Quantum Calculator to see how it handles the ‘–’ and ‘+’
unary operators.
This state machine takes advantage of hierarchy in several places. For example,
Cancel (the ‘C’ button) is handled explicitly only in the highest level state, calc
(look for the arrow labeled “C” at the top of Figure 1.3). This event triggers a self-
transition in the calc state. You can understand how the statechart handles the Can-
cel event based solely on the Ultimate Hook semantics of state nesting introduced
earlier. Assume, for example, that when the user clicks the ‘C’ button, the active state
is opEntered. This state doesn’t “know” how to handle the Cancel event, so it auto-
matically passes this event for processing to the next higher level state, that is, to
calc. The calc state knows how to handle the Cancel event by executing the afore-
mentioned self-transition. This causes exit from calc followed by entry, first to
calc, then ready, and finally begin, by the recursive execution of initial transitions.
At this point, the calculator ends processing of the Cancel event and waits for the
next event.
To restate: The statechart started in opEntered and ended in begin. Actually, the
statechart could have been in any of the other 11 substates of calc (refer to Exercise
1.3) and would still end up in begin. The classical flat state machine would require
specifying each of the 11 transitions explicitly. The statechart allows reusing one
transition 11 times. The gain is not only the drastic reduction in the sheer number of
transitions but also a more accurate representation of the problem at hand. There is
only one Cancel transition in the calculator problem. A natural language specifica-
tion might read as follows: Whatever state the calculator is in at the time the user
clicks the ‘C’ button, the calculator should clear its display and other internal regis-
ters and become ready for another computation. The statechart represents this speci-
fication faithfully, whereas the classical flat state machine would add repetitions and
artificial complexity.
Exercise 1.3 Not all 15 substates of calc can be active. For example states ready,
operand1, and operand2 can never become active. Explain why.
A Better Way of Programming — A Calculator That Works 11
1 #include <windows.h>
2 #include "qf_win32.h" // include the Quantum Framework (QF)
3
4 struct CalcEvt : public QEvent {
5 int keyId; // ID of the key depressed
6 };
7
8 class Calc : public QHsm { // calculator Hierarchical State Machine
9 public:
10 Calc() : QHsm((QPseudoState)initial) {}
11 static Calc *instance(); // Singleton accessor method
12 private:
13 void initial(QEvent const *e); // initial pseudostate-handler
14 QSTATE calc(QEvent const *e); // state-handler
15 QSTATE ready(QEvent const *e); // state-handler
16 QSTATE result(QEvent const *e); // state-handler
17 QSTATE begin(QEvent const *e); // state-handler
18 QSTATE negated1(QEvent const *e); // state-handler
19 QSTATE operand1(QEvent const *e); // state-handler
20 QSTATE zero1(QEvent const *e); // state-handler
21 QSTATE int1(QEvent const *e); // state-handler
22 QSTATE frac1(QEvent const *e); // state-handler
23 QSTATE opEntered(QEvent const *e); // state-handler
24 QSTATE negated2(QEvent const *e); // state-handler
25 QSTATE operand2(QEvent const *e); // state-handler
26 QSTATE zero2(QEvent const *e); // state-handler
27 QSTATE int2(QEvent const *e); // state-handler
28 QSTATE frac2(QEvent const *e); // state-handler
29 QSTATE final(QEvent const *e); // state-handler
30 private: // action methods...
31 void clear();
32 void insert(int keyId);
33 void negate();
34 void eval();
35 void dispState(char const *s);
36 private: // data attributes
37 HWND myHwnd;
38 char myDisplay[40];
12 Chapter 1: Whirlwind Tour of Quantum Programming
39 char *myIns;
40 double myOperand1;
41 double myOperand2;
42 int myOperator;
43 BOOL isHandled;
44 friend BOOL CALLBACK calcDlg(HWND hwnd, UINT iEvt,
45 WPARAM wParam, LPARAM lParam);
46 };
Events dispatched to the calculator are represented as instances of the CalcEvt
class (Listing 1.1, lines 4–6). This class derives from QEvent and adds the keyId
event parameter, which represents the ID of the key entered. As mentioned before,
the calculator hierarchical state machine Calc declared in lines 8 through 46 derives
from QHsm. The Calc class contains several attributes that keep track of the compu-
tation (the attributes constitute the memory of the state machine). Please note, how-
ever, that the attributes are not used to determine the state of the application. Rather,
the QHsm superclass keeps track of the active state, which is crisply defined at all
times. In fact, the calculator GUI displays it for you, so that you can easily correlate
calculator behavior with the underlying statechart from Figure 1.3. You also can rec-
ognize all states declared as state handler methods in lines 14 through 29. The
unusual use of indentation indicates state nesting.
The dialog procedure (Listing 1.2) starts the state machine (by invoking the
init() method in response to the WM_INIT_DIALOG Windows message), translates
the Windows events to calculator events, and dispatches the events for processing
(by invoking the dispatch() method) in response to the WM_COMMAND Windows
message.
A Better Way of Programming — A Calculator That Works 13
Listing 1.3 State handlers for calc, ready, and begin states. Boldface indicates
housekeeping code (see the last paragraph of this section)
21 insert(((CalcEvt *)e)->keyId);
22 Q_TRAN(&Calc::int1);
23 return 0;
24 case IDC_POINT:
25 clear();
26 insert(IDC_0);
27 insert(((CalcEvt *)e)->keyId);
28 Q_TRAN(&Calc::frac1);
29 return 0;
30 case IDC_OPER:
31 sscanf(myDisplay, "%lf", &myOperand1);
32 myOperator = ((CalcEvt *)e)->keyId;
33 Q_TRAN(&Calc::opEntered);
34 return 0;
35 }
36 return (QSTATE)&Calc::calc;
37 }
38
39 QSTATE Calc::begin(QEvent const *e) {
40 switch (e->sig) {
41 case Q_ENTRY_SIG: dispState("begin"); return 0;
42 case IDC_OPER:
43 if (((CalcEvt *)e)->keyId == IDC_MINUS) {
44 Q_TRAN(&Calc::negated1);
45 return 0; // event handled
46 }
47 else if (((CalcEvt *)e)->keyId == IDC_PLUS) { // unary "+"
48 return 0; // event handled
49 }
50 break; // event unhandled!
51 }
52 return (QSTATE) &Calc::ready;
53 }
As you can see, the housekeeping code6 (i.e., state machine declaration and state
handler skeletons, indicated in boldface in Listing 1.3) you need to write to translate
a statechart to C++ is almost trivial. In fact, it is not more complicated than the code
you need to write to translate a Unified Modeling Language (UML) class diagram to
C++ (i.e., class declaration and method skeletons). You probably don’t even think
that you translate a class diagram to C++; you simply code an object-oriented system
directly in C++. This is so because C++ provides the right (object-oriented) level of
abstraction. The practical techniques for implementing statecharts raise the level of
6. Douglass [Douglass 99] uses the term “housekeeping code” to denote sections of code that are used repetitively
to represent common constructs such as states, transitions, events, and so on. Some CASE tools can automati-
cally generate such representational invariants from statechart diagrams.
16 Chapter 1: Whirlwind Tour of Quantum Programming
abstraction further (to the “quantum” level) and, in the same sense, enable direct
modeling of reactive systems in C++.
their descendants. The same holds true in state hierarchies. For example, consider a
hypothetical “failed” state that turns on an alarm bell upon entry (as part of its entry
action) and turns it off upon exit (as part of its exit action). If this state has a sub-
state, say “unsafe,” and this substate becomes active, the alarm bell will ring because
being in the unsafe state also means being in the failed state. If the system is in the
unsafe state, it also is in the failed state and, recursively, is in every ancestor state of
failed. The is in (is-in-a-state) generalization of states corresponds to the is a (is-a-
kind-of) generalization of classes.
1.3.3 Programming-by-Difference
Class inheritance is commonly used for programming-by-difference. This program-
ming style is the essence of reuse: A subclass needs to define only the differences from
its superclass and otherwise can reuse (share) implementation defined in the super-
class.
Behavioral inheritance is identical in this respect. A substate needs to define only
the differences from its superstate and can otherwise reuse the behavior defined in
the superstate. In other words, supporting programming-by-difference behavioral
inheritance enables reuse of behavior.
that gradually renders any software system prohibitively expensive to maintain and
modify.
Because of the similarities between behavioral inheritance and class inheritance,
the same general refactorings are applicable both to OO systems and to statecharts.
• Refactoring to generalize — creating a common superclass–creating a com-
mon superstate
• Refactoring to specialize — deriving subclasses from a common base–nest-
ing substates in a common superstate
In addition, like OO design patterns, state patterns capture many structures that
result from refactoring state models. Using these patterns early in the life of a state-
chart design can prevent later refactorings. Alternatively, when restructuring becomes
inevitable, state patterns can provide convenient targets for your refactorings.
As described by the laws of quantum theory, microscopic objects have the follow-
ing two most characteristic properties.
• Quantum objects spend their lives in strictly defined quantum states and
can change their state only by means of uninterruptible transitions known
as quantum leaps. Because of various symmetries, the quantum states are
naturally hierarchical (degenerate in quantum terminology).
• Quantum systems cannot interact with one another directly; rather, every
interaction proceeds via an intermediate artifact (intermediate boson). The
various intermediate bosons are mediators of fundamental forces (e.g., pho-
tons mediate the electromagnetic force, gluons the strong force, and bosons
W± and Zo the weak forces).
QP follows the quantum model quite faithfully. Part I of this book corresponds to
the first characteristics of quantum systems — their discrete, statelike behavior. Part
II, on the other hand, covers the second aspect of the quantum analogy — the inter-
actions. The fundamental units of decomposition in QP are concurrently active hier-
archical state machines (active objects). These software machines can interact with
one another only by asynchronous exchange of various event instances.
1.5 Summary
This chapter provided a quick tour of Quantum Programming. QP is concerned with
reactive systems, which are systems that continuously interact with their environment
by means of exchanging events. Over the years, several techniques have evolved that
can be used to design and implement such systems. One of the most powerful ideas
has proved to be the concept of hierarchical event processing, which GUI program-
mers know as the Ultimate Hook pattern. Almost two decades ago, David Harel gen-
eralized this concept and combined it with finite state machines to create the
formalism known as statecharts. Although statecharts have gained almost universal
acceptance in software methodologies and modeling languages, like UML, their
adoption into everyday programming has been slow. The main reason is the wide-
spread misunderstanding that statecharts are only usable when supported by sophisti-
cated CASE tools. The result is a lack of practical advice on how to efficiently hand-
code statecharts in mainstream programming languages such as C or C++. However,
as you saw in the Quantum Calculator example, you can easily implement the funda-
mental concepts of statecharts directly in C++ by applying the behavioral inheritance
meta-pattern. This pattern is central to QP, just as abstraction, inheritance, and poly-
morphism are patterns central to OOP.
The analogy between QP and OOP goes deeper. They are both unified around the
concept of inheritance. Just as class inheritance is a cornerstone of OOP, behavioral
Summary 21
A Crash Course in
Statecharts
Nothing is particularly hard if you divide it into small jobs.
— Henry Ford
If you look through enough real-life code in use across the industry, you probably
agree that the code pertaining to the reactive parts of various systems is riddled with
a disproportionate number of convoluted conditional execution branches (deeply
nested if–else or switch–case statements in C/C++). This highly conditional code
(recall the Visual Basic Calculator from Chapter 1) is a testament to the basic charac-
teristics of reactive systems, which respond to an input based on not only the nature
of the input but the history of the system (i.e., on past inputs in which the system was
involved).
If you could eliminate even a fraction of these conditional branches, the code
would be much easier to understand and test, and the sheer number of convoluted
execution paths through the code would drop radically, perhaps by orders of magni-
tude. Techniques based on state machines are capable of achieving exactly this — a
dramatic reduction of the different paths through the code and simplification of the
conditions tested at each branching point.
23
24 Chapter 2: A Crash Course in Statecharts
The state machines described in the UML specification represent the current state
of the art in the long evolution of these techniques. UML state machines, known also
as UML statecharts [OMG 01], are object-based variants of Harel statecharts [Harel
87] and incorporate several concepts defined in ROOMcharts, a variant of the state-
chart defined in the real-time object-oriented modeling (ROOM) language [Selic+
94].
This chapter briefly introduces UML statecharts with a fresh, unorthodox per-
spective on the role of state machines and state modeling. My intention is not to give
a complete discussion of UML statecharts, which the official OMG specification
[OMG 01]1 covers formally and comprehensively. Rather, my goal in this chapter is
to lay a foundation quickly by establishing basic terminology, introducing basic
notation,2 and clarifying semantics. This chapter is restricted to only a subset of
those statechart features that are arguably most fundamental. The emphasis is on
essence rather than formality.
2.1.1 States
A state is a situation or condition in the life of a system during which some (usually
implicit) invariant holds, the system performs some activity, or the system waits for
some external event [OMG 01].
A state captures the relevant aspects of the system’s history very efficiently. For
example, when you strike a key on a keyboard, the character code generated will be
either an uppercase or a lowercase character, depending on whether the Caps Lock is
1. The official UML specification [OMG 01] is included in PDF on the accompanying CD-ROM.
2. Appendix B contains a comprehensive summary of the notation.
The Essence of Finite State Machines 25
active. Therefore, the keyboard is in the capsLocked state, or the default state
(most keyboards have an LED that indicates when the keyboard is in the
capsLocked state). The behavior of a keyboard depends only on certain aspects of
its history, namely whether Caps Lock has been activated, but not, for example, on
how many and which specific characters have been typed previously. A state can
abstract away all possible (but irrelevant) event sequences and capture only the rele-
vant ones.
not always straightforward and presents a difficult design decision that will have
profound effects on software performance and complexity.
2.1.3 Guards
Extended state machines often react to stimuli based not only on the qualitative state
but also on the value of the extended state variables associated with that state. For
instance in the keyboard example, when the keystroke counter exceeds a certain
value, the state machine alters its behavior by changing state. In fact, the logical con-
dition (comparing the counter with the threshold) is tested by every keystroke, but
the change of state occurs only when the condition evaluates to TRUE.
This example illustrates the general mechanism by which extended state variables
influence behavior. Boolean expressions, called guard conditions (or simply guards),
are evaluated dynamically based on the value of extended state variables. 3 Guard
conditions affect the behavior of a state machine by enabling or disabling certain
operations (e.g., change of state).
The need for guards is the immediate consequence of adding memory (extended
state variables) to the state machine formalism. Used sparingly, guards and extended
state variables form an incredibly powerful mechanism that can immensely simplify
designs. Used too liberally, however, guards can easily defeat the purpose of using
state machines in the first place.
If you recall from the first paragraph of this chapter, the primary reason to use
state machines is to reduce the number of conditional branches in the code and to
reduce the complexity of the tests performed at each branch. The use of guards goes
exactly against these goals by reintroducing testing of (extended state) variables and
branching based on these tests. In the extreme case, guards effectively take over han-
dling of all the relevant conditions in the system, which puts you back to square one.
Indeed, abuse of guards is the primary mechanism of architectural decay in designs
based on state machines.
2.1.4 Events
In the most general terms, an event is an occurrence in time and space that has signif-
icance to the system. Strictly speaking, in the UML specification, the term “event”
refers to the type of occurrence rather than to any concrete instance of that occur-
rence [OMG 01]. For example, Keystroke is an event for the keyboard, but each
press of a key is not an event but a concrete instance of the Keystroke event. Another
event of interest for the keyboard might be Power-on, but turning the power on
tomorrow at 10:05:36 will be just an instance of the Power-on event.
3. Guard conditions also can contain event parameters (see the discussion of events and event parameters in the
next section).
The Essence of Finite State Machines 27
An event can have associated parameters, allowing the event instance to convey
not only the occurrence of some interesting incident but also quantitative informa-
tion regarding that occurrence. For example, the Keystroke event generated by press-
ing a key on a computer keyboard has associated parameters that convey the
character scan code, as well as the status of the Shift, Ctrl, and Alt keys.
An event instance outlives the instantaneous occurrence that generated it and
might convey this occurrence to one or more state machines. Once generated, the
event instance goes through a processing life cycle that can consist of up to three
stages. First, the event instance is received when it is accepted and awaiting process-
ing (e.g., it is placed on the event queue). Later, the event instance is dispatched to the
state machine, at which point it becomes the current event. Finally, it is consumed
when the state machine finishes processing the event instance. A consumed event
instance is no longer available for processing.
not stipulate any particular order; rather, it puts the burden on the designer to devise
guards in such a way that the order of their evaluation does not matter. Practically,
this means that guard expressions should have no side effects, at least none that
would influence evaluation of other guards having the same trigger.
4. Mealy automata dismiss this problem by simply assuming that actions take no time to execute (the so-called
zero time assumption).
The Essence of Finite State Machines 29
The problem with the preemptive model is that it introduces internal concurrency
within the scope of a single state machine. If preemption is allowed, handling the
high-priority event might modify some internal variables that were in the process of
being modified by the interrupted (low-priority) processing. After resuming, the low-
priority processing might find some of these variables unexpectedly modified, which
could cause errors. This creates a standard concurrency problem that requires some
form of mutual exclusion. However, proper handling of this situation leads to
immense complexity in the general case, rendering the preemptive model impractical.
In the RTC model, the system processes events in discrete, indivisible RTC steps.
Higher priority events cannot interrupt the handling of other events, thereby com-
pletely avoiding the internal concurrency issue. This model also gets around the
problem of the ill-defined state in the Mealy automaton. During event processing, the
system is unresponsive (unobservable), so the ill-defined state during that time has no
practical significance. The RTC model is analogous to the quantum mechanical inter-
pretation of a quantum leap, where a transition between different quantum states
(the quantum leap) is fundamentally indivisible (uninterruptible). Because a quantum
system has to finish one interaction before engaging in another, it always appears to
be in a well-defined quantum state. Such systems are fundamentally unobservable in
the midst of a transition.
RTC does not mean that a state machine has to monopolize the processor until
the RTC step is complete. The preemption restriction only applies to the task context
of the state machine that is already busy processing events. In a multitasking envi-
ronment, other tasks (not related to the task context of the busy state machine) can
be running, possibly preempting the currently executing state machine. As long as
other state machines do not share variables with each other, there are no concurrency
hazards.
State machine formalisms, including UML statecharts, universally assume RTC
execution semantics. The key advantage of RTC processing is simplicity. Its biggest
disadvantage is that, within the scope of a single state machine, event handling can-
not take too long to ensure a timely response to higher priority events. In order to
achieve high responsiveness, timely low-latency and high-priority processing cannot
be mixed in the same state machine with high-latency, low-priority processing. 5 This
requirement can sometimes significantly complicate implementation.
5. A state machine can improve responsiveness by breaking up the CPU-intensive processing into sufficiently short
RTC steps.
30 Chapter 2: A Crash Course in Statecharts
Figure 2.1 State transition diagram representing the computer keyboard FSM
state
default ANY_KEY[keystrokes > 100000]
guard
ANY_KEY / generate uppercase code
6. Appendix B contains a succinct summary of the graphical notations used throughout the book, including state
transition diagrams.
The Essence of UML Statecharts 31
7. The graphical notation of a statechart is a straightforward extension of the state transition diagrams.
32 Chapter 2: A Crash Course in Statecharts
Figure 2.2 (a) Simple statechart with state s11 nested inside state s1; (b) state
model of a simple toaster oven, in which states toasting and baking
share the common transition from state heating to doorOpen
(a) (b)
s1 heating
OPEN_DOOR
superstate toasting
s11
doorOpen
substate baking
valuable because it reduces the amount of detail you need to deal with at one time.
As Grady Booch [Booch 94] notes:
… we are still constrained by the number of things that we can comprehend at one
time, but through abstraction, we use chunks of information with increasingly greater
semantic content.
However valuable abstraction might be, you cannot cheat your way out of com-
plexity simply by hiding it inside composite states. However, the composite states can
not only hide but also reduce complexity through the reuse of behavior. Without
such reuse, even a moderate increase in system complexity often leads to an explo-
sive increase in the number of states and transitions. Classical nonhierarchical FSMs
can easily become unmanageable, even for moderately involved systems. This is
because traditional state machine formalism inflicts repetitions. For example, if you
transform the statechart from Figure 2.2b to a classical flat state transition diagram, 8
you must repeat one transition (from heating to doorOpen) in two places — as a
transition from toasting to doorOpen and from baking to doorOpen. Avoiding
repetitions allows HSMs to grow proportionally to system complexity. As the mod-
eled system grows, the opportunity for reuse also increases and thus counteracts the
explosive increase in states and transitions typical for traditional FSMs. As will
become clear by the end of this chapter, hierarchical states enable capturing symme-
tries of the system.
8. Such a transformation is always possible because HSMs are mathematically equivalent to classical FSMs.
The Essence of UML Statecharts 33
less cluttered diagrams. In other words, simpler diagrams are just a side effect of
behavioral reuse enabled by state nesting.
The fundamental character of state nesting comes from the combination of
abstraction and hierarchy, which is a traditional approach to reducing complexity
and is otherwise known in software as inheritance. In OOP, the concept of class
inheritance describes relations between classes of objects. Class inheritance describes
the is a relationship among classes. For example, class Bird might derive from class
Animal. If an object is a bird (instance of the Bird class), it automatically is an ani-
mal, because all operations that apply to animals (e.g., eating, eliminating, reproduc-
tion) also apply to birds. But birds are more specialized, since they have operations
that are not applicable to animals in general. For example, flying applies to birds but
not to fish.9
The benefits of class inheritance are concisely summarized by Gamma and col-
leagues [Gamma+ 95].
Inheritance lets you define a new kind of class rapidly in terms of an old one, by
reusing functionality from parent classes. It allows new classes to be specified by
difference rather than created from scratch each time. It lets you get new
implementations almost for free, inheriting most of what is common from the ancestor
classes.
As you saw in the previous section, all these basic characteristics of inheritance
apply equally well to nested states (just replace the word “class” with “state”),
which is not surprising because state nesting is based on the same fundamental is a
classification as object-oriented class inheritance. For example, in a state model of a
toaster oven, state toasting nests inside state heating. If the toaster is in the
toasting state, it automatically is in the heating state, because all behavior per-
taining to heating applies also to toasting (e.g., the heater must be turned on). But
toasting is more specialized because it has behaviors not applicable to heating in gen-
eral. For example, setting toast color (light or dark) applies to toasting but not to
baking.
In the case of nested states, the is a (is-a-kind-of) relationship merely needs to be
replaced by the is in (is-in-a-state) relationship; otherwise, it is the same fundamental
classification. State nesting allows a substate to inherit state behavior from its ances-
tors (superstates); therefore, it’s called behavioral inheritance. Note that behavioral
inheritance is an original term characteristic of QP and does not come from the UML
specification. Please also note that behavioral inheritance describes the relationship
between substates and superstates, and you should not confuse it with traditional
(class) inheritance applied to entire state machines.
means independent in this context) and that being in such a composite state entails
being in all of its orthogonal regions simultaneously [Harel+ 98].
Orthogonal regions address the frequent problem of a combinatorial increase in
the number of states when the behavior of a system is fragmented into independent,
concurrently active parts. For example, apart from the main keypad, a computer
keyboard has an independent numeric keypad. From the previous discussion, recall
the two states of the main keypad already identified: default and capsLocked (Fig-
ure 2.1). The numeric keypad also can be in two states — numbers and arrows —
depending on whether Num Lock is active. The complete state space of the keyboard
in the standard decomposition is the cross product of the two components (main
keypad and numeric keypad) and consists of four states: default–numbers,
default–arrows, capsLocked–numbers, and capsLocked–arrows. However, this
is unnatural because the behavior of the numeric keypad does not depend on the
state of the main keypad and vice versa. Orthogonal regions allow you to avoid mix-
ing the independent behaviors as a cross product and, instead, to keep them separate,
as shown in Figure 2.3.
Note that if the orthogonal regions are fully independent of each other, their com-
bined complexity is simply additive, which means that the number of independent
states needed to model the system is simply the sum k + l + m + …, where k, l, m, …
denote numbers of or-states in each orthogonal region. The general case of mutual
dependency, on the other hand, results in multiplicative complexity, so in general,
the number of states needed is the product k × l × m × ….
Figure 2.3 Two orthogonal regions (main keypad and numeric keypad) of a
computer keyboard
keyboard
mainKeypad numericKeypad
default numbers
ANY_KEY/generate NUMERIC_KEY/
lowercase code generate number code
important is that orthogonal regions can coordinate their behaviors by sending event
instances to each other.
Even though orthogonal regions imply independence of execution (i.e., some kind
of concurrency), the UML specification does not require that a separate thread of
execution be assigned to each orthogonal region (although it can be implemented
that way). In fact most commonly, orthogonal regions execute within the same
thread. The UML specification only requires that the designer not rely on any partic-
ular order in which an event instance will be dispatched to the involved orthogonal
regions.
10. Commonly such a safety-critical function is (and should be) realized by mechanical interlocks, but for the sake
of this discussion, suppose you need to implement it in software.
The Essence of UML Statecharts 37
Figure 2.4 (a) Changing the identity of object :Bird into :Fish requires complete
destruction and recreation of the entire object, including the part
inherited from the common Animal superclass; (b) state transition
from toasting to baking does not require exit and reentry into the
common heating superstate
toasting
Bird Fish
baking
:Bird :Fish
might have orthogonal regions, the current active state is actually represented by
a tree of states starting with the single top state at the root down to individual
simple states at the leaves. The UML specification refers to such a state tree as
state configuration [OMG 01].
Every state transition, except for internal transitions (see the next section), causes
the statechart to exit a source state configuration and enter a target state configura-
tion. The UML specification prescribes that taking a state transition involves execut-
ing the following actions in the following sequence [OMG 01]:
• exit actions of the source state configuration,
• actions associated with the transition, and
• entry actions of the target state configuration.
This three-step transition rule is easy to interpret when the source and target state
are both simple and nest at same level, as in the transition between states default
and capsLocked in Figure 2.3. However in statecharts, state transition can connect
any two states directly, including composite states at different levels of nesting. The
problem is to discover which states need to be exited in step one. The UML specifica-
tion prescribes that the first step involves exiting all nested states from the current
active state (which might be a direct or transitive substate of the source state) up to,
but not including, the least common ancestor (LCA) state of the source and target
states. As the name indicates, the LCA is the lowest composite state that is simulta-
neously a superstate (ancestor) of both the source and the target states. As described
before, the order of execution of exit actions is always from the most deeply nested
state (the current active state) up the hierarchy to the LCA (but without exiting the
LCA). In the last step (after executing actions associated with the transition), the tar-
get state needs to be entered. Executing entry actions commences from the level
where the exit actions left off (i.e., from inside the LCA), down the state hierarchy to
The Essence of UML Statecharts 39
the target state. If the target state is composite, then its submachine is recursively
entered via the default transition or the history mechanism (see the description of
history pseudostates coming up in Section 2.2.7).
2.2.7 Pseudostates
Because statecharts started as a visual formalism [Harel 87], some nodes in the dia-
grams other than the regular states turned out to be useful for implementing various
features (or simply as a shorthand notation). The various “plumbing gear” nodes are
collectively called pseudostates. More formally, a pseudostate is an abstraction that
encompasses different types of transient vertices (nodes) in the state machine graph.
The UML specification [OMG 01] defines the following kinds of pseudostates.
40 Chapter 2: A Crash Course in Statecharts
• The initial pseudostate (shown as a black dot) represents a source for default
transition. There can be, at most, one initial pseudostate in a composite state.
• The shallow-history pseudostate (shown as a circled letter “H”) is a shorthand
notation that represents the most recent active direct substate of its containing
state. A transition coming into the shallow-history vertex (called a transition to
history) is equivalent to a transition coming into the most recent active substate of
a state. A transition can originate from the history connector to designate a state
to be entered in case a composite state has no history yet (has never been active
before).
• The deep-history pseudostate (shown as a circled “H*”) is similar to shallow-his-
tory, except it represents the whole, most recent state configuration of the com-
posite state that directly contains the pseudostate.
• The join pseudostate (shown as a vertical bar) serves to merge several transitions
emanating from source vertices in different orthogonal regions.
• The fork pseudostate (represented identically as a join) serves to split an incoming
transition into two or more transitions terminating in different orthogonal
regions.
• The junction pseudostate (shown as a black dot) is a semantics-free vertex that
chains together multiple transitions. A junction is like a Swiss Army knife: it per-
forms various functions. Junctions can be used both to merge multiple incoming
transitions (from the same concurrent region) and to split an incoming transition
into multiple outgoing transition segments with different guards. The latter case
realizes a static conditional branch because the use of a junction imposes static
evaluation of all guards before the transition is taken.
• The choice pseudostate (shown as an empty circle or a diamond) is used for
dynamic conditional branches. It allows the splitting of transitions into multiple
outgoing paths, so the decision on which path to take could depend on the results
of prior actions performed in the same RTC step.
tion, so many people use either structured English or, more formally, expressions in
an implementation language such as C, C++, or Java [Douglass 99b]. Practically, this
means that UML statechart notation depends heavily on the specific programming
language.
This is not to criticize the graphical notation of statecharts. In fact, it is remark-
ably expressive and can scarcely be improved. Rather, the aforementioned difficulties
stem from the inherent difficulty in visualizing the software concepts themselves. As
Brooks [Brooks 95] writes:
More fundamentally, … software is difficult to visualize. Whether we diagram control
flow, variable scope nesting, variable cross-references, data flow, hierarchical data
structures, or whatever, we feel only one dimension of the intricately interlocked
software elephant.
Later in the text, he responds to Harel:
… software structure is not embedded in three-space, so there is no natural mapping
from a conceptual design to a diagram, whether in two dimensions or more … one
needs multiple diagrams [multiple views], each conveying some distinct aspect, and
some aspects don’t diagram well at all.
Figure 2.5 Comparison of (a) Mealy state machine, (b) Moore state machine, and
(c) activity graph (flowchart)
Figure 2.5 shows a comparison of Mealy and Moore state machines with a flow-
chart. State machines (a and b) perform actions in response to explicit triggers. In
contrast, the flowchart (c) does not need explicit triggers; rather, it transitions from
node to node in its graph automatically upon completion of an activity.
Compared to the statechart, a flowchart reverses the sense of vertices and arcs
[Simons 00]. In a statechart, the processing is associated with the arcs, whereas in a
flowchart, it is associated with the vertices (Figure 2.5 attempts to show that reversal
of roles by aligning the arcs of the statecharts with the processing stages of the flow-
chart).
You can compare a flowchart to an assembly line in manufacturing because the
flowchart describes the progression of some task from beginning to end. A statechart
generally has no notion of such a progression. A computer keyboard (Figure 2.1), for
example, is not in a more advanced stage when it is in the capsLocked state, com-
pared to being in the default state. A state in a state machine is an efficient way of
specifying constraints of the overall behavior of a system, rather than a stage of pro-
cessing.
framework (see Part II of this book) that integrates tightly with the underlying
operating system.
12. Parsing and state machines are related. In fact, most parsers operate as finite state machines.
Examples of State Models 45
Figure 2.6 The first two steps in elaborating the Quantum Calculator statechart
(a) (b)
PLUS, MINUS, operand1
MULTIPLY, OPER operand1
0..9,
DIVIDE EQUALS OPER C POINT
opEntered
opEntered result
operand2
0..9, EQUALS
POINT 0..9,
POINT operand2
Figure 2.6 shows the first steps in elaborating the Quantum Calculator state-
chart.14 In the first step (a) the state machine attempts to realize the primary function
of the system (the primary use case), which is to compute the expression operand1
operator operand2 equals …. The state machine starts in the operand1 state, whose
function is to ensure that the user can only enter a valid operand. This state obvi-
ously needs an internal submachine to accomplish this goal, but ignore that for now.
The criterion for transitioning out of operand1 is entering an operator (+, -, *, or
/). The statechart then enters the opEntered state, in which the calculator waits for
the second operand. When the user clicks a digit (0 … 9) or a decimal point, the state
machine transitions to the operand2 state, which is similar to operand1. Finally, the
13. This particular application ignores invalid inputs. Often, a better approach is to actively prevent generation of
the invalid inputs in the first place (e.g., by disabling invalid options).
14. Designing a statechart is not a strict science. You can arrive at a correct design in many different ways. This is
just one of them.
46 Chapter 2: A Crash Course in Statecharts
user clicks ‘=’, at which point the calculator computes and displays the result. It then
transitions back to the operand1 state to get ready for another computation.
The simple state model from Figure 2.6a has a major problem, however. When
the user clicks ‘=’ in the last step, the state machine cannot transition directly to
operand1, because this would erase the result from the display. You need another
state, result, in which the calculator pauses to display the result (Figure 2.6b).
Three things can happen in the result state: (1) the user clicks an operator button
to use the result as the first operand of a new computation,15 (2) the user clicks Can-
cel (C) to start a completely new computation, or (3) the user enters a number or a
decimal point to start entering the first operand.
Figure 2.6b illustrates a trick worth remembering — the consolidation of signals
PLUS, MINUS, MULTIPLY, and DIVIDE into a higher level signal OPER (operand). This
transformation avoids repetition of the same group of triggers on two transitions
(from operand1 to opEntered and from result to opEntered). Although most
events are generated externally to the statechart, in many situations it is still possible
to perform simple transformations before dispatching them (e.g., see Listing 1.2 in
Chapter 1). Such transformations often simplify designs more than the slickest state
and transition topologies.
Figure 2.7 Applying state nesting to factor out the common Cancel (C) transition
operand2 operand2
The state machine from Figure 2.6b accepts the Cancel command only in the
result state. However, the user expects to be able to cancel and start over at any
time. The statechart in Figure 2.7a adds this feature in a naïve way. A better solution
is to factor out the common transition into a higher level state calc and let all sub-
states reuse the Cancel transition through behavioral inheritance. Figure 2.7b shows
this solution, except that it implements Cancel as an empty self-transition 16 rather
than as a transition from the calc to operand1 substate. This solution enables even
more reuse, because a self-transition triggers exit and then reentry to the state (see
Section 2.2.6, “Internal Transitions”), thus reusing the initialization that these
actions perform anyway.
The states operand1 and operand2 need submachines to parse floating-point
numbers (Figure 2.8). These submachines consist of three substates. The zero sub-
state is entered when the user clicks ‘0’. Its function is to ignore additional zeros that
the user might try to enter (so that the calculator displays only one ‘0’). The function
of the int substate is to parse the integer part of a number. This state is entered
either from outside or from the zero peer substate (when the user clicks ‘1’ through
‘9’). Finally, the substate frac parses the fractional part of the number. It is entered
either from outside or from both of its peer substates when the user clicks the deci-
mal point.
0 1..9 POINT
operandX
Exercise 2.1 Integrate composite states operand1 and operand2 into the statechart
from Figure 2.7b (i.e., draw the calculator statechart with all levels of
detail).
Exercise 2.2 The quantum calc0 application on the accompanying CD-ROM imple-
ments the statechart from Figure 2.7b. Find and execute this application.
Test how it performs and correlate its behavior with the underlying stat-
echart. Examine the source code.
The last step brought the calculator statechart to the point in which it can actually
compute expressions (Exercise 2.2). However, it can handle only positive numbers.
In the next step, I will add handling of negative numbers, which turns out to be per-
haps the toughest problem in this design because the same button (–) represents the
binary operator of subtraction in some contexts and the unary operator of negation
in others.
In only two possible contexts can ‘–’ unambiguously represent negation rather
than subtraction: (1) in the opEntered state (as in the expression 2*–2 =) and (2) at
48 Chapter 2: A Crash Course in Statecharts
the beginning of a new computation (as in the expression –2*2 =). The solution to
the first case (shown in Figure 2.9a) is simpler. You need one more state, negated2,
which is entered when the operator is MINUS (note the use of the guard). Upon
entry, this state sets up the display to show ‘–0’ and subsequently does not clear the
display when transitioning to the operand2 state. This behavior is different from
opEntered because, in this state, the display must be cleared to prepare for the sec-
ond operand.
(a) (b)
OPER EQUALS
OPER[keyId==MINUS] OPER[keyId==MINUS]
opEntered begin
0..9, negated2 result
0..9,
negated1
POINT
0..9, POINT 0..9,
POINT 0..9, POINT
operand2
POINT operand1
The second case in which ‘–’ represents negation is trickier because the beginning
of a new computation specification is much more subtle. Here, it indicates the situa-
tion just after launching the application or after the user clicks Cancel, but not when
the calculator displays the result from the previous computation. Figure 2.9b shows
the solution. State begin is separated from operand1 to capture the behavior spe-
cific to the beginning of a new computation (note the initial transition pointing now
to begin rather than to operand1). The rest of the solution is analogous to the first
case, except now state begin plays the role of opEntered.
Exercise 2.3 The state machine fragment in Figure 2.9b still offers opportunity for
reuse of the identical transition from result to operand1 and from
begin to operand1. Factor out this transition and place it in a common
superstate, ready.
The calculator is almost ready now. The final touches (that I leave as an exercise)
include adding Cancel Entry transitions in appropriate contexts and adding a transi-
tion to the final state to terminate the application.
The complete Quantum Calculator statechart is shown in Figure 1.3 in Chapter 1.
Executable applications with compete source code (versions in C and C++) are avail-
able on the accompanying CD-ROM.
Examples of State Models 49
17. Theoretically, the hydrogen atom has an infinite number of states (energy levels), so strictly speaking, it cannot
be modeled by a finite state machine. However, most relevant aspects of hydrogen behavior can be modeled by
considering just a few of the lowest energy levels.
18. The indicated lifetimes of the corresponding decay paths are just examples and do not correspond to actual
measurements.
50 Chapter 2: A Crash Course in Statecharts
3
entry/E=-1.511eV
3P
entry/l =1;
3S
3P-1 3P0 3P+1
entry/l =0; m=0; entry/m=-1; entry/m=0; entry/m=+1;
3D
entry/l=2;
3D-2 3D-1 3D0 3D+1 3D+2
entry/m=-2; entry/m=-1; entry/m=0; entry/m=+1; entry/m=+2;
after 1E-18s/ after 1E-18s [B!=0] after 1E-18s [B!=0] g(1.89e V)/
emit g(1.89eV) after 1E-18s [B!=0] absorb g
2
entry/E=-3.4e V 2P
entry/l =1;
2S
2P-1 2P0 2P+1
entry/l =0; m=0; entry/m=-1; entry/m=0; entry/m=+1;
entry/E=-13.6e V;
ground state l =0; m=0;
case [B != 0], are intentionally omitted just to make this graph readable. UML
notation addresses the clutter arising in virtually every nontrivial state diagram by
allowing stubbed transitions and submachine states (besides, of course, using
abstraction to hide the internal structure of composite states) [OMG 01]. Please
note, however, that the difficulties with representation come from graphical notation
rather than the fundamental inability of statecharts (as a concept) to scale up to han-
dle the behavior of complex systems.
In these equations, all (direct or transitive) substates of a given energy state corre-
spond to exactly the same energy eigenvalue (–13.6eV/n2). In quantum mechanics,
such a state is called degenerate. Degeneration is always an indication of some symme-
try of the system. For example, the degeneration of the angular momentum state
comes from spherical symmetry of the atom. This observation is very important:
Behavioral inheritance resulting from the nesting of states corresponds to symmetry of
the system.
A quantum mechanical state hierarchy naturally complies with the LSP for states.
For example, if an atom is in a given quantum state of the projection operator Lz, it
simultaneously is in the quantum state of angular momentum operator L2, and simul-
taneously is in the quantum state of the Hamiltonian H. In such a hierarchy, you can
efficiently use abstraction. For example, you can abstract away angular momentum
and consider only energy levels of the atom (e.g., to understand its energy spectrum).
This abstraction corresponds to zooming out to the highest level of nesting. On the
other hand, if you destroy the spherical symmetry by subjecting the atom to an exter-
nal magnetic field (e.g., to study the Zeeman effect), you might be interested in the
lowest level substates of the magnetic quantum number.
Exercise 2.4 Closer inspection of hydrogen spectra reveals so-called fine structure,
caused by a small magnetic moment associated with electron spin. The
spin is an intrinsic angular momentum, with only two possible projec-
– ). How can you include electron
tions on the quantization axis (sz = ± 1/2 h
spin in the state model of the hydrogen atom? Hint: Electron spin is
mostly orthogonal to the basic electron–proton interaction.
2.4 Summary
Most reactive systems have state behavior, which means the response of a system
depends not only on the nature of a stimulus but on the history of the previous
stimuli. Such behavior is commonly modeled as FSMs or automata. FSMs effi-
ciently capture overall system behavior and any applicable constraints.
States are means of partitioning behavior into nonoverlapping chunks (the
divide and conquer strategy). The concept of state is a very useful abstraction of
system history, capable of capturing only relevant sequences of stimuli (and ignoring
all irrelevant ones). In extended state machines (state machines with “memory”),
state corresponds to qualitative aspects of system behavior, whereas extended state
variables (program memory) correspond to the quantitative aspects.
An event is a type of instantaneous occurrence that can cause a state machine to
perform actions. Event instances are means of conveying such occurrences to state
Summary 53
machines. Events can have parameters, which convey not only the occurrence of
something interesting but the quantitative information regarding this occurrence.
Upon reception of an event instance, a state machine responds by performing actions
(executing code). The response might include changing state, which is called a state
transition. Classical FSMs have two complementary interpretations of actions and
transitions. In Mealy automata, actions are associated with transitions, whereas in
Moore automata, actions are associated with states.
State machine formalisms universally assume the RTC execution model. In this
model, all actions triggered by an event instance must complete before the next event
instance can be dispatched to the state machine, meaning the state machine executes
uninterruptible steps (RTC steps) and starts processing each event in a stable state
configuration.
UML statecharts are an advanced formalism for specifying state machines. The
formalism is a variant of extended state machines with characteristics of both Mealy
and Moore automata. Statecharts include notations of nested hierarchical states and
orthogonal regions and extend the notation of actions.
The most important innovation of statecharts over classical FSMs is the introduc-
tion of hierarchical states. The semantics of state nesting allows substates to define
only differences in behavior from the superstates, thus promoting sharing and reuse
of behavior. The relation between a substate and its superstate has all the characteris-
tics of inheritance and is called behavioral inheritance in QP. Behavioral inheritance
is as fundamental as class inheritance and allows the building of whole hierarchies of
states, as with class taxonomies. The properly designed state hierarchies comply with
the LSP, extended for states.
Statecharts introduce state entry and exit actions, which provide the means for
guaranteed initialization and cleanup, very much as constructors and destructors do
for classes. Entry actions are always executed starting with the outermost state,
which is analogous to class constructors executed from the most general class. The
exit actions, similar to destructors, are always executed in exact reverse order.
Entry and exit actions combined with state nesting complicate transition
sequence. The general rule is to (1) execute exit actions from the source state, (2)
execute actions associated with transitions, and (3) execute entry actions to the tar-
get. The only exceptions to this rule are internal transitions, which never cause the
execution of exit or entry actions and therefore are distinctively different from self-
transitions.
Statecharts were first invented as a visual formalism; therefore, they are
heavily biased toward graphical representation. However, it is important to dis-
tinguish the underlying concept of the HSM from statechart notation.
The significance of HSMs goes beyond software. For example, state hierar-
chies arise naturally in microscopic systems governed by the laws of quantum
54 Chapter 2: A Crash Course in Statecharts
Implementing state machines is not as easy as it looks. Even with classical nonhierar-
chical FSMs, you must make an amazing number of design decisions and trade-offs:
• How do you represent events? How about events with parameters?
• How do you represent states?
• How do you represent transitions?
• How do you dispatch events to the state machine?
When you add state hierarchy, exit/entry actions, and transitions with guards, the
design becomes anything but trivial.
In this chapter, I focus on standard implementation techniques, which you can
find in the literature or in the working code. They are mostly applicable to the classi-
cal flat (nonhierarchical) extended state machines because there are hardly any stan-
dard implementations of HSMs.
55
56 Chapter 3: Standard State Machine Implementations
1. Judging by 12 years of articles (1988–2000) published on the subject in Embedded Systems Programming mag-
azine.
2. The initial transition (the init() method) is intentionally separated from the state machine constructor to give
you better control over the initialization sequence.
Nested switch Statement 57
comment, and star. The input alphabet of the state machine consists of signals STAR
(*), SLASH (/), and CHAR (any character different from * and /).
SLASH code
SLASH
CHAR,
star slash
CHAR SLASH
comment STAR
STAR STAR
CHAR,
SLASH
Exercise 3.1 In the spirit of eXtreme Programming (XP), first prepare a test for the C
comment parser state machine. The test harness should (1) open a C
source file for reading, (2) instantiate a tested state machine object, (3)
take a default transition by invoking init() on this object, (4) read
characters from the file (until end-of-file) and translate them into signal
events (CHAR, STAR, SLASH), and (5) dispatch the event instances to the
state machine by invoking dispatch() on the state machine object.
Listing 3.1 Comment parser state machine implemented using the nested switch
statement technique
60 case CHAR_SIG:
61 myCommentCtr += 2; // count STAR-? as comment
62 tran(COMMENT); // go back to COMMENT
63 break;
64 }
65 break;
66 }
67 }
Signals are typically represented as an enumeration (Listing 3.1, lines 1–3), as are
states (lines 4–6). The myState state variable is included in the Cparser1 class (line
14) because each instance needs to keep track of its own state. Execution of the state
machine begins with the initial transition via a call to init(). The heart of the state
machine is implemented in the dispatch() event handler method (lines 19–67),
which the client code invokes once per RTC step. State transitions are achieved by
reassigning the state variable (tran(), line 11).
Variations of this implementation include breaking up the monolithic event-han-
dler code by moving the second level of discrimination (based on the signal) into spe-
cialized state handler functions (see Exercise 3.3). In this case, the job of the main
event handler is reduced to dispatching events to appropriate state handlers. Also,
the state machine is often a Singleton (i.e., there is only one instance of it in the sys-
tem). In that case, the state machine can be coded as a module instead of a class, and
the single instance of the state variable can be hard-coded in the event handler.
The nested switch statement implementation has the following consequences.
• It is simple.
• It requires enumerating states and triggers.
• It has a small memory footprint, since only one scalar state variable is necessary
to represent a state machine.
• It does not promote code reuse since all elements of a state machine must be
coded specifically for the problem at hand.
• Event dispatching time is not constant but depends on the performance of the two
levels of switch statements, which degrade with increasing number of cases (typ-
ically as O(log n), where n is the number of cases).
• The implementation is not hierarchical, and manually coded entry/exit actions
and nested initial transitions are prone to error and difficult to maintain in view
of changes in the state machine topology. This is mainly because the code pertain-
ing to one state (e.g., an entry action) becomes distributed and repeated in many
places (on every transition leading to this state).
• The latter property is not a problem for code-synthesizing tools, which often use a
nested switch statement type of implementation.
60 Chapter 3: Standard State Machine Implementations
Exercise 3.2 Implement CParser1 in C. Hint: You can preserve the notion of the
CParser1 class by following techniques described in Section A.1 of
Appendix A. Please do not use the “C+” macros in this exercise.
Signals →
CHAR_SIG STAR_SIG SLASH_SIG
doNothing(),
← States
code
slash
doNothing(), a2(), doNothing(),
slash
code comment code
a1(), doNothing(), a1(),
comment
comment star comment
a2(), a1(), a2(),
star
comment star code
This table lists signals (triggers) along the top and states along the left edge. The
contents of the cells are transitions represented as {action, next-state} pairs. For
example, in the slash state, the STAR_SIG signal triggers a transition to the com-
ment state, associated with the execution of the a2() action. Empty cells correspond
to undefined signal–state combinations. The common policy of handling such trig-
gers is to silently ignore them without changing the state. You can choose a different
policy; for example, such events could trigger an error() action.
Listing 3.2 shows how a typical implementation using this technique might look.
State Table 61
array of transitons
:StateTable
:Tran :Tran :Tran Tran
myState :Tran :Tran :Tran
myTable action : Action
myNstates :Tran :Tran :Tran nextState
myNsignals :Tran :Tran :Tran
dispatch()
3. Invoking methods of a subclass through a pointer-to-member function of the superclass can be potentially
unsafe when combined with multiple inheritance — that is, if the subclass inherits from more base classes than
StateTable (see the sidebar “C++ Pointer-to-Member Functions and Multiple Inheritance” on page 75).
State Table 63
The application-specific part requires: (1) enumerating states and signals (Listing
3.2, lines 27–32), (2) subclassing StateTable (line 33), (3) defining the action func-
tions (lines 39–40), and (4) initializing the transition table (lines 46–59). Note that
CParser2 declares the table myTable as both static and const (line 41). The
static specifier means that all instances of the CParser2 class can share a single
instance of the table. The const specifier indicates that the table is immutable and
can be placed in ROM. Note the necessity of upcasting4 pointer-to-member func-
tions in the static initializer5 (lines 46–59) because methods a1() and a2() are mem-
bers of a subclass of StateTable (CParser2) rather than of StateTable directly.
There seem to be two main variations on state table implementation in C++. Con-
crete state machines can either be state tables (inheritance) or have a state table
(aggregation). The technique presented here falls into the inheritance category. How-
ever, the aggregation approach seems to be more popular (e.g., see [Douglass 01,
99]). Aggregation introduces the indirection layer of the context class — that is, the
concrete class on behalf of which the aggregated state table executes actions. Inherit-
ance eliminates this indirection, because the StateTable class plays the role of the
context (concrete state machine) class simultaneously. In other words, by virtue of
inheritance, every concrete state machine (like CParser2) also is a StateTable.
The state table, like the nested switch statement technique, is not hierarchical.
However, it is always possible to extend such nonhierarchical techniques to incorpo-
rate exit actions, entry actions, and nested initial transitions by hard-coding them
into transition action functions, which are then placed into the state table. Figure 3.3
shows an example of such a transition action function fromStateAAonE1() that
handles the whole hierarchical transition chain by explicitly calling the appropriate
exit actions, transition actions, and entry actions [Duby 01].
The state table implementation has the following consequences.
• It maps directly to the highly regular state table representation of a state machine.
• It requires the enumeration of triggers and states.
• It provides relatively good performance for event dispatching (O(const), not tak-
ing into account action execution).
• It promotes code reuse of the generic event processor, which is typically small.
• It requires a large state table, which is typically sparse and wasteful. However,
because the state table is constant, it often can be stored in ROM rather than
RAM.
4. If your C++ compiler does not support the new-style cast static_cast<Action>(…) (e.g., some older or
EC++ compilers don’t), then you should use the C-style cast (Action)(…).
5. The static (compile-time) initialization requires that the inner class StateTable::Tran is an aggregate —
that is, a class without private members or constructors [Stroustrup 91]. That’s why I’ve declared this class as
struct in line 5 of Listing 3.2.
64 Chapter 3: Standard State Machine Implementations
Exercise 3.4 Implement the CParser2 state machine in C. Hint: Sections A.1 and A.2
of Appendix A describe simple techniques for implementing classes and
inheritance in C. Please use the “C+” macros CLASS() and SUBCLASS()
to declare classes StateTable and CParser2, respectively.
Exercise 3.5 Implement a variation of the state table technique in which you push the
responsibility of changing state into the action method (action methods
must call tran() explicitly). This variation allows you to replace the
Transition class with a pointer-to-member function. Note how this
cuts the size of the transition array in half (storing next-states becomes
unnecessary). However, you need to implement many more action meth-
ods, because now they are more specific. Modify the event processor
StateTable::dispatch() and the transition table initialization
accordingly.
State Design Pattern 65
Figure 3.4 State design pattern applied to the CParser state machine
abstract CParserState
State Pattern state
onCHAR() virtual
onSTAR() methods
context 1
onSLASH()
CParser3 concrete
1 states
delegation myState
myCommentCtr
myState->onCHAR() CodeState CommentState
onCHAR()
myState->onSTAR() onSTAR() SlashState StarState
onSLASH()
myState->onSLASH()
init()
myState = target # tran()
61 context->tran(&CParser3::myCodeState);
62 }
63 void SlashState::onSTAR(CParser3 *context) {
64 context->myCommentCtr += 2;
65 context->tran(&CParser3::myCommentState);
66 }
67 void SlashState::onSLASH(CParser3 *context) {
68 context->tran(&CParser3::myCodeState);
69 }
70 void CommentState::onCHAR(CParser3 *context, char c) {
71 context->myCommentCtr++;
72 }
73 void CommentState::onSTAR(CParser3 *context) {
74 context->tran(&CParser3::myStarState);
75 }
76 void CommentState::onSLASH(CParser3 *context) {
77 context->myCommentCtr++;
78 }
79 void StarState::onCHAR(CParser3 *context, char ch) {
80 context->myCommentCtr += 2;
81 context->tran(&CParser3::myCommentState);
82 }
83 void StarState::onSTAR(CParser3 *context) {
84 context->myCommentCtr += 2;
85 context->tran(&CParser3::myCommentState);
86 }
87 void StarState::onSLASH(CParser3 *context) {
88 context->myCommentCtr += 2;
89 context->tran(&CParser3::myCodeState);
90 }
The CParserState class (Listing 3.3, lines 2–7) provides the interface for han-
dling events as well as the default (do-nothing) implementation for the actions asso-
ciated with these events. The four C parser states are defined as concrete subclasses
of the abstract CParserState class (lines 8–29). These subclasses override only spe-
cific event-handler methods; those corresponding to events that are handled in these
states. For example, state CodeState overrides only the onSLASH() method. Class
CParser3 plays the role of the Context class from the pattern (lines 30–50). It grants
friendship to all state classes (lines 31–34) and also declares all concrete states as
static members (lines 35–38). The context class duplicates the interface of the
abstract State class declaring a method for every signal event (lines 45–47). The
implementation of these methods is entirely prescribed by the pattern. The context
class simply delegates to the appropriate methods of the State class, which are
invoked polymorphically. The specific actions are implemented inside the event han-
dler methods of the concrete CParserState subclasses (lines 57–90).
68 Chapter 3: Standard State Machine Implementations
Exercise 3.6 (Advanced) Implement the State design pattern version of the C com-
ment parser in C. Hint: Appendix A describes the realization of abstrac-
tion, inheritance, and polymorphism in C. Note: I consider this exercise
advanced because the design makes heavy use of polymorphism, which is
not necessary, even for the implementation of later hierarchical state
machines.
Optimal FSM Implementation 69
The standard State design pattern does not use the dispatch() method for per-
forming RTC steps. Instead, for every signal event, it provides a specific (type-safe)
event-handler method. However, the pattern can be modified (simplified) by com-
bining all event handlers of the state class into just one, generic state handler,
dispatch(), as shown in Figure 3.5. The abstract state class then becomes generic,
and its dispatch() method becomes the generic state handler. Demultiplexing
events (by event type), however, must be done inside the dispatch() methods of
the concrete state subclasses.
Figure 3.5 Simplified State design pattern applied to C comment parser state
machine
State
Simplified State generic
pattern abstract state dispatch()
1
context 1
virtual method
CParser3 concrete
states
delegation myState
myCommentCtr
CodeState CommentState
myState->dispatch() init()
dispatch() SlashState StarState
myState = target # tran()
Exercise 3.7 Implement the C comment parser state machine using the Simplified
State design pattern from Figure 3.5.
FSM typedef
void // return type
myState : State (Fsm::* // class the function is the member of
State) // name of pointer-to-member
init()
(unsigned const sig); // argument list
dispatch()
# tran()
generic FSM base class
myState = target
CParser4 Optimal FSM
(this->*myState)(sig) Pattern
code()
slash()
state handler comment() concrete FSM
methods star()
Listing 3.4 The optimal FSM pattern applied to the C comment parser
1 class Fsm {
2 public:
3 typedef void (Fsm::*State)(unsigned const sig);
4 Fsm(State initial) : myState(initial) {}
5 virtual ~Fsm() {} // virtual xtor
6 void init() { dispatch(0); }
7 void dispatch(int sig) { (this->*myState)(sig); }
8 protected:
9 void tran(State target) { myState = target; }
10 #define TRAN(target_) tran(static_cast<State>(target_))
11 State myState;
12 };
13 enum Signal{ // enumeration for CParser signals
14 CHAR_SIG, STAR_SIG, SLASH_SIG
15 };
16 class CParser4 : public Fsm {
17 public:
18 CParser4() : Fsm((State)&Cparser4::initial) {} // ctor
19 long getCommentCtr() const { return myCommentCtr; }
20 private:
21 void initial(int); // initial pseudostate
22 void code(int sig); // state-handler
23 void slash(int sig); // state-handler
24 void comment(int sig); // state-handler
25 void star(int sig); // state-handler
26 private:
27 long myCommentCtr; // comment character counter
28 };
Optimal FSM Implementation 71
29
30 void CParser4::initial(int) {
31 myCommentCtr = 0;
32 TRAN(&CParser4::code); // take the default transition
33 }
34 void CParser4::code(int sig) {
35 switch (sig) {
36 case SLASH_SIG:
37 TRAN(&CParser4::slash); // transition to "slash"
38 break;
39 }
40 }
41 void CParser4::slash(int sig) {
42 switch (sig) {
43 case STAR_SIG:
44 myCommentCtr += 2; // SLASH-STAR characters count as comment
45 TRAN(&CParser4::comment); // transition to "comment"
46 break;
47 case CHAR_SIG:
48 case SLASH_SIG:
49 TRAN(&CParser4::code); // go back to "code"
50 break;
51 }
52 }
53 void CParser4::comment(int sig) {
54 switch (sig) {
55 case STAR_SIG:
56 TRAN(&CParser4::star); // transition to "star"
57 break;
58 case CHAR_SIG:
59 case SLASH_SIG:
60 ++myCommentCtr; // count the comment character
61 break;
62 }
63 }
64 void CParser4::star(int sig) {
65 switch (sig) {
66 case SLASH_SIG:
67 myCommentCtr += 2; // count STAR-SLASH as comment
68 TRAN(&CParser4::code); // transition to "code"
69 break;
70 case CHAR_SIG:
71 case STAR_SIG:
72 myCommentCtr += 2; // count STAR-? as comment
73 TRAN(&CParser4::comment); // go back to "comment"
74 break;
75 }
76 }
72 Chapter 3: Standard State Machine Implementations
As you can see, this implementation combines elements from the Nested switch
statement, State Table, and Simplified State design pattern, but it also adds some
original ideas. The design hinges on class Fsm. This class plays a double role as the
Context class from the simplified State Pattern and the event processor from the
State Table pattern. The novelty of the Optimal FSM design comes from representing
states directly as state handler methods, which are members of the Fsm class (actu-
ally, its subclasses6 like CParser4). This means that state handlers have immediate
access to all attributes of the Context class (via the implicit this pointer in C++)
without breaking encapsulation. Like the Context class, Fsm keeps track of the cur-
rent state by means of the myState attribute of type Fsm::State, which is type-
def’d as a pointer-to-member function of the Fsm class (Listing 3.4, line 3).
The Optimal FSM design pattern has the following consequences.
• It is simple.
• It partitions state-specific behavior and localizes it in separate state handler meth-
ods. These methods have just about the right granularity — neither too fine (as
with action methods) nor monolithic (as with the consolidated event handler).
• It provides direct and efficient access to state machine attributes from state han-
dler methods and does not require compromising the encapsulation of the Con-
text class.
• It has a small memory footprint because only one state variable (the myState
pointer) is necessary to represent a state machine instance.
• It promotes code reuse of an extremely small (trivial) and generic event processor
implemented in the Fsm base class.
• It makes state transitions efficient (by reassigning the myState pointer).
• It provides good performance for event dispatching by eliminating one level of
switch from the Nested switch Statement technique and replacing it with the
efficient function pointer dereferencing technique. In typical implementations,
state handlers still need to rely on one level of a switch statement, with perfor-
mance dependent on the number of cases (typically O(log n), where n is the num-
ber of cases). However, the switch can be replaced by a look-up table in selected
(critical) state handlers (see Exercise 3.8)
• It is scalable and flexible. It is easy to add both states and events, as well as to
change state machine topology, even late in the development cycle.
6. Invoking methods of a subclass through a pointer-to-member function of the superclass can be potentially
unsafe when combined with multiple inheritance — that is, if the subclass inherits from more base classes than
Fsm (see the sidebar “C++ Pointer-to-Member Functions and Multiple Inheritance” on page 75).
State Machines and C++ Exception Handling 73
Exercise 3.8 Reimplement the optimal FSM C comment parser in C. Change the
implementation of one state handler — say, CParser4comment() — to
use an internal pointer-to-function look-up table, rather than the switch
statement, to dispatch events at a fixed time. Hint: Appendix A describes
the realization of abstraction and inheritance in C. Please use the tech-
niques directly in this exercise without the “C+” macros.
respects. The following disassembled output shows the invocation of the in-lined
Fsm::dispatch() method (x86 instruction set, 32-bit protected mode, Microsoft
VC++ compiler).
0040118C push esi ; stack the event pointer e
0040118D mov ecx,407940h ; put 'this' pointer in ecx
00401192 call dword ptr ds:[407944h] ; (this->*myState)(e)
As you can see, the dereferencing of the pointer-to-member function and the
(this->*myState)() method invocation are both accomplished in just one
machine instruction (noted in bold). The two preceding instructions prepare argu-
ments for the call (stack the e pointer and fetch the this pointer) and are needed no
matter what technique you use.
The following is the disassembled output of a state transition (the in-lined tran()
method).
0040101A mov dword ptr [ecx],401030h ; QFSM_TRAN(slash)
Again, state transition takes only one machine instruction because state transition
corresponds just to reassigning the this->myState pointer (the this pointer is, as
before, in the ecx register).
Exercise 3.9 In the literature, you often find implementations that apply pointers to
functions but still use a scalar state variable to resolve the state handler
through a look-up table (e.g., see [Gomez 00]). Try to implement this
technique as a variation of the optimal FSM. Look at the disassembled
output and compare it with the original implementation. What are other
disadvantages of this technique?
7. I am interested here only in the efficiency of the generic event processor, not the specific user actions.
8. The Optimal FSM implementation in C should be truly unbeatable; however, the performance of the C++
implementation depends on the C++ compiler.
Role of Pointer-to-Member Functions 75
However, if MI is not important to you (which I believe is the case for the vast
majority of applications), then the simple techniques presented in this chapter should
be safe while offering unbeatable performance.
Please note that the ENTRY_SIG and EXIT_SIG signals are reserved; that is, cli-
ents must not use them as their application-specific signals.
Exercise 3.10 Suppose you want the C comment parser state machine to count the
number of comment blocks (/* … */). The obvious solution is to count
the number of transitions from slash to comment (Figure 3.1), but this
approach is not robust against possible extensions of the state machine.
A safer solution would be to use entry or exit actions. However, using
the entry action to count the number of entries into state comment does
not provide the correct number of comment blocks. Explain why. Pro-
pose a hierarchical C comment parser state machine, in which the count-
ing of entries into the comment state would indeed provide the number of
the complete comment blocks.
9. The ROOMchart implementation published by Selic and colleagues [Selic+ 94] is different from code generated
by the ObjecTime toolset.
78 Chapter 3: Standard State Machine Implementations
10. For example, a UML-compliant design automation tool, Rhapsody, from I-Logix (www.ilogix.com) seems
not to use the UML meta-model for code generation. Instead, the code synthesized by the tool resembles a hand-
coded double switch statement technique [Douglass 99].
Summary 79
3.11 Summary
The standard implementation techniques and their variations discussed in this
chapter can be freely mixed and matched to provide a continuum of possible
trade-offs. Indeed, most of the implementations of state machines that you can
find in the literature seem to be variations or combinations of the three funda-
mental techniques: the Nested switch Statement, the State Table, and the object-ori-
ented State design pattern. In this chapter, I provided concrete, executable code, and
for each fundamental technique, I discussed the consequences of its use, as well as
some of the most common variations.
One particular combination of techniques, the Optimal FSM pattern, deserves
special attention because it is elegant and offers an unbeatable combination of good
performance and a small memory footprint. As you will see in Chapter 4, its inter-
face can be compatible with the behavioral inheritance meta-pattern, so you could
use it as a drop-in replacement for the simpler reactive classes, which don’t need state
hierarchy but would benefit from somewhat better performance and a smaller mem-
ory footprint.
In all techniques, state machines tend to eliminate many conditional state-
ments from your code. By crisply defining the state of the system at any given
time, state machines require that you test only one variable (the state variable)
instead of many variables to determine the mode of operation (recall the Visual Basic
Calculator example from Chapter 1). In all but the most basic approach of the
Nested switch Statement, even this explicit test of the “state variable” disappears as
a conditional statement. This coding aspect is similar to the effect of polymorphism,
which eliminates many tests based on the type of the object and replaces them with
more efficient (and extensible) late binding.
In the last section, I skimmed through some published attempts to implement
state hierarchy. Only automatic code synthesis seems to address the problem
correctly. The published manual techniques rely on explicitly hard-coding tran-
sition chains, which renders the code inflexible and practically defeats the pur-
pose of using state hierarchy in the first place. In the next chapter, I present the
QP (Quantum Programming) approach.
80 Chapter 3: Standard State Machine Implementations
4
Chapter 4
Implementing Behavioral
Inheritance
Perfection is achieved, not when there is nothing more to add
but when there is nothing left to take away.
— Antoine de Saint Exupery
1. The concept of behavioral inheritance (defined in Chapter 2) is the relationship between the substate and the
superstate in an HSM.
2. You can find a few such state patterns in Chapter 5.
81
82 Chapter 4: Implementing Behavioral Inheritance
To be practical and truly useful, the concepts of state machine and state hierarchy
must map easily to mainstream programming languages like C or C++. One
excellent example of how to successfully realize fundamental meta-patterns in a
C-like language3 is C++ itself (viewed as an OO extension to C). The C++ object
model (e.g., see [Lippman 96]) is nothing more than a concrete implementation
of the three fundamental OO meta-patterns. In view of the OO analogy between
behavioral inheritance and class inheritance, a successful HSM implementation
should be able to imitate the following main elements attributed to the wide-
spread acceptance of the C++ object model.
1. It should be simple to use and maintain. Defining states should be as easy as
defining C++ classes.
2. It should allow for easy changes in the state machine topology (state nesting and
state transitions). No manual coding of transition chains should be required. The
necessary modifications should be confined to one place in the code (ideally, one
line), like changing superclass in C++.
3. It should provide good run-time efficiency and should have a small memory foot-
print. The cost of dispatching events to a state machine should be comparable to
the invocation of virtual functions in C++.
4. It should follow the “zero overhead” principle of C++ (what you don’t use
shouldn’t cost you anything). For instance, the virtual function mechanism of
C++ allows you to explicitly specify class methods for which you accept the over-
head of late binding (virtual functions). Other class methods (nonvirtual) will not
pay the price.
Although UML statecharts support state nesting and thus enable behavioral
inheritance, they also contain many more concepts of secondary importance. The full
specification of the UML state machine package [OMG 01] presents a concoction of
features at various levels of generality, usefulness, and implementation overhead that
tends to overwhelm and obscure the really fundamental and important aspects of
statecharts. More importantly, the big and heavyweight UML specification precludes
a small and efficient implementation.
Therefore, in order to meet the goals enumerated earlier, the HSM implementa-
tion does not attempt to address all features specified in the UML state machine
package; rather, it addresses only the following few essential elements:
• hierarchical states with full support for behavioral inheritance,
• guaranteed initialization and cleanup with state entry and exit actions, and
• support for specializing state models via class inheritance.
3. Some recent additions to the family of C-like languages include Java and C#.
Structure 83
As you can see, this minimal approach leaves out pretty much everything except
support for behavioral inheritance and extensibility. In particular, the behavioral
inheritance meta-pattern intentionally limits itself only to the passive event proces-
sor, which needs to be driven externally to process events. In particular, the meta-
pattern does not include the standard elements traditionally associated with state
machines, such as an event queue, an event dispatcher, an execution context (thread),
or timing services. There are at least two reasons for this separation of concerns.
First, unlike the generic event processor, the other elements necessary to execute state
machines depend heavily on the concrete operating system. Second, in many cases,
these elements are already available. For example, GUI systems such as Microsoft
Windows offer a complete event-driven environment for executing passive state
machines, so there is no need to duplicate this functionality.4
The goal of the behavioral inheritance meta-pattern is to provide a generic event-
processor that you can use with any event queuing and dispatching mechanism. 5 The
strategy is to provide just enough (but not more!) truly fundamental elements to
allow for the efficient construction of all other (higher level) statechart features,
including those built into the UML specification.
Note: In Chapter 5, you will see how to realize event deferral, orthogonal regions,
and transitions to history as state patterns that build on top of the funda-
mental behavioral inheritance implementation presented here. Chapter 6
addresses the reuse of state models through the inheritance of entire state
machines.
4.1 Structure
Chapter 3 detailed one particular implementation of the classical flat state machine
(the Optimal FSM) that had exceptionally favorable properties. This approach can
be extended to support state hierarchy without sacrificing its good characteristics.
Figure 4.1 shows the overall structure of the behavioral inheritance meta-pattern.
On the left side of the diagram, the design is similar to the Optimal FSM design
(compare Figure 3.6 in Chapter 3). The abstract HSM base class QHsm (quantum
HSM) provides the familiar init() interface to initialize the state machine, the dis-
patch() method to dispatch events, and the (protected) tran() method to execute
state transitions. This base class is central to the design because all concrete state
machines derive from it.
4. The Quantum Calculator (Chapter 1) provides an example of integrating the event processor directly with the
Windows GUI.
5. Part II of this book describes the Quantum Framework, an infrastructure for executing state machines opti-
mized for embedded real-time systems.
84 Chapter 4: Implementing Behavioral Inheritance
As in the Optimal FSM design, the state QState (quantum state) is represented as
a pointer-to-member function of the QHsm class. Class QHsm keeps track of the active
state by means of the myState attribute. In addition, it uses another attribute
(mySource) to keep track of the current transition source during a state transition (in
HSMs, when a transition is inherited from a higher level superstate, the source of
this transition is different from the active state).
On the right side of Figure 4.1, you find facilities for representing events and
event parameters. The QEvent (quantum event) class represents a SignalEvent (see
Section 2.2.8 in Chapter 2), which can be used as-is (for events without parameters)
or can serve as a base for subclassing (for events with parameters). QEvent relates to
QHsm through the signature of the state handler method (see the note attached to
QState in Figure 4.1).
Listing 4.1 shows the complete declaration of the QHsm class, which provides the
familiar public methods: init() for triggering the initial transition (line 9), and
dispatch(), for dispatching events (line 10). You also can find the protected
tran() method for executing transitions (line 21), but this method is not intended to
be invoked directly by the clients. Instead, QHsm provides three macros to execute
state transitions: Q_INIT() is exclusively for initial transitions, Q_TRAN() is for reg-
ular state transitions, and Q_TRAN_DYN() is for state transitions in which the target
can change at run time. The following sections explain all these elements in detail.
As you also will see later in this chapter, an HSM implementation can do without
polymorphism at the basic level. For simplicity, the QHsm class intentionally avoids
Structure 85
virtual functions, except of the virtual destructor (Listing 4.1, line 8). The vir-
tual destructor, however, makes the QHsm class polymorphism-ready and is impor-
tant to properly synthesize pointer-to-member functions in some C++
implementations. Chapter 6 discusses these issues, as well as the legitimate use of
polymorphism in conjunction with state machines. In Chapter 6, you also will see
how to design extensible object models and how to reuse them through inheritance.
If you are familiar with the standard Chain of Responsibility design pattern
[Gamma+ 95], you might recognize that it addresses similar design problems to
behavioral inheritance. In fact, every state hierarchy is a specific chain of responsibil-
ity, in which a request (event instance) is sent down a chain of state hierarchy in
which more than one state has a chance to handle it. However, the two patterns are
also significantly different. The chain of responsibility addresses the forwarding of
requests among different objects, whereas behavioral inheritance addresses event
handling within a single object.
4.1.1 Events
As you probably noticed from the standard state machine implementations (Chapter
3), most of the techniques (in particular, the optimal FSM approach) require a uni-
form representation of events, which leaves essentially only two choices for passing
events to the handler methods: (1) passing the signal and generic event parameters
separately or (2) combining the two into an event object. In Chapter 3, I demon-
strated how to use the first option. Passing signals and parameters separately was
fine for events with only a small and fixed set of parameters (e.g., the raw Windows
API (Win32) uses the fixed-parameter technique to pass events to WinMain()), but in
general, the second option offers more flexibility. Combining the signal with the
parameters has an additional advantage, in that event instances (as opposed to pairs
of signals and event parameters) are available in virtually every event-driven environ-
ment and can be passed around more easily and stored in event queues.
Event instances are used primarily as “bags” for packaging and passing around
signals and event parameters. To generate events efficiently, it’s often convenient to
use statically preallocated event objects initialized with an initializer list. To allow
such initialization, a class must be an aggregate; that is, it must not have private or
protected members, constructors, base classes, and virtual functions [Stroustrup 91].
For that reason, the quantum event class QEvent (Listing 4.2) is declared as struct
without any constructors (an obvious constructor would take one argument to ini-
tialize the sig attribute). In C++, struct is exactly equivalent to class, except in
struct, the default protection level is public rather than private [Lipmann 96].
New events with arbitrary parameters derive from the QEvent base class. The
main responsibilities of the QEvent class are to bundle event parameters (by sub-
classing), to store the signal, and to provide a means of identifying the derived
classes. The QEvent class combines the last two responsibilities and assigns them
both to the sig attribute of scalar type QSignal.
When you derive from QEvent, obviously the subclasses are no longer aggregates.
However, you should still keep your event classes simple and lightweight. Keep
declaring your subclasses as structs, as a reminder that they are lightweight. In par-
ticular, you should avoid introducing constructors or virtual functions 6 in the
derived event classes. As you will see in Part II, events generally do not go thorough
conventional instantiation and cleanup (unless you use overloaded new and delete
operators), so the constructors aren’t invoked and the virtual pointers aren’t set up.
Listing 4.2 declares QSignal as an unsigned short (typically a 16-bit integer).
For most applications, the dynamic range of 216 (65,536) should be sufficient for
representing all necessary signals (even the dynamic range of a byte, 2 8 [256], typi-
cally will do). Naturally, you can redefine QSignal to use as many bits as you need.
The base class QEvent dictates that all event objects inherit the sig attribute,
which provides the uniform mechanism for event processing because state handlers
need to accept only the generic QEvent* pointer. On the flip side, this approach com-
promises type safety because a handler method doesn’t know the concrete type of the
event object passed as a generic pointer. All the handler knows is how to access the
sig attribute to infer the concrete class of the event so that it can explicitly downcast
the generic event to a concrete event. Therefore, it is crucial for the sig attribute to
provide not only the signal but a unique mapping to the concrete event class. In other
words, many signals can be mapped to the same QEvent (sub)class, but each QEvent
subclass should have only one corresponding signal (otherwise, to which class would
you downcast in your state handler method?).
The QEvent class contains other attributes (intentionally omitted in Listing 4.2),
which I discuss in Chapter 9. I had to include them in the level of QEvent, rather than
add them later via inheritance, to keep the benefits of an aggregate. For now, suffice it
to say that these data members are used to automatically recycle event objects in Part
II of this book (a specific garbage collection of the Quantum Framework).
Exercise 4.1 Redefine the signature of the state handler method in the optimal FSM
implementation (Listing 3.4 in Chapter 3) to accept a generic immutable
event pointer (QEvent const*) instead of an integer signal. Name this
new class QFsm (quantum FSM). Subsequently use this quantum FSM
design to implement the C comment parser from Chapter 3.
6. This technique precludes using run time type identification (RTTI) for identifying event classes.
88 Chapter 4: Implementing Behavioral Inheritance
4.1.2 States
When you look at the structure of the behavioral inheritance meta-pattern shown in
Figure 4.1, you might be surprised to see no State class. After all, State is the central
abstraction for a state machine, and a State class falls out automatically from every
OO analysis of the problem. Indeed, an earlier HSM implementation [Samek+ 00]
was built around such a central State class. Using state objects seemed unavoidable
because, in contrast to a basic flat state, a hierarchical state includes more than
behavior. At a minimum, it must provide a data link to its superstate to represent
state nesting, which is analogous to data links that chain together request-handler
objects in the Chain of Responsibility design pattern. However, representing states as
objects has severe drawbacks: Objects require storage and initialization, which are
big inconveniences for the clients.
However, a state handler method can provide behavior and the badly needed
structural link by returning the superstate. As simple as it seems, this was a break-
through idea for me7 because it allowed a straightforward extension of the optimal
FSM design, preserving most of its favorable properties.
From the optimal FSM implementation in Chapter 3, you might recall that the
state was represented as a pointer to the state handler method — that is, a pointer-to-
member function of the QFsm class. Exercise 4.1 arrives at the following signature.
typedef void // return type
(QFsm::* // class the function is member of
QFsmState) // name of pointer-to-member
(QEvent const *); // argument list
In the case of a hierarchical state, a state handler must additionally return the
superstate, which leads to a recursive definition of the hierarchical state handler sig-
nature. Constructing such a signature is not possible in C++ (see [Sutter 01]), so it’s
approximated by the following definition of the quantum state, QState.
typedef void (QHsm::*QPseudoState)(QEvent const *);
typedef QPseudoState // return type
(QHsm::* // class the function is member of
QState) // name of pointer-to-member
(QEvent const *); // argument list
The definition of the QState type nails down the signature of a hierarchical state
handler. Listing 4.3 shows an example of a state handler method that handles events
according to their signals (e->sig attribute) and returns the superstate if it cannot
process a given event. This particular state handler method comes from the Quantum
Calculator example discussed in Chapter 1.
7. Perhaps the main difficulty of arriving at this obvious solution was breaking with traditional OO analysis princi-
ples, which prescribe mapping abstractions to classes.
Structure 89
Listing 4.3 Example of a hierarchical state handler (refer to the statechart shown
in Figure 1.3 in Chapter 1)
A state handler can handle these signals by defining the appropriate cases in the
usual switch statement. A state handler is free to execute any actions in response to
those signals, but it should not take any state transitions in entry/exit actions. Con-
versely, it should always invoke the Q_INIT() macro to designate the initial direct
substate in response to the Q_INIT_SIG signal.
Note: The UML specification allows the initial transition to target both direct and
transitive substates. For simplicity and better efficiency, the HSM imple-
mentation restricts initial transitions to targeting the direct substates only.
The following code is an example of a state handler that uses these facilities.
QSTATE Calc::calc(QEvent const *e) {
switch (e->sig) {
case Q_ENTRY_SIG:
dispState("ready"); // entry action
return 0; // entry action executed
case Q_INIT_SIG:
clear();
Q_INIT(&Calc::ready); // initial transition
return 0; // initial transition taken
. . .
}
return (QSTATE)&Calc::top; // signal unhandled, return superstate
}
The reserved signals take up the lowest signal IDs, which are thus not available
for clients. For convenience, the public HSM interface contains the signal
Q_USER_SIG, which indicates the first signal free for client use. A simple way to
guarantee unique signals is to define them in a single enumeration. In this case,
Q_USER_SIG can be used as follows.
Structure 91
enum MySignals {
MY_KEYPRESS_SIG = Q_USER_SIG,
MY_MOUSEMOVE_SIG,
MY_MOUSECLICK_SIG,
. . .
};
If you look carefully, you might notice that a reserved signal starts with a 1 rather
than a 0. This is because signal 0 also is reserved but is used only in the internal
implementation; therefore, it is not included in the public interface presented here.
The additional reserved signal, 0 (the Empty signal Q_EMPTY_SIG), should never be
handled explicitly by a state handler. Its only purpose is to force a state handler to
return the superstate.
exit from the source to give the clients a chance to squeeze in the transition-related
actions before entering the target. This design not only put the burden on the clients
to arrange the code appropriately, but obscured the implementation and prohibited
valuable optimizations of the fragmented transition execution chain.
Yet, in my own experience with HSMs, as well as in reference statechart designs, I
have never encountered a state model in which this particular alteration in the
sequence of actions would really matter. If anything, then, the altered sequence
implemented in the Q_TRAN() macro seems more intuitive, because even the UML
specification advises: “[You should] think of a transition as belonging to the source
state” [OMG 01, section 3.80.3]. Therefore, it seems most natural to execute the
transition actions in the context of the source state (i.e., before changing the state
through invocation of the Q_TRAN() macro). The notion that transition actions are
executed in the source state is additionally reinforced in the QHsm implementation,
because you define the transition actions in the source state handler. At the same
time, the UML specification arbitrarily prescribes leaving the source state up to the
LCA and then executing the transition actions. For me, choosing the context of the
LCA for execution of the transition actions is not intuitive. To be more specific, I
believe that the transition execution sequence specified in the UML is flawed and the
proposed sequence here is correct.
More importantly, however, the altered sequence does not compromise any fun-
damental benefits of HSMs, like programming-by-difference or guaranteed initializa-
tion and cleanup via entry and exit actions. Please note that the altered transition
sequence still preserves the essential order of exiting a nested source state (from the
most deeply nested state up the hierarchy to the LCA) and entering a target state
(from the LCA state down the hierarchy to the target).
(you cannot use the top state as a transition source either, because you cannot over-
ride the QHsm::top() state handler).
top
The only customizable aspect of the top state is the initial transition (see Figure
4.2). Clients must define the initial pseudostate handler for every state machine. The
QHsm constructor enforces this condition by requiring a pointer to the initial pseu-
dostate handler as an argument (Listing 4.1, line 7). The initial pseudostate handler
defines only the initial transition, which must designate the default state of the state
machine nested inside the top state (via the Q_INIT() macro). The initial transition
can also specify arbitrary actions (typically initialization). The following code is an
example of an initial pseudostate handler (from the Quantum Calculator example in
Chapter 1).
void Calc::initial(QEvent const *) {
clear(); // perform initializations...
Q_INIT(&Calc::calc); // designate the default state
}
The QHsm constructor intentionally does not execute the initial transition
defined in the initial pseudostate because the clients don’t have precise control
over the timing when the C++ run time executes constructors.8 Instead, a state
machine comes out of the instantiation process in the initial pseudostate. Later,
the client code must trigger the initial transition explicitly by invoking init()
(described in the next section). This process separates instantiation of the state
machine from initialization, giving the clients full control over the sequence of
initializations in the system, which can be important because state machines are
often Singletons9 that are statically allocated and instantiated in an arbitrary order
by the C++ run time prior to invoking main(). During the static instantiation, some
vital objects can be missing, hardware might not be properly initialized yet, and mul-
titasking is typically not yet enabled.
8. For example, state machine objects are often static, in which case the C++ run time instantiates them before
invoking main().
9. For example, the Quantum Calculator from Chapter 1 is a Singleton (see the Singleton design pattern in
Gamma and colleagues [Gamma+ 95]).
94 Chapter 4: Implementing Behavioral Inheritance
Please note that the topmost initial transition can fire only once (actually, exactly
once), because after you leave the top state, you cannot transition back. In other
words, your state machine cannot reuse the initial pseudostate in its life cycle.
Exercise 4.2 Propose an alternative design for the state machine initialization that
would implement an initial transition as a polymorphic initial()
method (a virtual function in C++), rather than as an initial pseu-
dostate. This method should be abstract (purely virtual) in the QHsm class
so that clients would have to override it.
Exercise 4.3 Extend the previous design by adding another polymorphic method,
onUnhandled(), which would be invoked from the top state handler,
instead of silently discarding a user-defined signal. The default imple-
mentation should do nothing, but clients could override it to customize
the treatment of such signals (e.g., treat them as errors).
10. This statechart also provides an exhaustive test case for the underlying internal implementation of the event pro-
cessor described in Section 4.4.
An Annotated Example 95
s0
entry/ c s2
exit/ h[!foo]/
s1 entry/
exit/ foo=1;
entry/ c
exit/ s21
d s11 entry/
f g
entry/ exit/
exit/ s211
h[foo]/foo=0; f b entry/
a e
b d exit/
g
For simplicity, this statechart uses only a text-based interface. Most actions con-
sist only of printf() statements that report the status of the state machine. You can
regard these statements as a primitive instrumentation of the code.
In this section, I present the C++ implementation; however, I will present the C
implementation of this statechart later in this chapter (Section 4.5.5). The accompa-
nying CD-ROM contains complete code for both the C and C++ versions.
Listing 4.4 Enumerating signals and subclassing QHsm; the unusual indentation
used to declare state handler methods indicates state nesting
1 #include "qf_win32.h"
2
3 enum QHsmTstSignals {
4 A_SIG = Q_USER_SIG, // user signals start with Q_USER_SIG
5 B_SIG, C_SIG, D_SIG, E_SIG, F_SIG, G_SIG, H_SIG
6 };
7
8 class QHsmTst : public QHsm { // QHsmTst derives from QHsm
9 public:
10 QHsmTst() : QHsm((QPseudoState)initial) {} // default Ctor
11 private:
12 void initial(QEvent const *e); // initial pseudostate
13 QSTATE s0(QEvent const *e); // state-handler
14 QSTATE s1(QEvent const *e); // state-handler
15 QSTATE s11(QEvent const *e); // state-handler
16 QSTATE s2(QEvent const *e); // state-handler
17 QSTATE s21(QEvent const *e); // state-handler
18 QSTATE s211(QEvent const *e); // state-handler
19 private: // extended state variables...
20 int myFoo;
21 };
Listing 4.5 Definition of the state handler methods of the QHsmTst class
14 return (QSTATE)&QHsmTst::top;
15 }
16
17 QSTATE QHsmTst::s1(QEvent const *e) {
18 switch (e->sig) {
19 case Q_ENTRY_SIG: printf("s1-ENTRY;"); return 0;
20 case Q_EXIT_SIG: printf("s1-EXIT;"); return 0;
21 case Q_INIT_SIG: printf("s1-INIT;"); Q_INIT(&QHsmTst::s11);
return 0;
22 case A_SIG: printf("s1-A;"); Q_TRAN(&QHsmTst::s1); return 0;
23 case B_SIG: printf("s1-B;"); Q_TRAN(&QHsmTst::s11); return 0;
24 case C_SIG: printf("s1-C;"); Q_TRAN(&QHsmTst::s2); return 0;
25 case D_SIG: printf("s1-D;"); Q_TRAN(&QHsmTst::s0); return 0;
26 case F_SIG: printf("s1-F;"); Q_TRAN(&QHsmTst::s211); return 0;
27 }
28 return (QSTATE)&QHsmTst::s0;
29 }
30
31 QSTATE QHsmTst::s11(QEvent const *e) {
32 switch (e->sig) {
33 case Q_ENTRY_SIG: printf("s11-ENTRY;"); return 0;
34 case Q_EXIT_SIG: printf("s11-EXIT;"); return 0;
35 case G_SIG: printf("s11-G;"); Q_TRAN(&QHsmTst::s211); return 0;
36 case H_SIG: // internal transition with a guard
37 if (myFoo) { // test the guard condition
38 printf("s11-H;");
39 myFoo = 0;
40 return 0;
41 }
42 break;
43 }
44 return (QSTATE)&QHsmTst::s1;
45 }
46
47 QSTATE QHsmTst::s2(QEvent const *e) {
48 switch (e->sig) {
49 case Q_ENTRY_SIG: printf("s2-ENTRY;"); return 0;
50 case Q_EXIT_SIG: printf("s2-EXIT;"); return 0;
51 case Q_INIT_SIG: printf("s2-INIT;"); Q_INIT(&QHsmTst::s21);
return 0;
52 case C_SIG: printf("s2-C;"); Q_TRAN(&QHsmTst::s1); return 0;
53 case F_SIG: printf("s2-F;"); Q_TRAN(&QHsmTst::s11); return 0;
54 }
55 return (QSTATE)&QHsmTst::s0;
56 }
57
58 QSTATE QHsmTst::s21(QEvent const *e) {
98 Chapter 4: Implementing Behavioral Inheritance
59 switch (e->sig) {
60 case Q_ENTRY_SIG: printf("s21-ENTRY;"); return 0;
61 case Q_EXIT_SIG: printf("s21-EXIT;"); return 0;
62 case Q_INIT_SIG: printf("s21-INIT;"); Q_INIT(&QHsmTst::s211);
return 0;
63 case B_SIG: printf("s21-C;"); Q_TRAN(&QHsmTst::s211); return 0;
64 case H_SIG: // self transition with a guard
65 if (!myFoo) { // test the guard condition
66 printf("s21-H;");
67 myFoo = 1;
68 Q_TRAN(&QHsmTst::s21); // self transition
69 return 0;
70 }
71 break; // break to return the superstate
72 }
73 return (QSTATE)&QHsmTst::s2; // return the superstate
74 }
75
76 QSTATE QHsmTst::s211(QEvent const *e) {
77 switch (e->sig) {
78 case Q_ENTRY_SIG: printf("s211-ENTRY;"); return 0;
79 case Q_EXIT_SIG: printf("s211-EXIT;"); return 0;
80 case D_SIG: printf("s211-D;"); Q_TRAN(&QHsmTst::s21); return 0;
81 case G_SIG: printf("s211-G;"); Q_TRAN(&QHsmTst::s0); return 0;
82 }
83 return (QSTATE)&QHsmTst::s21; // return the superstate
84 }
Translating the statechart from Figure 4.3 into code is straightforward and
requires adherence to just a few simple rules. Consider, for instance, the
QHsmTst::s21() state handler (Listing 4.5, lines 58–74). First, look up this state in
the diagram and trace around its state boundary. You need to implement all transi-
tions originating at this boundary, as well as all internal transitions enlisted in this
state. Additionally, if an initial transition is embedded directly in the state, you need
to implement it as well. For state s21, the transitions that originate at the boundary
are transition b and self-transition h. In addition, the state has an entry action, an
exit action, and an initial transition.
Coding of entry and exit actions is the simplest. You just intercept the reserved
signals Q_ENTRY_SIG or Q_EXIT_SIG, enlist actions you want to execute, and termi-
nate the lists with return 0 (Listing 4.5, lines 60, 61).
To code the initial transition, you intercept the reserved signal Q_INIT_SIG, enlist
the actions, and then designate the target substate through the Q_INIT() macro (line
62), after which you exit the state handler with return 0.
An Annotated Example 99
You code a regular transition in a very similar way, except that you intercept a
custom-defined signal (e.g., B_SIG, line 63), and you use the Q_TRAN() macro to des-
ignate the target state. Again, you exit state handler with return 0.
Coding a transition with a guard is a little more involved. Lines 64 through 71
of Listing 4.5 show how to handle this case. As before, you intercept the custom sig-
nal (here, H_SIG), except now you test the guard condition inside an if (…) state-
ment (line 65) first. You place the transition actions, Q_TRAN() macro, and return 0
inside the TRUE branch of the if statement (lines 66–69). Because the return is
placed inside the if statement, the code following the if statement executes only
when the guard expression evaluates to FALSE. When that happens, you break out of
the switch (line 71) and return the superstate from the state handler (to indicate
that the event has not been handled).
The last step of every state handler designates the superstate by returning the cor-
responding state handler to the caller. In the case of state s21, this is the s2() state
handler (line 73). Please note the necessary type cast to QSTATE.
Listing 4.5 demonstrates many more examples of the simple rules just mentioned.
Note that this implementation automatically handles the execution of transition
chains — that is, the computation of the LCA state and the execution of appropriate
exit actions, entry actions, and initial transitions. Consequently, no manual coding of
transition chains is necessary.
12 char c = getc(stdin);
13 getc(stdin); // discard '\n'
14 if (c < 'a' || 'h' < c) { // character out of range?
15 return 0; // terminate
16 }
17 test.dispatch(&testEvt[c - 'a']); // dispatch event
18 }
19 return 0;
20 }
Exercise 4.4 Find the Qhsmtst.exe application on the accompanying CD-ROM and
execute it. Try out all possible transitions. Compare the application out-
put with the test log from Listing 4.7.
actions, as well as the active state (the last state entered). For instance, in line 3, the
state machine is in state s11 because it is the last state entered in the previous line.
Signal a injected in this line triggers a self-transition inherited from state s1. The
statechart responds in line 4 by executing the transition sequence described in Sec-
tion 4.1.4.
Interestingly, in hierarchical state machines, the same transition can cause differ-
ent behavior, depending on which state inherits the transition. For example, in lines 5
and 7, the injection of signal e triggers the same state transition each time (the state-
chart has only one transition e in state s0). However, the responses of the state
model in lines 6 and 8 are different because transition e fires from different state con-
figurations — once when s11 is active (line 6) and next when s211 is active (line 8).
Additionally, in extended state machines, the response depends not only on the
stimulus and the state but on the value of the extended state variables. For example,
signal h triggers a transition in line 12 but is not handled in line 14, although the
state machine remains in the same state, s211. From the inspection of the statechart
in Figure 4.3, you can see that transition h in state s21 has a guard, which once eval-
uates to TRUE and the next time to FALSE.
After experimenting for a while with the QHsmTst statechart, you might want to
modify it to try out different state topologies. In fact, in this implementation of the
HSM, it is easy to change any aspect of the state model, even late in the develop-
ment process.11 The following two exercises give you some ideas of how you can
modify the statechart to learn even more from this example.
Exercise 4.5 Modify the state machine by moving transition e from s0 to s2 and by
changing the target of transition f in state s1 from s211 to s21.
Exercise 4.6 Change the initial transition in state s2 to target the s211 state rather
than s21 and observe the assertion failure on transition c. Change the
target of transition g in state s211 to top (see Section 4.1.4) and observe
another assertion failure. Explain the reasons for breaking the assertions.
11. It is even easier to change the code than to redraw the state diagram.
102 Chapter 4: Implementing Behavioral Inheritance
machine code that is easy to maintain. Although illustrated in C++, most of the
guidelines apply equally well to the C implementation.
Exercise 4.7 Most design automation tools capable of translating statechart diagrams
to code internally represent statecharts in textual format. For example,
the ROOM method [Selic+ 94] defines a ROOM linear form representa-
tion, which is capable of capturing HSMs among other things. Try to
invent your own textual notation, succinct yet expressive, to represent
statecharts. Use your notation to write down a specification for state s21
of the statechart from Figure 4.3. Compare it with the QHsmTst::s21()
state handler code in Listing 4.5.
would unnecessarily bloat the state handler methods. Instead, the Quantum Calcu-
lator statechart represents the whole group of numerals 1 through 9 as one signal,
IDC_1_9.
The granularity of signals is too coarse if you find yourself frequently using guard
conditions that test event parameters. In this case, event parameters are the de facto
signals. Consider the Windows message (signal) WM_COMMAND, frequently used in
Windows GUI applications (e.g., Listing 1.2 in Chapter 1). This signal is too coarse,
because clients typically must test parameters associated with the WM_COMMAND (most
frequently LOWORD(wParam)) to choose the desired behavior. In other words, values
of LOWORD(wParam) are the de facto signals. In this case, the too-coarse signal gran-
ularity results in a suboptimal (and not very elegant) additional switch statement.
When you encounter signals that are too coarse, the first thing you should try is to
redefine or remap signals to the right level of granularity (the Quantum Calculator
application exemplifies the remapping option). However, if you cannot do this, you
should include all the de facto signals directly in your state handlers. All too often,
the additional layer of signal dispatching is moved to separate methods, which
makes state handlers incomplete (in the sense discussed in Section 4.3.1).
exactly one of its substates is active” [OMG 01, page 2-162]. In view of the OO
analogy between state hierarchies and class taxonomies, if this rule were applied to
classes, it would require that every class that has subclasses must necessarily be
abstract (cannot have instances). However, it often happens that an abstraction cap-
tured in a class can be used directly (instantiated) in some parts of the code. Indepen-
dently, someone else might specialize this abstraction by creating subclasses, but this
should not break the earlier code that instantiated the original class. You cannot
foresee whether someone will subclass your class (unless it is explicitly abstract), nei-
ther can you prevent subclassing.12
Following the OO analogy, it is logical to relax the UML rule and allow compos-
ite states to become active without any of their substates being active. Consider, for
example, the C comment parser state machine discussed in Chapter 3. Figure 4.4a
shows a UML-compliant hierarchical version of the C comment parser state machine
(compare it with the classical FSM shown in Figure 3.1 in Chapter 3). Unfortunately,
the hierarchical version is significantly more complex than the flat FSM (it adds two
more states and two more initial transitions) because the high-level code and com-
ment states provide only groupings for their substates and add little toward seman-
tics. The non-UML-compliant version (Figure 4.4b), on the other hand, is no more
complex than the classical FSM because it has exactly the same number of states and
transitions. The difference between the UML-compliant and the -noncompliant ver-
sions is that the code and comment states each has only one substate, which is not
necessarily active when its superstate is active (note the lack of initial transitions in
the code and comment states).
Figure 4.4 An HSM representation of the C comment parser from Chapter 3; (a)
UML-compliant, (b) not UML-compliant
(a) (b)
code code
SLASH
SLASH slash
code_char slash
CHAR, SLASH
CHAR, SLASH SLASH STAR
SLASH STAR comment
comment CHAR/
STAR com_char SLASH/ STAR
12. In Java, you can use the final keyword, but in C++, you generally cannot prevent subclassing.
106 Chapter 4: Implementing Behavioral Inheritance
The advantage of the hierarchical version over the flat state machine from Chap-
ter 3 is that HSMs are more intuitive and extensible. For example, when the classical
FSM (Figure 3.1 in Chapter 3) encounters a slash character while in the code state, it
transitions to the slash state, leaving the code state. However, the slash character
also can represent a division operator, so conceptually, the parser should remain in
the code state until it collects enough evidence for a conclusive decision that the
parser is no longer parsing code. This is the exact behavior of both hierarchical mod-
els (Figure 4.4a, b). All things being equal, however, the model in Figure 4.4b is
much simpler than the model in Figure 4.4a.
Exercise 4.8 Implement the HSM from Figure 4.4b in C and in C++. Subsequently,
extend the hierarchical C comment parser to count the number of com-
ment blocks (/* … */), as well as the number of comment characters
(see Exercise 3.10 in Chapter 3). Hint: Count the number of entries into
the comment state.
The goal of this macro is to present one of the reserved signals (Q_EMPTY_SIG,
Q_ENTRY_SIG, Q_EXIT_SIG, or Q_INIT_SIG) to a given state handler, state_.
Please note the characteristic syntax of handler method invocation based on the
The Event Processor 107
13. For example, embedded C++ (EC++) compilers don’t support the new type casts.
108 Chapter 4: Implementing Behavioral Inheritance
13 s = myState;
14 TRIGGER(s, Q_ENTRY_SIG); // enter the substate
15 }
16 }
Listing 4.8 shows the definition of the init() method. The preconditions14 in
lines 2 and 3 assert that the HSM has not been executed yet (myState points to the
top state) and that mySource has been initialized to the initial pseudostate handler
by the QHsm constructor. The initial transition of the top submachine is triggered in
line 5. According to the limitation mentioned in Section 4.1.3, the initial transition
can target only a direct substate of a given state, which init() asserts in line 7. The
while loop in line 10 triggers an initial transition in the current state and tests the
return value to find out if the state handler has actually handled the transition. If so
(the state handler returns 0), the body of the loop asserts that the target is a direct
substate of the source (lines 11, 12) and then enters the target substate. The loop
continues until there are no more initial transitions to take.
not exactly recursive (see Section 4.1.2), the Q_STATE_CAST() type cast in line 3 of
Listing 4.9 is necessary.
Exercise 4.9 The QHsm class provides the “is-in-state” query (see the declaration of
isIn() in Listing 4.1, line 11). Write the body of this method. Note that
in HSMs, to be in a state also means to be in all substates of that state.
Hint: You can implement the is-in-state query using the same state hier-
archy traversal as the dispatch() method.
Executing state transitions is by far the most complex part of the HSM imple-
mentation. Figure 4.5 illustrates the challenge. This diagram shows the inheritance
tree of states comprising the Quantum Calculator statechart (Figure 1.3 in Chapter
1) and two exemplary transitions. As described earlier in this chapter (Section
15. Chapter 6 presents another example (related to inheritance of entire state machines) in which you need to use
dynamic state transitions.
110 Chapter 4: Implementing Behavioral Inheritance
4.1.4), a transition execution sequence involves the exit of all states up to the LCA,
then recursive entry into the target state. Exiting the current state configuration is
relatively straightforward because it follows the natural direction of navigation
through the state hierarchy (denoted by the behavioral inheritance arrow in Figure
4.5). However, the entry to the target requires navigating in the opposite direction
(recall that state handlers return only the superstate).
The solution to the problem of entering the target state configuration is to first
record the exit path from the target to the LCA, without executing any actions. You
can do this by dispatching the reserved empty signal (Section 4.1.3), which causes
every state handler to return the superstate without causing any side effects. 16 After
the exit path has been recorded in that way, it can easily be turned into the entry path
by playing it backwards, which is the desired order.17
This strategy immediately suggests an optimization. Instead of rediscovering the
entry path every time, one might as well store it permanently in a static object and
subsequently reuse the path information for a much more efficient execution of the
transition. However, this works only for static transitions (coded with Q_TRAN()),
where the target never changes at run time. Dynamic transitions (coded with
Q_TRAN_DYN()) cannot use this optimization and must determine the transition exe-
cution sequence every time.
16. It is the responsibility of the client (you) to design state handler methods in such a way that the empty signal (0)
causes no side effects.
17. One of the first documented uses of this method was to get rid of the mythological Minotaur (half-man, half-
bull monster on the island of Crete). The Athenian hero Theseus unraveled a ball of thread on his way to the
Minotaur’s labyrinth, killed the beast, and followed the thread to find his way out.
The Event Processor 111
In the next section, I discuss the somewhat simpler QHsm::tran() dynamic tran-
sition, and in the following section, I cover the QHsm::tranStat() static transition
as an optimization of the first technique.
Only after exiting all states up to the source of the transition can tran() proceed
with the second step, which is execution of the transition itself. This step tries to
optimize the workload by minimizing the number of “probing” invocations of state
handlers with empty signals (i.e., with the sole purpose of eliciting the superstate).
The optimization relies on testing directly for all the simplest source–target state con-
figurations, which are most likely to occur in practice. Moreover, the strategy is to
order these configurations in such a way that the information about the state config-
uration obtained from earlier steps can be used in later steps. Figure 4.7 shows such
ordering of state transition topologies, and Table 4.1 (page 115) enlists the tests
required to determine a given configuration.
Figure 4.7 Ordering of all possible source and target state configurations used in
QHsm::tran()
Exercise 4.11 Compare Figure 4.7 with the example statechart from Figure 4.3 on
page 95. Convince yourself that transitions a through g in the example
statechart correspond to the cases enumerated in Figure 4.7 (e.g., signal
a triggers the state transition described in Figure 4.7a, and so on).
11 s = TRIGGER(s, Q_EMPTY_SIG);
12 }
13 }
14
15 *(e = &entry[0]) = 0;
16 *(++e) = target; // assume entry to target
17
18 // (a) check mySource == target (transition to self)
19 if (mySource == target) {
20 TRIGGER(mySource, Q_EXIT_SIG); // exit source
21 goto inLCA;
22 }
23 // (b) check mySource == target->super
24 p = TRIGGER(target, Q_EMPTY_SIG);
25 if (mySource == p) {
26 goto inLCA;
27 }
28 // (c) check mySource->super == target->super (most common)
29 q = TRIGGER(mySource, Q_EMPTY_SIG);
30 if (q == p) {
31 TRIGGER(mySource, Q_EXIT_SIG); // exit source
32 goto inLCA;
33 }
34 // (d) check mySource->super == target
35 if (q == target) {
36 TRIGGER(mySource, Q_EXIT_SIG); // exit source
37 --e; // do not enter the LCA
38 goto inLCA;
39 }
40 // (e) check rest of mySource == target->super->super... hierarchy
41 *(++e) = p;
42 for (s = TRIGGER(p, Q_EMPTY_SIG); s;
43 s = TRIGGER(s, Q_EMPTY_SIG))
44 {
45 if (mySource == s) {
46 goto inLCA;
47 }
48 *(++e) = s;
49 }
50 TRIGGER(mySource, Q_EXIT_SIG); // exit source
51 // (f) check rest of mySource->super == target->super->super...
52 for (lca = e; *lca; --lca) {
53 if (q == *lca) {
54 e = lca - 1; // do not enter the LCA
55 goto inLCA;
56 }
57 }
114 Chapter 4: Implementing Behavioral Inheritance
Figure 4.8 The entry path to the target state recorded in entry[]; pointer e
points to the entry last filled; pointer lca points to the LCA in the steps
shown in Figure 4.7f and g; lca - 1 points to the first state that needs
to be entered in the steps shown in Figure 4.7f and g (see Listing 4.10)
entry[0] 0
record entry[1] target
execute entry[2] target->super
... ... lca-1
lca
e entry[n] e.g., top
entry[..] unused . . .
Table 4.1 Processing source–target state configurations from Figure 4.7; line
numbers refer to Listing 4.10
18. Of course, you can change this number to anything you like by redeclaring entry[] in line 3 of Listing 4.10.
19. This number is arbitrary, and you can change it for your particular application.
The Event Processor 117
Now you can understand that specifying a transition requires using the preproces-
sor macro Q_TRAN(), rather than directly invoking tranStat(), because every tran-
sition requires a separate static storage for the associated transition object. As shown
in Listing 4.1, line 26, Q_TRAN() defines such a static Tran object for every transi-
tion. To localize the scope of this object to a given transition, the macro wraps it in a
dummy (optimized away) if (1) {…} else statement, so that you can safely use it
as a single instruction terminated with a semicolon (even inside compound if state-
ments, without causing the dangling else problem).
Otherwise (lines 18–24), the transition boils down to traversing the prerecorded state
chain myChain and at each step triggering the appropriate signal (encoded in two bits
of the myActions bit mask) to the appropriate state handler.
Exercise 4.12 The event processor comprises the methods init(), dispatch(),
tran(), and tranStat() and controls all aspects of state machine exe-
cution. It is relatively easy to instrument the event processor code by
introducing “hooks” (callback methods) that are invoked under specific
circumstances. For example, try instrumenting an active-state hook that
is invoked whenever the active state changes. Most of the commercial
code-synthesizing tools use such hooks, among others, to animate state
diagrams during state transitions.
15
16 // (a) check mySource == target (transition to self)
17 if (mySource == target) {
18 RECORD(mySource, Q_EXIT_SIG); // exit source
19 goto inLCA;
20 }
21 // (b) check mySource == target->super
22 p = TRIGGER(target, Q_EMPTY_SIG);
23 if (mySource == p) {
24 goto inLCA;
25 }
26 // (c) check mySource->super == target->super (most common)
27 q = TRIGGER(mySource, Q_EMPTY_SIG);
28 if (q == p) {
29 RECORD(mySource, Q_EXIT_SIG); // exit source
30 goto inLCA;
31 }
32 // (d) check mySource->super == target
33 if (q == target) {
34 RECORD(mySource, Q_EXIT_SIG); // exit source
35 --e; // do not enter the LCA
36 goto inLCA;
37 }
38 // (e) check rest of mySource == target->super->super... hierarchy
39 *(++e) = p;
40 for (s = TRIGGER(p, Q_EMPTY_SIG); s;
41 s = TRIGGER(s, Q_EMPTY_SIG))
42 {
43 if (mySource == s) {
44 goto inLCA;
45 }
46 *(++e) = s;
47 }
48 RECORD(mySource, Q_EXIT_SIG); // exit source
49 // (f) check rest of mySource->super == target->super->super...
50 for (lca = e; *lca; --lca) {
51 if (q == *lca) {
52 e = lca - 1; // do not enter the LCA
53 goto inLCA;
54 }
55 }
56 // (g) check each mySource->super->super..for each target...
57 for (s = q; s; s = TRIGGER(s, Q_EMPTY_SIG)) {
58 for (lca = e; *lca; --lca) {
59 if (s == *lca) {
120 Chapter 4: Implementing Behavioral Inheritance
4.5 C Implementation
The C++ implementation of the behavioral inheritance meta-pattern is fundamen-
tally object oriented in that it takes advantage of data abstraction (packaging data
with functions into classes) and inheritance (the capability to define new classes
based on existing classes).
You can code such a design in a procedural language such as C, because, as men-
tioned in the introduction to this chapter, abstraction and inheritance are only rela-
tively low-level meta-patterns, just as behavioral inheritance is. Therefore, they can
be used in virtually any programming language, not necessarily an object-oriented
one. Appendix A describes techniques for implementing these concepts in C as a set
of idioms and preprocessor macros that I call “C+.”
In fact, “C+” implements the C++ object model so faithfully, that converting the
implementation of any C++ design into “C+” involves mostly a mechanical applica-
tion of simple translation rules. Moreover, the exercises used in Appendix A to illus-
trate “C+” concepts already prepare most of the elements for the “C+” HSM
C Implementation 121
implementation. Therefore, in this section I just fill in a few missing pieces and high-
light only the most interesting parts of the code. The complete “C+” behavioral
inheritance implementation is available on the accompanying CD-ROM.
Note: Although I call it by the strange name “C+,” rest assured that the imple-
mentation is fully portable, ANSI C–compliant code, although it looks very
much like C++.
Please note that the C++ version of the QHsm class intentionally avoids polymor-
phism because it isn’t necessary for the most common uses of HSMs. Therefore,
although the “C+” QHsm class supports it,20 you can ignore polymorphism when you
derive your own state machines from it.
Before proceeding any further, you should skim through Appendix A (you can
skip the description of polymorphism at first), so that you will understand the “C+”
macros, naming conventions, and the idiomatic use of C in this section.
me->source__ = (QState)initial;
return me; // return success
}
As in the C++ case, the declaration of QState cannot be fully recursive (i.e., the
state handler cannot return a state handler [Sutter 01]); instead, the return type is
approximated by the QPseudoState pointer-to-member function.
argument. You use this pointer subsequently to access class attributes. The following
example of the QHsmDispatch() method illustrates all these elements.
void QHsmDispatch(QHsm *me, QEvent const *e) {
for (me->source__ = me->state__; me->source__;
me->source__ = (QState)(*me->source__)(me, e))
{}
}
The most interesting part of this method is the invocation of the state handler
(emphasized) based on the pointer-to-member function (*me->source__)(me, …)
followed by the cast of the return type (QPseudoState) to QState.
Exercise 4.13 Using QHsmDispatch() as a template, translate the rest of the QHsm class
methods from C++ to “C+.”
1 #include "qhsm.h"
2
3 SUBCLASS(QHsmTst, QHsm)
4 int foo__; /* private extended state variable */
5 METHODS
6 QHsmTst *QHsmTstCtor(QHsmTst *me);
7
8 void QHsmTst_initial(QHsmTst *me, QEvent const *e);
9 QSTATE QHsmTst_s0(QHsmTst*me, QEvent const *e);
10 QSTATE QHsmTst_s1(QHsmTst*me, QEvent const *e);
11 QSTATE QHsmTst_s11(QHsmTst*me, QEvent const *e);
12 QSTATE QHsmTst_s2(QHsmTst*me, QEvent const *e);
13 QSTATE QHsmTst_s21(QHsmTst*me, QEvent const *e);
14 QSTATE QHsmTst_s211(QHsmTst*me, QEvent const *e);
15 END_CLASS
16
17 QHsmTst *QHsmTstCtor(QHsmTst *me) {
C Implementation 125
18 QHsmCtor_(&me->super_, (QPseudoState)QHsmTst_initial);
19 return me;
20 }
21
22 void QHsmTst_initial(QHsmTst *me) {
23 printf("top-INIT;");
24 me->foo__ = 0; /* initialize extended state variable */
25 Q_INIT(QHsmTst_s0);
26 }
27
28 QSTATE QHsmTst_s0(QHsmTst *me, QEvent const *e) {
29 switch (e->sig) {
30 case Q_ENTRY_SIG: printf("s0-ENTRY;"); return 0;
31 case Q_EXIT_SIG: printf("s0-EXIT;"); return 0;
32 case Q_INIT_SIG: printf("s0-INIT;"); Q_INIT(QHsmTst_s1); return 0;
33 case E_SIG: printf("s0-E;"); Q_TRAN(QHsmTst_s211); return 0;
34 }
35 return (QSTATE)QHsm_top;
36 }
37 . . . /* other state handlers */
38
39 static QHsmTst test;
40
41 int main() {
42 . . .
43 QHsmTstCtor(&test, (QPseudoState)QHsmTst_initial);
44 QHsmInit((QHsm *)&test, 0);
45 for (;;) {
46 . . . /* receive event */
47 QHsmDispatch((QHsm *)&test, &e); /* dispatch event*/
48 }
49 . . .
50 }
Listing 4.14 shows the most interesting implementation details. The state model
class (QHsmTst) is declared in lines 3 through 15. Please note that QHsmTst does not
declare a virtual table; therefore, it will not support polymorphism. Consequently,
there is no definition of VTABLE, and the constructor (lines 17–20) does not hook the
virtual pointer (inherited from QHsm). The only initialization that the
QHsmTstCtor() constructor performs is the invocation of the QHsmCtor() super-
class constructor in line 18. In lines 22 through 26 you see the definition of the ini-
tial pseudostate, whereas in lines 28 through 36 you see an example of a state
handler, which illustrates, among other things, how a state handler returns its super-
state (line 35). Finally, in the test harness, don’t forget to invoke the constructor (line
43) explicitly before initialization of the state machine (line 44). You dispatch events
126 Chapter 4: Implementing Behavioral Inheritance
to the state machine in the usual way by calling QHsmDispatch(). Please note the
explicit type casting (upcasting) used when calling methods inherited from the QHsm
class on behalf of the QHsmTst object (lines 44 and 47).
Exercise 4.14 Implement in “C+” the rest of the state machine from Figure 4.3. Exe-
cute a test session as logged in Listing 4.7.
Exercise 4.15 Implement in “C+” the Quantum Calculator GUI application from
Chapter 1.
4.6 Caveats
The HSM implementation has a few pitfalls, which most often will cause contract
violations at run time (see the sidebar “Design by Contract in C/C++” on page 107)
but occasionally can lead to subtle bugs. In this section, I point out some malformed
HSMs that you could construct by instantiating the behavioral inheritance meta-pat-
tern incorrectly.
Perhaps the most far-reaching assumption of this HSM implementation is that
state machine topology is static (i.e., it does not change at run time). This assumption
corresponds roughly to statically defined class hierarchy in OOP and, in general pro-
gramming, to the assumption that code does not modify itself. Whereas normally,
state machine topology is indeed fully defined at compile time, some coding styles
could lead to unintentional modifications of the transition topology at run time.
Consider, for instance, the following state handler.
QState MyHsm::stateA(QEvent const *e) {
switch (e->sig) {
. . .
case MYSIG1_SIG:
Q_TRAN((...) ? (QState)stateB : (QState)stateC); // WRONG!!!
return 0;
. . .
}
return (QState)top;
}
The MyHsm::stateA() state handler violates the assumption of static state tran-
sition because the target of the transition changes at run time. Looking into the
Q_TRAN() macro definition, you can see that only one static transition object gets
instantiated and that it cannot store two different transition chains. In this case, only
Caveats 127
one transition chain is recorded in the transition object (targeting whichever state
happens to be picked by the condition evaluated the first time through). Subse-
quently, the condition (choice point) will have no effect. The correct way of coding
the choice point is to use two (or more) statically defined transitions as follows.
QState MyHsm::stateA(QEvent const *e) {
switch (e->sig) {
. . .
case MYSIG1_SIG:
if (...)
Q_TRAN(stateB);
else
Q_TRAN(stateC);
return 0;
. . .
}
return (QState)top;
}
The idea here is to store the active substate (deep history) of stateA upon exit
and restore it in the initial transition. However, this solution makes the initial transi-
tion nonstatic (it changes at run time). Please note that you cannot introduce a choice
point here as before because choice points (or junctions for that matter) are not
allowed on initial transitions. Chapter 5 shows the correct way to implement transi-
tions to history as the History state pattern.
Another potential source of problems is confusing macros Q_INIT() and
Q_TRAN(). One could rightfully argue that a single macro (Q_TRAN()) should be suf-
ficient to implement all kinds of transitions, including initial transitions. Indeed, such
an implementation is possible, but not optimal. For one thing, using Q_TRAN() in
128 Chapter 4: Implementing Behavioral Inheritance
4.7 Summary
So here it is: the optimized behavioral inheritance meta-pattern. Admittedly, it is only
a drastically simplified subset of UML state machines, but through its full support
for the profound concept of behavioral inheritance (state hierarchy), it forms the
foundation for adding other features.
The following bullet items quickly recapitulate how this implementation measures
up against the initial goals.
• It is simple to use and maintain. Defining HSMs requires subclassing the QHsm
class. Defining states corresponds to adding state handler methods to the derived
class, which you can do at any time, even late in the development process. State
handler methods are an inexpensive commodity, and there are no limits (except
for code space) on how many you can use.
• It allows changing state machine topology easily. In particular, no transition
chains must be coded manually. For instance, to change the target of a transition,
you modify the argument of the Q_TRAN() macro. Similarly, to change the super-
state of a given state, you modify the final return statement in the corresponding
state handler. All these changes are confined to one line of code.
• It provides good run-time efficiency and has a small memory footprint. Dispatch-
ing events to a state machine involves dereferencing a function pointer and is
comparable to the virtual function invocation in C++.21 The QHsm class adds only
Summary 129
two pointers to the subclasses. Complete event processor code requires about
2KB of code space.
• It does not force you to pay for what you don’t use. For example, transitions to
history and event deferral typically impose memory and run-time overhead, even
if not used; therefore, they are not implemented at the fundamental level of the
behavioral inheritance meta-pattern. However, you can easily add these features
for specific states as state patterns (Chapter 5) and only pay for what you actually
use.
Please note that this HSM implementation provides only the event processor com-
ponent of a state machine, which processes dispatched event instances according to
the general semantics of UML state machines. The implementation intentionally
omits event queuing and event dispatching mechanisms, which are also necessary
components of a hypothetical state machine [OMG 01]. The goal of this implemen-
tation is to provide a generic event processor that can be used with any event queu-
ing and dispatching mechanism. This approach allows the behavioral inheritance
pattern to fit easily into existing event-driven environments that already support
event queuing and dispatching, most notably GUI frameworks (recall the Quantum
Calculator GUI application). In Part II of this book, I show you concrete ways in
which to implement event queuing and dispatching that is suitable for real-time
embedded applications.
21. Assuming that the event is handled in the lowest level of nesting, inheriting behavior from superstates requires
invocation of their sate-handler methods, which incurs some additional overhead.
130 Chapter 4: Implementing Behavioral Inheritance
5
Chapter 5
State Patterns
Science is a collection of successful recipes.
— Paul Valery (1871–1945)
In the previous chapter, you learned how to implement hierarchical state machines
(HSMs) in C++ and in C by instantiating the behavioral inheritance meta-pattern. In
fact, applying the pattern turned out to be a rather simple one-to-one mapping
between a state model and the code. With just a bit of practice, you will forget that
you are translating state models into code; rather, you will directly code state
machines in C or C++, just as you directly code classes in C++ or Java.
At this point, you will no longer struggle with convoluted if–then–else state-
ments and gazillions of flags. You will start thinking at a higher level of abstraction
about the best ways to partition behavior into states, about the events available at
any given time, and about the structure of your state machine.
However, coming up with a good structure for nontrivial state machines isn’t
easy. Experienced reactive-system designers know that a reusable and flexible state
machine design is difficult to get right the first time. Yet, experienced designers
repeatedly realize good state machines, whereas new designers are overwhelmed by
the options available and tend to fall back on convoluted if–then–else statements
and the multitude of flags they have used before.
131
132 Chapter 5: State Patterns
One thing that distinguishes an expert from a novice is the ability to recognize the
similarities among problems encountered in the past and to reuse proven solutions
that work. To share their expertise, OO designers began to catalog proven solutions
to recurring problems as OO design patterns [Gamma+ 95]. Similarly, state patterns
began to appear [Douglass 99]. In contrast to the OO patterns, which are concerned
with optimal ways of structuring classes and objects, the state patterns focus on
effective ways of structuring states, events, and transitions.
A state pattern has five essential elements, just as an OO pattern does.
1. The pattern name — a word or two denoting the problem, the solution, and the
consequences of a pattern. A good name is vital because it will become part of
your vocabulary.
2. The problem — an explanation of the problem the pattern addresses. A problem
is often motivated by an example.
3. The solution — a description of the elements (states, transitions, events, actions,
and extended state variables) that compose the solution and their relationships,
responsibilities, and collaborations.
4. The sample code — a presentation of a concrete implementation of an instance
of the pattern. Usually the sample code implements the motivating example.
5. The consequences — the results and trade-offs of applying the pattern.
In this chapter, I provide a minicatalog of five basic state patterns (Table 5.1). The
first two are relatively simple state machine solutions to common problems. The
other three are just more advanced or expensive features that are found in UML stat-
echarts but are not supported directly in the behavioral inheritance meta-pattern.
The leading theme of all these patterns is reusing behavior through behavioral inher-
itance, in contrast to the state patterns described in the book Doing Hard Time, by
Bruce Powel Douglass [Douglass 99] that all revolve around orthogonal regions. The
other distinguishing aspect of the state patterns presented here is that all are illus-
trated by concrete, executable code. A state diagram alone is not enough to under-
stand a state pattern because the devil is always in the detail. To be practical, a
pattern must be accompanied by a concrete working example that will help you truly
comprehend and evaluate the pattern and give you a good starting point for your
own instantiation of the pattern.
Many examples in this chapter are implemented as Windows GUI applications
because the behavioral inheritance meta-pattern provides only the event processor
and lacks the other essential components of a typical event-driven system, such as
event queuing. As a reactive system, Windows provides those missing elements.
However, the patterns are not at all Windows- or GUI-specific. In particular, they all
can be used in conjunction with any other infrastructure to execute a state machine
(e.g., the Quantum Framework discussed in Part II of this book).
Ultimate Hook 133
5.1.2 Problem
Many reactive systems require consistent policies for handling events. In a GUI
design, this consistency is part of the characteristic look and feel of the user interface.
The challenge is to provide such a common look and feel in system-level software
that client applications can use easily as the default. At the same time, the clients
must be able to override every aspect of the default behavior easily if they so choose.
134 Chapter 5: State Patterns
5.1.3 Solution
The solution is to apply programming-by-difference or, specifically in this case, the
concept of behavioral inheritance. A composite state can define the default behavior
(the common look and feel) and supply an “outer shell” for nesting client substates.
The semantics of state nesting provides the desired mechanism of handling all events,
first in the context of the client code (the nested state) and of automatically forward-
ing all unhandled events to the superstate (the default behavior). In that way, the cli-
ent code intercepts every stimulus and can override every aspect of the behavior. To
reuse the default behavior, the client simply ignores the event and lets the superstate
handle it (the substate inherits behavior from the superstate).
reset
C
(idiom)
generic generic explicit
superstate final state
A/
specific (idiom)
«state pattern» B/
Ultimate Hook entry/
specific exit/ D final
substate A/ entry/terminate();
Figure 5.1 shows the Ultimate Hook state pattern using a basic graphical notation
adapted from Douglass [Douglass 99]. The dashed oval labeled «state pattern» indi-
cates collaboration among states. Dashed arrows emanating from the oval indicate
state roles within the pattern. States playing these roles are shown with heavy bor-
ders. For example, the concrete generic state plays the role of the generic superstate
of the pattern, whereas the specific state plays the role of the specific substate.
A diagram like this attempts to convey an abstract pattern but can only show a
concrete example (instance) of the pattern. In this instance, the concrete generic
state in Figure 5.1 handles events A and B as internal transitions, event C as a self-
transition, and event D as the termination of the state machine. The concrete spe-
cific state overrides event A and provides its own initialization and cleanup (in
entry and exit actions, respectively). Of course, another instance of the pattern can
implement completely different events and actions.
A few idioms worth noting are illustrated in this statechart. First is the overall
canonical structure of the state machine that, at the highest level, consists of only one
composite state (playing the role of the generic superstate). Virtually every applica-
tion can benefit from having such a highest level state because it is an ideal place for
defining common policies subsequently inherited by the whole (arbitrary complex)
submachine.
Ultimate Hook 135
Within such a canonical structure, a useful idiom for resetting the state machine is
an empty (actionless) self-transition in the generic superstate (transition C in Figure
5.1). Such a transition causes a recursive exit from all nested states (including the
generic superstate), followed by reinitialization starting from the initial transition of
the highest level state. This way of resetting a state machine is perhaps the safest
because it guarantees proper clean-up through the execution of exit actions. Simi-
larly, the safest way to terminate a state machine is through an explicit transition out
of the generic superstate to a final state (transition D in Figure 5.1) because all per-
tinent exit actions are executed. The behavioral inheritance meta-pattern does not
provide a generic final state. Instead, the statechart in Figure 5.1 proposes an
idiom, which consists of an explicit final state with an application-specific termina-
tion coded in its entry action.1
Listing 5.1 Ultimate Hook sample code; the unusual indentation of state handler
methods (lines 10–12) indicates state nesting
1. The Quantum Calculator statechart from Chapter 1 is an example of the canonical state machine structure that
uses idioms to reset and terminate.
136 Chapter 5: State Patterns
Exercise 5.1 Reimplement in C the Ultimate Hook state pattern from Listing 5.1.
Hint: Appendix A describes the techniques for realizing classes and
inheritance in C, and Section 4.5 of Chapter 4 provides specific guide-
lines for instantiating the behavioral inheritance meta-pattern in C.
5.1.5 Consequences
The Ultimate Hook state pattern is presented here in its most limited version —
exactly as it is used in GUI systems (e.g., Microsoft Windows). In particular, nei-
ther the generic superstate nor the specific substate exhibits any interesting state
machine topology. The only significant feature is behavioral inheritance (state
nesting), which can be applied recursively within the specific substate. For
example, at any level, a GUI window can have nested child windows, which
handle events before the parent.
Even in this most limited version, however, the Ultimate Hook state pattern is
a fundamental technique for reusing behavior. In fact, every state model using
the canonical structure implicitly applies this pattern.
The Ultimate Hook state pattern has the following consequences.
• The specific substate needs to know only those events it overrides.
• New events can be added easily to the top-level generic superstate with-
out affecting the specific substate.
• Removing or changing the semantics of events that clients already use is
difficult.
• Propagating every event through many levels of nesting (if the specific
substate has recursively nested substates) can be expensive.
The Ultimate Hook state pattern is closely related to the Template Method
OO design pattern and can be generalized by applying unrestricted inheritance
of state machines (see Chapter 6).
138 Chapter 5: State Patterns
5.2 Reminder
5.2.1 Intent
Make the statechart topology more flexible by inventing an event and posting it to self.
5.2.2 Problem
Often in state modeling, loosely related functions of a system are strongly cou-
pled by a common event. Consider, for example, periodic data acquisition, in
which a sensor producing the data needs to be polled at a predetermined rate.
Assume that a periodic TIMEOUT event is dispatched to the system at the desired
rate to provide the stimulus for polling the sensor. Because the system has only
one external event (the TIMEOUT event), it seems that this event needs to trigger
both the polling of the sensor and the processing of the data. A straightforward
but suboptimal solution is to organize the state machine into two distinct orthogo-
nal regions (for polling and processing).2 However, orthogonal regions increase the
cost of dispatching events (see the section “Orthogonal Component” on page 149)
and require complex synchronization between the regions (polling and processing
are not quite orthogonal).
5.2.3 Solution
A simpler and more efficient solution is to invent a stimulus (DATA_READY) and to
propagate it to self as a reminder that the data is ready for processing (Figure 5.2).
This new stimulus provides a way to decouple polling from processing without using
orthogonal regions. Moreover, you can use state nesting to arrange these two func-
tions in a hierarchical relation to take advantage of behavioral inheritance. 3
In the most basic arrangement, the processing state can be a substate of
polling and can simply inherit the polling behavior so that polling occurs in
the background to processing. However, the processing state might also choose
to override polling. For instance, to prevent flooding the CPU with sensor data,
processing might inhibit polling occasionally. The statechart in Figure 5.2 illus-
trates this option. The busy substate of processing overrides the TIMEOUT event
and thus prevents this event from being handled in the higher level polling super-
state.
Further flexibility of this solution entails fine control over the generation of
the invented DATA_READY event, which does not have to be posted at every occur-
rence of the original TIMEOUT event. For example, to improve performance, the
2. This example illustrates an alternative design for the Polling state pattern described in Douglass [Douglass 99].
3. Using state hierarchy in this fashion is typically more efficient than using orthogonal regions.
Reminder 139
polling state could buffer the raw sensor data and generate the DATA_READY
event only when the buffer fills up, Figure 5.2 illustrates this option with the if (…)
condition, which precedes the post(DATA_READY) action in the polling state.
polling
TIMEOUT/pollSensor(); Post a
reminder
if (...) post(DATA_READY)
to self
«state pattern»
processing Reminder
Use the
DATA_READY posted
idle busy reminder
TIMEOUT[...]/
/*ignore */ mutually
TIMEOUT[...] exclusive
guards
4. In Part II of this book, I describe a Quantum Framework that supports self-posting of events.
140 Chapter 5: State Patterns
1 enum SensorSignals {
2 DATA_READY = Q_USER_SIG, TERMINATE
3 };
4 class Sensor : public QHsm {
5 public:
6 Sensor() : QHsm((QPseudoState)initial) {}
7 private:
8 void initial(QEvent const *e);
9 QSTATE polling(QEvent const *e);
10 QSTATE processing(QEvent const *e);
11 QSTATE idle(QEvent const *e);
12 QSTATE busy(QEvent const *e);
13 QSTATE final(QEvent const *e);
14 private:
15 int myPollCtr;
16 int myProcCtr;
17 BOOL isHandled; // flag indicating if the last event was handled
18 HWND myHwnd; // the main window handle
19 friend BOOL CALLBACK reminderDlg(HWND hwnd, UINT iEvt,
20 WPARAM wParam, LPARAM lParam);
21 };
22
23 void Sensor::initial(QEvent const *) {
24 SendMessage(myHwnd, WM_SETICON, (WPARAM)TRUE,
25 (LPARAM)LoadIcon(inst, MAKEINTRESOURCE(IDI_QP)));
26 myPollCtr = 0;
27 myProcCtr = 0;
28 Q_INIT(&Sensor::polling);
29 }
30 QSTATE Sensor::final(QEvent const *e) {
31 switch (e->sig) {
32 case Q_ENTRY_SIG:
33 EndDialog(myHwnd, 0);
34 return 0;
35 }
36 return (QSTATE)&Sensor::top;
37 }
38 QSTATE Sensor::polling(QEvent const *e) {
39 switch (e->sig) {
40 case Q_ENTRY_SIG: SetTimer(myHwnd, 1, 500, 0); return 0;
41 case Q_EXIT_SIG: KillTimer(myHwnd, 1); return 0;
42 case Q_INIT_SIG: Q_INIT(&Sensor::processing); return 0;
43 case WM_TIMER:
44 SetDlgItemInt(myHwnd, IDC_POLL, ++myPollCtr, FALSE);
45 if ((myPollCtr & 0x3) == 0){
46 PostMessage(myHwnd, WM_COMMAND, DATA_READY, 0);
Reminder 141
47 }
48 return 0;
49 case TERMINATE: Q_TRAN(&Sensor::final); return 0;
50 }
51 if (e->sig >= Q_USER_SIG) {
52 isHandled = FALSE;
53 }
54 return (QSTATE)&Sensor::top;
55 }
56 QSTATE Sensor::processing(QEvent const *e) {
57 switch (e->sig) {
58 case Q_INIT_SIG: Q_INIT(&Sensor::idle); return 0;
59 }
60 return (QSTATE)&Sensor::polling;
61 }
62 QSTATE Sensor::idle(QEvent const *e) {
63 switch (e->sig) {
64 case Q_ENTRY_SIG:
65 SetDlgItemText(myHwnd, IDC_STATE, "idle");
66 return 0;
67 case DATA_READY: Q_TRAN(&Sensor::busy); return 0;
68 }
69 return (QSTATE)&Sensor::processing;
70 }
71 QSTATE Sensor::busy(QEvent const *e) {
72 switch (e->sig) {
73 case Q_ENTRY_SIG:
74 SetDlgItemText(myHwnd, IDC_STATE, "busy");
75 return 0;
76 case WM_TIMER:
77 SetDlgItemInt(myHwnd, IDC_PROC, ++myProcCtr, FALSE);
78 if ((myProcCtr & 0x1) == 0) {
79 Q_TRAN(&Sensor::idle);
80 }
81 return 0;
82 }
83 return (QSTATE)&Sensor::processing;
84 }
The simple GUI for this application (Figure 5.3) displays the currently active
state (busy or idle), as well as the number of times WM_TIMER has been handled in
polling and processing, respectively.
142 Chapter 5: State Patterns
Exercise 5.2 Find the Reminder state pattern implementation on the accompanying
CD-ROM and execute it. Next, change the polling state to generate
DATA_READY every eighth, instead of every fourth, clock tick. Recompile
and execute again.
Exercise 5.3 Reimplement in C the Reminder state pattern from Listing 5.2. Hint:
Appendix A describes the techniques for realizing classes and inheritance
in C, and Section 4.5 of Chapter 4 provides specific guidelines for instan-
tiating the behavioral inheritance meta-pattern in C.
5.2.5 Consequences
Although conceptually very simple, the Reminder state pattern has profound conse-
quences. It can address many more problems than illustrated in the example. You
could use it as a Swiss Army knife to fix almost any problem in the state machine
topology.
For example, consider the artificial limitation of the behavioral inheritance imple-
mentation (Section 4.2.1 in Chapter 4), which restricts the initial transition from tar-
geting substates nested deeper than one level5 (as in Figure 5.4a). The equivalent
cascaded initial transitions (as in Figure 5.4b) are sometimes inconvenient because a
composite state with the initial transition can never become active without one of its
substates being active (see Section 4.3.3 in Chapter 4). Reminder enables you to
change the topology of the state machine and replace the initial transition with a
regular one triggered by an invented signal, INIT, posted in the higher level initial
transition (as in Figure 5.4c).
You also can apply the Reminder idiom to eliminate troublesome completion
transitions, which in the UML specification are transitions without an explicit trigger
(they are triggered implicitly by completion events, aka anonymous events). The
behavioral inheritance meta-pattern requires that all transitions have explicit trig-
gers; therefore, the pattern does not support completion transitions. However, the
Reminder pattern offers a workaround. You can invent an explicit trigger for every
transition and post it to self. This approach actually gives you much better control
over the behavior because you can explicitly specify the completion criteria.
Figure 5.4 (a) An initial transition penetrating two levels of nesting (not allowed
in the behavioral inheritance meta-pattern); (b) the equivalent nested
initial transitions; (c) elimination of the innermost initial transition
through the Reminder pattern (the ^INIT action indicates the INIT
event propagates to self)
s1 s1 s1
INIT
s11 s12 s11 s12 s11 s12
5.3.2 Problem
One of the biggest challenges in designing reactive systems is that such systems
must be prepared to handle every event at any time. However, sometimes an
event arrives at a particularly inconvenient moment when the system is in the
midst of some complex event sequence. In many cases, the nature of the event is
such that it can be postponed (within limits) until the system is finished with the
current sequence, at which time the event can be recalled and conveniently pro-
cessed.
Consider, for example, the case of a server application that processes trans-
actions (e.g., from ATM terminals). Once a transaction starts, it typically goes
through a sequence of processing, which commences with receiving the data from
a remote terminal followed by the authorization of the transaction. Unfortu-
nately, new transaction requests to the server arrive at random times, so it is pos-
sible to get a request while the server is still busy processing the previous
transaction. One option is to ignore the request, but this might not be acceptable.
Another option is to start processing the new transaction immediately, which can
complicate things immensely because multiple outstanding transactions would
need to be handled simultaneously.
5.3.3 Solution
The solution is to defer the new request and handle it at a more convenient time,
which effectively leads to altering the sequence of events presented to the state
machine.
UML statecharts support such a mechanism directly by allowing every state to
specify a list of deferred events. As long as an event is on the combined deferred
list of the currently active state configuration, it is not presented to the state
machine but, rather, queued for later processing. Upon a state transition, events that
are no longer deferred are automatically recalled and dispatched to the state
machine. Figure 5.5 illustrates a solution based on this mechanism (note the special
defer operator in the NEW_REQUEST internal transition in the busy state).
8. The Quantum Framework (described in Part II of this book) supports FIFO and LIFO policies through the
postFIFO() and postLIFO() methods, respectively.
Deferred Event 145
Note: State nesting immensely complicates event deferral because deferred lists
of all nested states of the current state configuration contribute to the
mechanism.
busy
NEW_REQUEST / defer defer operator
TERMINATE
receiving RECEIVED
NEW_REQUEST
authorizing
idle
AUTHORIZED
that defer() must copy the event into local storage, as opposed to storing only the
event pointer (line 25). An event instance passed to a state handler is typically
destroyed (or goes out of scope) after dispatching. Also, no matter how big you make
the deferred event queue, there is always a possibility of overflowing it; therefore,
defer() should return the status of the deferral to the caller. This application gener-
ates a warning beep when deferring an event fails (line 44).
The application simulates processing delays in states receiving and
authorizing with a Windows timer. A timer is created on entry to either state
and then destroyed on exit. This example demonstrates how you can use entry
and exit actions for guaranteed initialization and cleanup (you don’t want to
leak a Windows timer!).
Exercise 5.4 Find the Deferred Event state pattern implementation on the accompany-
ing CD-ROM and execute it. Next, set a breakpoint in the exit action
from the authorizing state. Start the application, issue the new request,
wait until servicing proceeds to the authorizing state, and terminate the
application before it transitions back to the idle state. Verify that the exit
action from authorizing is executed (and thus the timer is not leaked).
The sample application in Listing 5.3 defers only one type of event:
NEW_REQUEST. However, you can easily extend the pattern to defer any number of
events. For instance, it might be inappropriate to terminate TServer while it is
still processing a transaction; therefore, you can defer the TERMINATE event until
the server is idle. To distinguish between deferring and recalling NEW_REQUEST
and deferring TERMINATE, you need to create another pair of methods, say
deferTerminate() and recallTerminate(), as well as a separate attribute in
which to store the instances of the TERMINATE event type (Exercise 5.6).
Exercise 5.5 Reimplement in C the Deferred Event pattern from Listing 5.3. Hint:
Appendix A describes the techniques for realizing classes and inheritance
in C, and Section 4.5 of Chapter 4 provides specific guidelines for instan-
tiating the behavioral inheritance meta-pattern in C.
Exercise 5.6 Add deferring the TERMINATE signal to the TServer statechart from List-
ing 5.3 as described in the previous paragraph. Devise and execute a test
plan for the new feature.
Orthogonal Component 149
5.3.5 Consequences
Event deferral is a valuable technique for simplifying state models. Instead of
constructing an unduly complex state machine to handle every event at any time,
you can defer an event when it comes at an inappropriate or awkward time. The
event is recalled when the state machine is better able to handle it. The Deferred
Event state pattern is a lightweight alternative to the powerful but heavyweight
event deferral of UML statecharts. The Deferred Event state pattern has the fol-
lowing consequences.
• It requires explicit deferring and recalling of the deferred events.
• Concrete state machines (subclasses of QHsm), rather than the event processor,
are responsible for storing deferred events and for implementing defer() and
recall().
• If a state machine defers more than one event type, it might be appropriate to
implement a separate queue for each type, as well as a specific defer???()
and recall???() pair for deferring and recalling specific events, respec-
tively.
• Events are usually deferred in a high-level transition (often an internal tran-
sition). Conversely, events are typically recalled in an entry action to a low-
level state (the state that no longer defers a given event type).
• Recalling an event involves posting it to self; however, unlike deferred events
in the Reminder pattern, they are usually external rather than invented.
The real-time object-oriented modeling (ROOM) method [Selic+ 94] supports
a variation of the Deferred Event pattern presented here. The ROOM virtual
machine (infrastructure for executing ROOM models) provides the generic
methods defer() and recall(), which clients need to call explicitly. The vir-
tual machine, however, takes care of event queuing. Methods defer() and
recall() in ROOM are not specific to an event type but, rather, to the interface
component through which an event was received.
5.4.2 Problem
Many objects comprise relatively independent parts that have state behavior. As
an example, consider a simple digital alarm clock. The clock performs two
150 Chapter 5: State Patterns
Figure 5.8 AlarmClock class and its UML statechart with orthogonal regions
«class role»
timekeeping alarm
AlarmClock
myCurrentTime : int 12H mode12h on
ON
myAlarmTime : int TICK/ TICK/
24H mode24h off OFF
TERMINATE
TICK/
5.4.3 Solution
The solution is to use object composition instead of orthogonal regions. In the case
of the alarm clock, the alarm function can be included as a separate Alarm com-
ponent embedded inside the timekeeping container object, as shown in Figure 5.9.
This solution is based on the universal OO guideline that it is best to construct
classes out of high-level components, as opposed to low-level built-in types [Horst-
mann 95]. If the alarm function of the AlarmClock class is so independent that it
warrants separation into a distinct orthogonal region, it probably is a component of
the AlarmClock class. Indeed, as shown at the top of Figure 5.9, the alarm function
very naturally maps to the Alarm class that has both data (myAlarmTime) and
behavior (a state machine). Rumbaugh and colleagues [Rumbaugh+ 91] observe that
this is a general rule. Concurrency virtually always arises within objects by aggrega-
tion; that is, multiple states of the components can contribute to a single state of the
composite object.
The use of aggregation in conjunction with state machines raises three ques-
tions.
1. How does the container state machine communicate with the component state
machines?
Orthogonal Component 151
2. How do the component state machines communicate with the container state
machine?
3. What kind of concurrency model should be used?
Figure 5.9 Orthogonal Component state pattern; the pattern partitions state
behavior as well as extended state variables, as indicated by the class
roles at the top of the diagram
QHsm QFsm
AlarmClock
myAlarm 1 Alarm
myCurrentTime : int
myAlarm : Alarm myAlarmTime
Container «class role»
«class role»
Component
«state pattern»
timekeeping Orthogonal
Component on
mode12h TIME/
12H
TICK/
OFF ON
mode24h TERMINATE off
24H TICK/
The composite object interacts with its aggregate parts by synchronously dis-
patching events to them (by invoking dispatch() on behalf of the components).
GUI systems frequently use this model because it is how parent windows commu-
nicate with their child windows (e.g., dialog controls). Although, in principle,
the container could invoke various methods of its components or access their data
directly, dispatching events to the components should be the preferred way of
communication. The components are state machines, and their behavior
depends on their internal state.
To communicate in the opposite direction (from a component to the container),
a component needs to post events to the container. Note that a child cannot call
dispatch() on behalf of the parent because it would violate RTC semantics. As
a rule, the parent is always in the middle of its RTC step when a child executes.
Therefore, children need to asynchronously post (queue) events to the parent.
1 enum AlarmClockSignals {
2 TIME_SIG = Q_USER_SIG,
3 ALARM_SIG, TERMINATE
4 };
5 struct AlarmInitEvt : public QEvent {
6 HWND hWnd;
7 };
8 struct TimeEvt : public QEvent {
9 unsigned currentTime;
10 };
9. Most commonly, all orthogonal regions in a statechart also share a common execution thread [Douglass 99].
Orthogonal Component 153
Listing 5.5 shows the declaration of the Alarm state machine, which derives from
the QFsm class and therefore uses a slightly different state handler signature. The
Alarm class encapsulates two attributes — the self-explanatory myAlarmTime (line
9) and myHwnd (line 10) — to store the window handle that the Alarm component
needs in the implementation.
1 #include "clock.h"
2 #include "alarm.h"
3
4 void Alarm::initial(QEvent const *e) {
5 myHwnd = (static_cast<AlarmInitEvt const *>(e))->hWnd;
6 . . .
7 QFSM_TRAN(&Alarm::on);
8 }
9 void Alarm::on(QEvent const *e) {
10 switch (e->sig) {
11 case TIME_SIG:
12 if ((static_cast<TimeEvt *>(e))->currentTime == myAlarmTime) {
13 Beep(1000, 20);
14 PostMessage(myHwnd, WM_COMMAND, ALARM_SIG, 0); // notify
15 }
16 return;
17 case IDC_OFF:
18 . . .
19 QFSM_TRAN(&Alarm::off);
20 return;
21 }
22 }
23 void Alarm::off(QEvent const *e) {
24 char buf[12];
25 unsigned h, m;
26 switch (e->sig) {
27 case IDC_ON:
28 GetDlgItemText(myHwnd, IDC_ALARM, buf, sizeof(buf));
29 if (...) { // does the user input represent valid alarm time?
30 . . .
31 QFSM_TRAN(&Alarm::on);
32 }
33 return;
34 }
35 }
Listing 5.6 shows an abbreviated implementation of the Alarm FSM. The first
interesting aspect is the initial pseudostate (lines 4–8), which uses the event argu-
ment to initialize the myHwnd attribute (line 5). As described in Chapter 4, the state
154 Chapter 5: State Patterns
machine interface intentionally separates the initial transition from the state machine
instantiation, which this code exploits. The AlarmClock class, and thus its Alarm
component, are instantiated statically before the window handle is allocated. How-
ever, the initial transition, which is explicitly triggered much later, offers the con-
tainer (AlarmClock) an opportunity to initialize the component with the window
handle (or any other arbitrary parameters passed in the initializing event).
The other interesting feature is the handling of the TIME signal in (Listing 5.6,
lines 11–16). The guard condition for starting the alarm is the match between the
current time and the preset alarm time (line 12). Notice the usual downcasting of the
generic event pointer to the concrete event class (TimeEvt* in this case). In addition,
line 14 illustrates how the Alarm component notifies the container by posting the
ALARM_SIG event.
1 #include "clock.h"
2 #include "alarm.h"
3
4 class AlarmClock : public QHsm { // hierarchical state machine
5 public:
6 AlarmClock() : QHsm((QPseudoState)initial) {}
7 private:
8 void initial(QEvent const *e); // initial pseudostate
9 QSTATE timekeeping(QEvent const *e); // state-handler
10 QSTATE mode12hr(QEvent const *e); // state-handler
11 QSTATE mode24hr(QEvent const *e); // state-handler
12 QSTATE final(QEvent const *e); // state-handler
13 private:
14 unsigned myCurrentTime; // current time (in minutes)
15 Alarm myAlarm; // reactive component Alarm
16 BOOL isHandled;
17 HWND myHwnd; // the main window handle
18 friend class Alarm; // grant friendship to reactive component(s)
19 friend BOOL CALLBACK DlgProc(HWND hwnd, UINT iEvt,
20 WPARAM wParam, LPARAM lParam);
21 };
22
23 void AlarmClock::initial(QEvent const *) {
24 . . .
25 AlarmInitEvt ie; // initialization event for the Alarm component
26 ie.wndHwnd = myHwnd;
27 myAlarm.init(&ie); // initial transition in the alarm component
28 Q_INIT(timekeeping);
29 }
30
Orthogonal Component 155
10. Components also must “know” their container. If the container is a Singleton, then access is through its static
instance() method (see the Singleton pattern in Gamma and colleagues [Gamma+ 95]).
156 Chapter 5: State Patterns
solution, on the other hand, requires many more transitions — n(n – 1), in general
— to interconnect all states. There is also a difference in behavior. In the hierarchical
solution, if a system is already in mode12h, for example, and the 12H signal arrives,
the system leaves this mode and enters it again. (Naturally, you could prevent that
by overriding the high-level 12H transition in the mode12h state.) In contrast, if the
flat state machine of the Alarm class is in the off state, for example, then nothing
happens when the OFF signal appears. This solution might or might not be what you
want; however, the hierarchical solution (the Device Mode idiom) offers you both
options and scales much better with a growing number of modes.
Exercise 5.7 Change the superclass of the Alarm class from QFsm to QHsm and change
the Alarm state machine to use the same structure as the Alarm orthogo-
nal region shown in Figure 5.8. Note that you don’t need to change any-
thing in the AlarmClock container class because the QFsm and QHsm
classes have equivalent interfaces.
orthogonal regions, on the other hand, would burn many more CPU cycles by
automatically dispatching every TICK to every orthogonal region (i.e., to the Alarm
region as well).
Figure 5.10 shows the Windows GUI corresponding to the sample code described
in this section. The dialog box clearly separates the timekeeping and alarm functions.
There is no need to display the states of these components separately because they
are readily apparent from the settings of the two groups of radio buttons.
To make this example a little more interesting, I accelerated the clock to tick
at a much faster rate than usual (it makes about 20 accelerated minutes per real
second). That way you can hear an alarm (a short beep) much more often (about
every 72 seconds).
Exercise 5.8 Find the Orthogonal Component state pattern implementation on the
accompanying CD-ROM and execute it. Change the timekeeping mode
from 24 to 12 hours and the alarm mode from on to off. Notice that
only in the alarm-off state can you change the alarm time. Set the new
alarm time and turn the alarm back on. Verify that the clock beeps when
it reaches the alarm time.
Exercise 5.10 Components must often “know” their container. Apply the Singleton
design pattern (read about the Singleton pattern in Gamma and col-
leagues [Gamma+ 95]) to the AlarmClock class, and reorganize the
Alarm class such that it can accesses the AlarmClock container through
the Singleton’s instance() method. Remove the myHwnd attribute from
the Alarm class and let it share the window handle of AlarmClock
instead.
5.4.5 Consequences
The Orthogonal Component state pattern has the following consequences.
• It partitions independent islands of behavior into separate reactive objects.
This separation is deeper than with orthogonal regions because the objects
have both distinct behavior and distinct data.
• Partitioning introduces a container–component (also known as parent–child or
master–slave) relationship. The container implements the primary function-
ality and delegates other (secondary) features to the components. Both the
container and the components are state machines.
• The components are often reusable with different containers or even within
the same container (the container can instantiate more than one component of
a given type).
• The container shares its execution thread with the components.
• The container communicates with the components by directly dispatching
events to them. The components notify the container by posting events to it,
never through direct event dispatching.
• The components typically use the Reminder state pattern to notify the con-
tainer (i.e., the notification events are invented specifically for the internal
communication and are not relevant externally). If there are more compo-
nents of a given type, then the notification events must identify the originating
component (the component passes its ID in a parameter of the notification
event).
• The container and components can share data. Typically, the data is an
attribute of the container (to allow multiple instances of different containers).
The container typically grants friendship to the selected components.
• The container is entirely responsible for its components. In particular, it must
explicitly trigger initial transitions in all components,11 as well as explicitly dis-
11. In C, the container also must call constructors for all its components explicitly.
Orthogonal Component 159
patch events to the components. Errors may arise if the container “forgets” to dis-
patch events to some components in some of its states.
• The container has full control over the dispatching of events to the compo-
nents. It can choose not to dispatch events that are irrelevant to the compo-
nents. It can also change event types on the fly and provide some additional
information to the components.
• The container can dynamically start and stop components (e.g., in certain
states of the container state machine).
• The composition of state machines is not limited to just one level. Components
can have reactive subcomponents; that is, the components can be containers
for lower level subcomponents. Such a recursion of components can proceed
arbitrarily deep.
The Orthogonal Component state pattern is popular in GUI systems. For
example, dialog boxes are the containers that aggregate components in the form
of dialog controls (buttons, check boxes, sliders, etc.). Both dialog boxes and dia-
log controls are event-driven objects with state behavior (e.g., a button has
depressed and released states). GUIs also use the pattern recursively. For
instance, a custom dialog box can be a container for the standard File-Select or
Color-Select dialog boxes, which in turn contain buttons, check boxes, and so
on.
The last example points to the main advantage of the Orthogonal Component
state pattern over the AND-decomposition of statecharts (orthogonal regions).
Unlike an orthogonal region, you can reuse a reactive component many times
within one application and across many applications.
Both orthogonal regions and reactive components directly compete in the
design space with simple extended state variables. For example, Douglass [Dou-
glass 99] presents a simple Latch state pattern that is applicable to reactive
objects that must pass through a specific state in their life cycle in order to pro-
ceed with some activity. The proposed solution is to build a latch with an orthog-
onal region and two states: latched and unlatched. The latch starts in the
unlatched state and is forced to the latched state when the object passes
through the precondition state. Subsequently, the main state machine of the
object uses the IS_IN(latched) operator12 as a guard on a transition to a state. A
simpler solution is to implement the latch as a simple flag, cleared in the initial tran-
sition, and set on entry to the precondition state. Subsequently, the object’s state
12. The IS_IN(foo) operator returns TRUE if any of the orthogonal regions is in the foo state and returns
FALSE otherwise. This operator corresponds to the QHsm::isIn() method of the behavioral inheritance
meta-pattern implementation.
160 Chapter 5: State Patterns
machine can test the flag in the guard condition. The Latch orthogonal region is
overkill here and doesn’t add value because it isn’t used to capture modal behavior;
rather, it only stores information, which is a job for an extended state variable. The
litmus test of misusing an orthogonal region as storage occurs when the region is
employed only in conjunction with the IS_IN(state) operator.
You should apply good judgment and use the Orthogonal Component (or any
other) pattern only if it simplifies your designs. Sometimes it is better to use sim-
pler solutions, like extended state variables. Your ultimate goal is to defeat com-
plexity not create clever-looking diagrams.
5.5.2 Problem
State transitions defined in high-level composite states often deal with events
that require immediate attention; however, after handling them, the system
should return to the most recent substate of the given composite sate.
For example, consider a simple toaster oven. Normally the toaster operates
with its door closed. However, at any time, the user can open the door to check
the food or to clean the oven. Opening the door is an interruption; for safety rea-
sons, it requires shutting the heater off and lighting an internal lamp. However,
after closing the door, the toaster oven should resume whatever it was doing
before the door was opened. Here is the problem: What was the toaster doing just
before the door was opened? The state machine must remember the most recent
state configuration that was active before opening the door in order to restore it
after the door is closed again.
UML statecharts address this situation with two kinds of history pseudostates:
shallow history and deep history (see Section 2.2.7 in Chapter 2). This example
requires the deep history mechanism (denoted as the circled H * icon in Figure 5.11).
The behavioral inheritance meta-pattern does not support a history mechanism auto-
matically for all states because it would incur extra memory and performance over-
heads. However, it is easy to add such support for selected states.
Transition to History 161
5.5.3 Solution
Figure 5.11 illustrates the solution, which is to store the most recently active substate
of the doorClosed state in the dedicated attribute myDoorClosedHistory. The
ideal place for setting this attribute is the exit action from the doorClosed state.
Subsequently, the transition to history of the doorOpen state (transition to the circled
H*) uses the attribute as the target of the transition. Because this target changes at
run time, it is crucial to code this transition with the Q_TRAN_DYN() macro, rather
than the usual (optimized) Q_TRAN() macro (see Section 4.4.3 in Chapter 4).
Listing 5.8 Complete implementation of the toaster oven statechart (Figure 5.11)
16
17 enum ToasterOvenSignals {
18 OPEN_SIG = Q_USER_SIG,
19 CLOSE_SIG, TOAST_SIG, BAKE_SIG, OFF_SIG, END_SIG
20 };
21
22 void ToasterOven::initial(QEvent const *) {
23 myDoorClosedHistory = Q_STATIC_CAST(QState, &ToasterOven::off);
24 Q_INIT(&ToasterOven::doorClosed);
25 }
26
27 QSTATE ToasterOven::doorClosed(QEvent const *e) {
28 switch (e->sig) {
29 case Q_ENTRY_SIG: printf("door-Closed;"); return 0;
30 case Q_EXIT_SIG: myDoorClosedHistory = getState(); return 0;
31 case Q_INIT_SIG: Q_INIT(&ToasterOven::off); return 0;
32 case OPEN_SIG: Q_TRAN(&ToasterOven::doorOpen); return 0;
33 case TOAST_SIG: Q_TRAN(&ToasterOven::toasting); return 0;
34 case BAKE_SIG: Q_TRAN(&ToasterOven::baking); return 0;
35 case OFF_SIG: Q_TRAN(&ToasterOven::off); return 0;
36 case END_SIG: Q_TRAN(&ToasterOven::final); return 0;
37 }
38 return (QSTATE)&ToasterOven::top;
39 }
40
41 QSTATE ToasterOven::off(QEvent const *e) {
42 switch (e->sig) {
43 case Q_ENTRY_SIG: printf("toaster-Off;"); return 0;
44 }
45 return (QSTATE)&ToasterOven::doorClosed;
46 }
47
48 QSTATE ToasterOven::heating(QEvent const *e) {
49 switch (e->sig) {
50 case Q_ENTRY_SIG: printf("heater-On;"); return 0;
51 case Q_EXIT_SIG: printf("heater-Off;"); return 0;
52 }
53 return (QSTATE)&ToasterOven::doorClosed;
54 }
55
56 QSTATE ToasterOven::toasting(QEvent const *e) {
57 switch (e->sig) {
58 case Q_ENTRY_SIG: printf("toasting;"); return 0;
59 }
60 return (QSTATE)&ToasterOven::heating;
61 }
62
Transition to History 163
Exercise 5.11 Find the Transition to History state pattern implementation on the
accompanying CD-ROM and execute it. You drive the application by
injecting events from the keyboard (o = Open, c = Close, t = Toast, b =
Bake, f = Off, and e = End). Verify that opening and then closing the
door from any of the states baking, toasting, or off leads back to the
original state. Modify the implementation to use the Q_TRAN() macro
instead of Q_TRAN_DYN(). Recompile and examine how this breaks the
desired behavior.
Exercise 5.12 Reimplement in C the Transition to History state pattern from Listing
5.8. Hint: Appendix A describes the techniques for realizing classes and
inheritance in C, and Section 4.5 of Chapter 4 provides specific guide-
lines for instantiating the behavioral inheritance meta-pattern in C.
5.5.5 Consequences
The Transition to History state pattern has the following consequences.
• It requires that a separate QState pointer-to-member function is provided for
each composite state to store the history of this state. It is best to use attributes of
the concrete subclass of QHsm for this purpose.
• It requires explicitly setting the history variable in the exit action from the corre-
sponding composite state using QHsm::getState().
• It requires the dynamic (not optimized) Q_TRAN_DYN(<history-variable>)
transition macro to transition to the history of a given state.
• It corresponds to the deep-history, not the shallow-history, pseudostate (see Sec-
tion 2.2.7 in Chapter 2).
• You can explicitly clear the history of any state by resetting the corresponding
history variable.
As a part of the UML specification, the history mechanism qualifies as a
widely used pattern. The ROOM method [Selic+ 94] describes a few examples of
transitions to history in real-time systems, whereas Horrocks [Horrocks 99]
describes how to apply the history mechanism in the design of GUIs.
5.6 Summary
As Gamma and colleagues [Gamma+ 95] observe: “One thing expert designers
know not to do is solve every problem from first principles.” Collecting and docu-
Summary 165
menting design patterns is one of the best ways of capturing and disseminating
expertise in any domain, not just in software design.
State patterns are specific design patterns that are concerned with optimal
(according to some criteria) ways of structuring states, events, and transitions to
build effective state machines. In this chapter, I described just five such patterns 13 at
various levels of abstraction. The first two, Ultimate Hook and Reminder, are at a
significantly lower level than the rest, and perhaps I should have called them idioms
rather than patterns. However, they are so fundamental and useful that they belong
in every state machine designer’s bag of tricks.
The other three patterns (Deferred Event, Orthogonal Component, and Tran-
sition to History) are alternative (lightweight) realizations of features supported
natively in UML statecharts. Each one of these state patterns offers significant
performance and memory savings compared to the full UML-compliant realiza-
tion.
13. You also can find a few useful idioms for structuring state machines.
166 Chapter 5: State Patterns
6
Chapter 6
Useful reactive classes that you create through instantiation of the fundamental
behavioral inheritance meta-pattern frequently have rich state behavior and embed
involved statecharts. Coming up with a good statechart that captures a nontrivial
behavior is not easy. When you successfully design, implement, and debug a robust
reactive class, you want to get the maximum mileage out of it by reusing it in other
projects. Seldom, however, can you reuse a class as-is without modifications. You
need to be able to inherit from it and refine its behavior in subclasses. Traditional
OOP prescribes how to inherit attributes and refine individual class methods, 1 but
how do you inherit and refine entire state machines?
The issue is tricky because the behavioral inheritance meta-pattern represents a
statechart as a group of interrelated class methods (state handlers) rather than as a
1. If you are interested in the C implementation, please refer to Appendix A for an explanation of how to realize
inheritance in C.
167
168 Chapter 6: Inheriting State Models
single method. The challenge is to keep intact the numerous relationships among
state handlers in the process of inheriting and refining the derived statechart. The
associations among state handlers define the topology of the state machine and come
in two flavors: (1) state handlers refer other state handlers as targets of state transi-
tions and (2) state handlers designate other state handlers as superstates. As you will
see in a concrete working example, the behavioral inheritance meta-pattern naturally
preserves the statechart topology in a derived reactive class and enables easy refine-
ment of the inherited state handlers. However, how it happens is certainly not trivial,
and in this chapter, I peek under the hood to find out exactly what’s going on.
Another concern associated with inheriting entire state models is compliance with
the Liskov Substitution Principle (LSP). According to the traditional LSP, any super-
class should be freely substitutable for its subclass. For reactive classes, this means
that the behavior refined in any subclass should remain compatible with the behavior
of the original base class. At a minimum, this compatibility requires that the subclass
must accept all events that can be accepted by the parent, but there is obviously more
to it. Although it is generally difficult to define “compatibility of behavior” precisely,
this chapter at least enlists design heuristics and practical rules of thumb for inherit-
ance and refinement that comply with the LSP.
In Section 2.2.2 of Chapter 2, I introduced the concept of behavioral inheritance
and proposed extending LSP to nested states. Please note that these concepts are dis-
tinctively different from the classical class inheritance and the traditional LSP for
classes that are the subject of this chapter.
In this chapter, I use the most advanced programming techniques yet in the discus-
sion of statechart implementations. In particular, I rely heavily on polymorphism,
which I intentionally have avoided up to this point. Although the techniques pre-
sented here can be very useful, they come at the cost of increased complexity and
run-time overhead. Please feel free to skip this chapter on the first reading because
the material covered here is not necessary to comprehend the other parts of the book.
Figure 6.1 The original Quantum Calculator GUI (a) and the enhanced Quantum
Calculator after adding the % button (b)
Exercise 6.1 Launch the Visual Basic Calculator application from the accompanying
CD-ROM (refer to Appendix C for the structure of the CD-ROM). Try
computing 100 + 8% and verify that you don’t get the expected result
(108). Enter 2, –, –, % and watch the application crash.
From the problem statement, you see that the % button has a function similar to
the = button, in that it terminates a two-operand expression. This similarity suggests
that the PERCENT event should be handled in the same state context as the EQUALS
event — that is, in the operator2 state. This is also the only place it needs to be han-
dled, as shown in Figure 6.2.
This example illustrates a nontrivial refinement to a statechart because it requires
modifying the existing operand2 state (by adding a new transition), rather than
introducing a new state. The problem is that operand2 is already involved in many
relationships (Figure 6.2). For example, it is the superstate of zero2, int2, and
frac2, as well as the source of transitions triggered by the signals EQUALS, OPER, and
CE. The question is, can you override just the operand2() state handler without
breaking all the relationships in which it already takes part? You can if the
operand2() state handler is declared virtual in the base class. In other words, the
virtual function mechanism of C++ also works for pointer-to-member functions and
accomplishes exactly what you want. I’ll leave the interesting question of how this is
done to Section 6.1.1 and proceed with the example.
Listing 6.1 shows the Calc1 class, which is just a slightly modified version of the
original Quantum Calculator Calc class from Chapter 1. The Calc1 class is pre-
pared for inheritance2 by declaring all state handlers, as well as other helper meth-
ods, virtual3 (e.g., Listing 6.1, line 7). Calc1 also applies the Singleton design
pattern [Gamma+ 95] so that any subclass of Calc1 can be plugged in without
170 Chapter 6: Inheriting State Models
operand1
POINT
OPER OPER
OPER operand2
PERCENT zero2 int2 frac2
1_9 POINT
0/ 0/
1_9/ 1_9/
EQUALS
POINT
affecting the rest of the GUI code. You just introduce the static member function
Calc1::instance() (line 2) and protect the constructor (line 5) to prevent uncon-
trolled instantiation of Calc1. Other aspects of the implementation of the Calc1
class are identical to the original Calc class, including all the references to the
operand2 state (lines 11–22).
2. The base class Calc originally could have been designed with inheritance and refinement in mind, in which
case, creation of the Calc1 class would be unnecessary.
3. In the fine-tuning phase, you could recover some performance for some methods by selectively removing the
virtual keyword.
Statechart Refinement Example in C++ 171
As you’ve verified for yourself in Exercise 6.2, the enhanced Quantum Calculator
works just fine. The behavioral inheritance meta-pattern not only automatically assim-
ilates the newly defined Calc2::operand2() state handler into the fabric of between-
state relationships inherited from the Calc1 base class, but it even allows straightfor-
ward reuse of behavior from the original Calc1::operand2() state handler.
Exercise 6.3 The technique presented here allows you to refine all aspects of a state
handler. For example, find the VC++ project qcalc2.dsp on the accom-
panying CD-ROM and add an entry action to the Calc2::
opearator2() state handler (e.g., invoke the Win32 Beep()). Rebuild
the project and note that only one module, calc2.cpp, needs recompila-
tion. Launch the application and verify that your entry action indeed
fires on each entry into the refined operator2 state.
1 class Foo {
2 public:
3 typedef void (Foo::*Handler)();
4 virtual ~Foo() {} // virtual destructor
5 Handler f()
174 Chapter 6: Inheriting State Models
Note: Some C++ compilers offer an option to select the internal representation
of pointer-to-member functions. For example, GNU gcc provides the
command line option -fvtable-thunks to choose the thunk technique,
which you should select to improve performance of your state machines
[WindRiver 95].
The following machine language output, compiled from Listing 6.3 by the
Microsoft C/C++ 32-bit compiler (optimized code), illustrates the vcall thunk tech-
nique.5
; Foo::g()
00401010 push 407040h ; stack address of "Foo::g()\n"
00401015 call 00401115 ; call printf()
0040101A pop ecx
0040101B ret
; Bar::g()
00401020 push 40704Ch ; stack address of "Bar::g()\n"
00401025 call 00401115 ; call printf()
0040102A pop ecx
0040102B ret
; main()
004010F0 mov ecx,4078E8h ; place foo into ecx (this ptr)
004010F5 call dword ptr ds:[4078E4h] ; call via pointer h
004010FB mov ecx,4078E0h ; place bar into ecx (this ptr)
00401100 jmp dword ptr ds:[4078E4h] ; call via pointer h
; thunk
00401110 mov eax,dword ptr [ecx] ; grab vtable
00401112 jmp dword ptr [eax+4] ; jump to offset 1
4. Examples of compilers that use this technique include Microsoft C++ and Borland C++ for Intel x86 processors.
5. The machine code was cut and pasted from a debug session (please note that I intentionally used optimized,
non-debug version of code). I’ve added the comments manually.
176 Chapter 6: Inheriting State Models
; Foo::f()
00401E03 mov eax,[00407E14]
00401E08 test eax,eax
00401E0A je 00401E0E
00401E0C call eax
00401E0E push 407024h
00401E13 push 407014h
00401E18 call 00401EEB
00401E1D push 407010h
00401E22 push 407000h
00401E27 call 00401EEB
00401E2C add esp,10h
00401E2F ret
When you step through the Foo::f() method at address 00401E03, you’ll find
that it returns the address 00401110, which is the address of the compiler-syn-
thesized vcall thunk. This address is subsequently stored in the pointer-to-mem-
ber function h allocated at 004078E4. A function invocation through pointer-to-
member h takes only one machine instruction (e.g., main() performs two such
invocations at addresses 004010F5 and 004011006). The vcall thunk itself is very
concise. It expects the address of the object (the this pointer) in the ecx register,
which the thunk dereferences to find the corresponding virtual table. In the next
instruction, the thunk simply jumps to the address pointed to by the appropriate
slot in the virtual table (the second slot in this case). Overall, the generated code
is beautifully simple and tight.
Exercise 6.4 Execute the test code example from Listing 6.3 and step through the
code at assembly level. Subsequently remove the keyword virtual in
line 6. Recompile and step through the code again. What difference do
you see?
The main points to remember from this discussion are that (1) the C++ virtual
mechanism works for pointer-to-virtual-member functions, which enables
straightforward inheritance and refinement in the behavioral inheritance meta-
pattern, and (2) the mechanism incurs some additional overhead for all state
handlers declared virtual, regardless of whether they are actually overridden
in the subclasses.
6. At the address 00401100, you can see the typical function epilog elimination. Because the second
method call is the last instruction in main(), the compiler synthesized a jump rather than a call
instruction.
Statechart Refinement Example in C 177
Subsequently, you need to initialize the virtual table and then hook the virtual
pointer (v-pointer) in the protected constructor Calc1Ctor_().
BEGIN_VTABLE(Calc1, QHsm) /* initialize v-table of Calc1 class */
. . .
VMETHOD(Calc1, operand2) = Calc1_operand2; /*bind implementation*/
. . .
END_VTABLE
7. Although I use the strange name “C+,” the code is plain, highly portable ANSI C, even though it often looks like
C++.
178 Chapter 6: Inheriting State Models
if (!QHsmCtor_(&me->super_, (QPseudoState)Calc1_initial)) {
return 0;
}
VHOOK(Calc1); /* hook the v-pointer to the Calc1 v-table */
return me;
}
Finally, you need to change all references to state handlers of the Calc1 class
to use dynamic rather than static binding. The following state handler for the
begin state illustrates how you achieve this.
QSTATE Calc1_begin(Calc1 *me, QEvent const *e) {
switch (e->sig) {
case IDC_OPER:
if (((CalcEvt *)e)->keyId == IDC_MINUS) {
Q_TRAN(VPTR(Calc1, me)->negated1); /*polymorphic transition*/
return 0;
}
break;
}
return (QSTATE)VPTR(Calc1, me)->ready; /*polymorphic superstate*/
}
7
8 BEGIN_VTABLE(Calc2, Calc1) /* initialize the Calc2 v-table */
9 VMETHOD(Calc1, operand2) = /* bind the implementation */
10 (QSTATE (*)(Calc1*, QEvent const *))Calc2_operand2;
11 END_VTABLE
12
13 Calc2 *Calc2Ctor_(Calc2 *me) {
14 if (!Calc1Ctor_(&me->super_)) { /* invoke superclass' ctor */
15 return 0;
16 }
17 VHOOK(Calc2); /* hook the v-pointer to the Calc2 v-table */
18 return me;
19 }
20
21 QSTATE Calc2_operand2(Calc2 *me, QEvent const *e) {
22 switch (e->sig) {
23 case IDC_PERCENT:
24 sscanf(me->super_.display_, "%lf", &me->super_.operand2_);
25 switch (me->super_.operator_) {
26 case IDC_PLUS:
27 me->super_.operand2_ = 1.0 + me->super_.operand2_/100.0;
28 me->super_.operator_ = IDC_MULT;
29 break;
30 case IDC_MINUS:
31 me->super_.operand2_ = 1.0 - me->super_.operand2_/100.0;
32 me->super_.operator_ = IDC_MULT;
33 break;
34 case IDC_MULT: /* intentionally fall through... */
35 case IDC_DIVIDE:
36 me->super_.operand2_ /= 100.0; /* x*y%, x/y% */
37 break;
38 default:
39 ASSERT(0);
40 }
41 Q_TRAN(VPTR(Calc1, me)->result);
42 return 0; /* event handled */
43 }
44 /* let Calc1 handle other events */
45 return Calc1_operand2((Calc1 *)me, e);
46 }
The points of interest in Listing 6.4 include subclassing Calc1 (lines 1–6), ini-
tializing the virtual table (lines 8–11), and delegating unhandled events to the orig-
inal Calc1 state handler (line 45). The derived Calc2 class defines its own virtual
table (line 2) and a new implementation of the operand2 state handler (line 5).
Subsequently, this implementation is bound to the method in the virtual table (lines
9–10), and the v-table is hooked in the constructor (line 17). The C implementa-
tion of Calc2_operand2() is essentially identical to the Calc2::operand2() C++
180 Chapter 6: Inheriting State Models
counterpart (refer to Listing 6.2). The only differences are in the polymorphic state
transition (line 41) and the delegation of unhandled events (line 45). Please note
that the delegation cannot be dynamic (an attempt to resolve the call via a v-
pointer ends up in endless recursion) and is analogous to the C++ delegation that
also uses a static, fully qualified delegation via a call to Calc1::operand2().
Exercise 6.5 Find the C version of the VC++ project qcalc2.dsp on the accompany-
ing CD-ROM and add an entry action to the Calc2_opearator2()
state handler (e.g., the action could invoke the Win32 function Beep()).
Rebuild the project and note that only the calc2.c module needs to be
recompiled. Launch the application and verify that your entry action is
indeed executed on entry to the operator2 state.
6.3 Caveats
In most cases, the inheritance of state machines works just fine right out of the
box, as demonstrated by the refined Quantum Calculator example. However,
problems might arise when you start using inheritance in a more advanced way.
This section explains some potential issues and workarounds.
the flexibility needed for refinement via inheritance because you cannot store two
(or more) different transition chains in one static variable.
The solution is to forego optimization and recalculate the transition chain
dynamically (each time the transition fires) for every polymorphic transition requir-
ing the extra flexibility (e.g., the transitions in the Controller base class). The QHsm
class provides such a dynamic, unoptimized transition method — QHsm::tran()
(QHsmTran_() in C) — which you use through the Q_TRAN_DYN() macro.
Exercise 6.6 Replace all occurrences of the Q_TRAN() macro with Q_TRAN_DYN() in
the Calc1 base class. Recompile and execute the refined Calc2 applica-
tion. Verify that it operates as before.
Note: Depending on the C++ compiler and the order of the multiple base
classes in the declaration, MI might also work with the behavioral
inheritance meta-pattern. The point is, the code is not portable.
8. I am grateful to Jeff Claar for contributing this solution. It is somewhat similar to the original GotW problem
#57 posted and solved on Usenet [Sutter 01].
182 Chapter 6: Inheriting State Models
private:
QState myPtMF; // encapsulated pointer-to-member function
};
. . .
};
The callMemberFn() method that the functor invokes is declared as purely
virtual in the QHsm base class and subsequently must be overridden in every sub-
class of QHsm. The following macro automates the definition of this method in
each concrete subclass.
#define Q_DEFINE_CALL_MEMBER_FN(class_) \
virtual CQState callMemberFn(CQState::QState fn, \
QEvent const *e, QHsm *hsm)\
{\
class_ *c = static_cast<class_*>(hsm); \
return (c->*fn)(e); \
}
Exercise 6.7 Find the MI-compatible version of the extended Quantum Calculator
code and add a second base class to the refined Calc2 class. Note that
every state machine class invokes Q_DEFINE_CALL_MEMBER_FN() and
passes its class name to the macro. Verify that the application compiles
and executes correctly.
tors) are closely related. The resulting code is much smaller than it would be without
inheritance, and its maintenance is easier (e.g., fixing a bug in the simple version of
the product fixes it automatically in the more advanced version). Finally, inheritance
enforces a consistent look and feel across the product line because any changes prop-
agate automatically from base classes to the offspring (e.g., from Calc1 to Calc2).
However, to reap the benefits of inheritance, class taxonomies need to comply
with rules that have been proven by experience, such as the LSP. Generally, compli-
ance with the LSP is much harder to achieve in hierarchies of reactive classes than in
classes without state behavior. The following sections give you some ideas of how to
refine statecharts, starting with the most strict (and safe) category of refinements
through progressively more liberal (and more dangerous) ones. You should strive to
stay with the most strict category that accomplishes your goals. Of course, this dis-
cussion pertains only to single inheritance (see Section 6.3.2).
Template Method
The simplest — and safest — refinement of a reactive class is not to change state-
chart topology at all. However, you can still significantly change the behavior of
the subclasses by overriding actions and guards that the base class defines as vir-
tual methods. In this case, the reactive base class implements the invariant parts
of the behavior (the statechart topology) and leaves it up to subclasses to imple-
ment actions and guards that can vary. This is an example of the widely used
Template Method design pattern [Gamma+ 95]. In fact, reactive classes make
very good natural Template Methods.
The base class has a fine granularity of control over the statechart elements it
allows the subclasses to change. The base class can use C++ access control to
restrict access to certain methods, or it can refuse to declare certain actions and
guards virtual, which also effectively prohibits subclasses from overriding
them.
The enhanced Quantum Calculator code provides an example of this tech-
nique. For example, the actions clear(), insert(), negate(), and eval() are
all declared virtual in the Calc1 base class and can therefore be overridden by
the subclasses.
The technique is even more explicit in the C implementation because the Calc1
base class uses explicit dynamic binding to invoke the polymorphic actions. The fol-
lowing state handler for the result state illustrates the polymorphically invoked
184 Chapter 6: Inheriting State Models
Subtyping
The refinement policy for subtyping restricts statechart modifications to only
those that preserve pre- and postconditions of the base class for all events that it
accepts, which guarantees the substitutability of subclasses for the superclass.
Because both states and transitions realize the pre- and postconditions for
events in a state machine, this policy prohibits removing states and transitions.
You can freely add new states and transitions, but you can refine existing states
and existing transitions, guards, and actions only as follows [OMG 01].
• You can refine a state by adding new outgoing transitions as well as new sub-
states.
• You can refine an outgoing transition to target a direct or transitive substate of
the original target state (i.e., the transition can penetrate deeper into the state
hierarchy, but it still has to enter the original target state). This refinement
guarantees the postcondition established by entry to the original target state.
• You can refine a guard only by strengthening the condition, which weakens
any preconditions necessary for the transition.
• You can refine an action sequence by prepending or appending new actions,
but you should not alter the original action sequence.
Refinement of the Quantum Calculator falls into the subtyping category. The
refined operand2 state acquired a new outgoing transition triggered by the PER-
CENT signal.
Strict Inheritance
Strict inheritance still requires that you only add new features or override exist-
ing features; you cannot delete any existing elements. You can alter existing stat-
echart elements as follows.
Summary 185
• You can alter a transition to target a different state that is not necessarily
related (via behavioral inheritance) to the original target state. However, you
cannot change the source of the transition because this would correspond to
removing an outgoing transition from the original source state.
• You cannot change the superstate of any state (reparenting).
• You can change a guard to check a different condition that is not necessarily
related to the original.
• You can change an action sequence by dropping some of the original actions,
replacing them with others, or prepending or appending new actions. How-
ever, you should not alter the sequence of the original actions that remain
after the refinement.
Strict inheritance still preserves limited substitutability of subclasses for the
superclass. Although the refined behavior is no longer fully compatible, it is
often good enough when your objective is reuse of the implementation rather than
behavioral compatibility.
General Refinement
In most general cases, you may freely change states, transitions, guards, and
actions. You also may remove (or hide) features, which is sometimes called
reverse inheritance. However, this freedom will cost you compliance with the
LSP.
The behavioral inheritance meta-pattern allows all kinds of statechart modi-
fications, including most general refinements and the removal of features. For
example, you can achieve the effect of removing a state by redirecting all incom-
ing transitions away from it and by reparenting all its direct substates. Removing
a state transition is even easier. In the refined state handler, you simply return
zero for the unwanted transition, which means that the signal is handled but no
transition occurs.
6.4 Summary
The behavioral inheritance meta-pattern works correctly under traditional class
inheritance, which enables easy reuse and refinement of entire state models. The
form of reuse achievable in reactive classes is much higher than is available for
nonreactive classes because you inherit behavior in addition to inheriting struc-
ture.
The challenge in inheriting state machines is in preserving the numerous
associations among states in the inherited and refined state machine topology.
186 Chapter 6: Inheriting State Models
In C++, you enable inheritance of a state machine simply by declaring all its state
handlers virtual. Your C++ compiler takes care of the correct polymorphic behav-
ior of the whole statechart topology because the underlying pointer-to-virtual-mem-
ber functions act polymorphically. However, in the current C++ implementation, the
behavioral inheritance meta-pattern is incompatible with multiple inheritance.
In C, you must explicitly arrange for polymorphism by declaring and initializing
the virtual table for the reactive class and by dynamically resolving the reference to
each state handler (through the virtual pointer). In this chapter, I demonstrated how
to achieve this using “C+” — an OO extension to C (see Appendix A).
In rare occasions, when you anticipate instantiation of different reactive sub-
classes of a given base class in the same system, you need to be careful to use the
unoptimized dynamic transition Q_TRAN_DYN() macro in the shared statechart of
the base class instead of the optimized Q_TRAN() macro. In this case, the optimiza-
tion built into the Q_TRAN() macro inhibits the extra flexibility you need in the
shared state machine.
To achieve compliance with the LSP, you should restrict refinements you apply to
state machines. In general, you always can add new elements, but you should avoid
removing any existing states and transitions.
From a more abstract point of view, the unprecedented degree of reuse achievable
by inheriting entire state models results from the unification of Quantum Program-
ming (behavioral inheritance) with traditional object-oriented programming (class
inheritance).
PART II
PART II
Q UANTUM F RAMEWORK
In Part I, I showed you how to implement and use the powerful concept of the hier-
archical state machine by instantiating the behavioral inheritance meta-pattern. This
meta-pattern intentionally provided only the passive event processor, which must be
driven externally to actually process events. In particular, the meta-pattern did not
include the standard elements traditionally associated with state machines, such as
an event queue, an event dispatcher, an execution context (thread), or timing ser-
vices. This separation of concerns occurs for at least two reasons. First, unlike the
generic event processor, the other elements necessary to execute a state machine
depend heavily on the application domain and operating system support. Second, in
many cases, these elements are already available. For example, GUI systems such as
Microsoft Windows offer a complete event-driven environment for executing passive
state machines.
However, the majority of reactive systems, most notably embedded real-time sys-
tems, do not provide such an execution infrastructure. In Part II of this book, I
describe the Quantum Framework — an event-driven architecture for executing state
machines tailored specifically for embedded real-time systems. A real-time frame-
work similar to the Quantum Framework is at the heart of virtually every commer-
cial design-automation tool capable of generating real-time code. In fact, most of
these real-time frameworks are based on the model of concurrent state machines that
communicate with each other by sending and receiving events. In Part II, I conclude
187
188 PART II: QUANTUM FRAMEWORK
the mission of this book, which is to present a complete solution for programming
real-time systems with UML statecharts. The Quantum Framework combined with a
preemptive real-time operating system will give you the functional equivalent of a
code-synthesizing tool for developing real-time applications.
7
Chapter 7
State machines cannot operate in a vacuum. Apart from the event processor (sup-
plied by the behavioral inheritance meta-pattern), the execution environment for a
state machine must provide, at a minimum, the execution context (thread) and the
event queuing, event dispatching, and timing services. These elements strongly
depend on application domain and operating system support. However, within a
given domain, they change little from system to system, and their sufficiently robust
representations can be reused in many applications, rather than being developed
from scratch each time. For example, you could reuse an event queue or a timeout
event generator across many projects. However, you can do even better than merely
reuse specific elements as building blocks — you can reuse the whole infrastructure
surrounding state machines.
189
190 Chapter 7: Introducing the Quantum Framework
continues to think. (An alternative oriental version replaces spaghetti with rice and
forks with chopsticks.) The key question is: Can you write a program for each phi-
losopher that never gets stuck?
Although mostly academic, the problem is motivated by the practical issue of how
to assign resources to processes that need the resources to do their jobs; in other
words, how do you manage resource allocation. The idea is that a finite set of
threads is sharing a finite set of resources, and each resource can be used by only one
thread at a time.
Dijkstra's Semaphores
In 1965, Edsger Dijkstra invented semaphores as a protocol mechanism for synchro-
nizing concurrent threads [Dijkstra 65]. Dijkstra defined two primitive operations on
semaphores: P() (from Dutch “proberen”—test) and V() (from Dutch “verho-
gen”—increment). To obtain a semaphore a thread invoks the P() operation on the
semaphore, which returns only if the semaphore is available; otherwise the calling
thread blocks and waits for the semaphore. The V() operation releases the lock and
frees the semaphore to other threads.
Typically, mutlitasking operating systems provide an assortment of semaphores
optimized for different functions. The classical counting semaphore maintains the
lock counter and is optimized for guarding multiple instances of a resource. A special
case of counting semaphore is the binary semaphore, which can be locked by only one
thread at a time. A mutex semaphore (or simply mutex) is optimized for problems
inherent in mutual exclusion.
Listing 7.1 Simple (and incorrect) solution to the dining philosophers problem
implemented with Win32 API. The explicit fork flags are superfluous in
this solution because they are replaced by the internal counters of the
mutexes
The solution from Listing 7.1 still has a major flaw. Your program might run for a
few milliseconds or for a year (just as the first naïve solution did), but at any
moment, it can freeze with all philosophers holding their left fork (Listing 7.1, line
12). If this happens, nobody gets to eat — ever. This condition of indefinite circular
blocking on resources is called deadlock.
Exercise 7.1 Execute the dining philosophers example from Listing 7.1. Name at least
three factors that affect the probability of a deadlock. Modify the code to
increase this probability. After the system (dead)locks, use the debugger
to inspect the state of the forks and the philosopher threads.
Once you realize the possibility of a deadlock (which generally is not trivial), be
careful how you attempt to prevent it. For example, a philosopher can pick up the
left fork; then if the right fork isn’t available for a given time, put the left fork down,
wait, and try again (this is a big problem if all philosophers wait the same amount of
time — you get the same failure mode as before, but repeated). Even if each philoso-
pher waits a random time, an unlucky philosopher could starve (never get to eat).
Starvation is only one extreme example of the more general problem of nondeter-
minism because it is virtually impossible to know in advance the maximum time a
philosopher might spend waiting for forks (or how long a philosopher thread is pre-
empted in a preemptive multitasking system).
Any attempt to prevent race conditions, deadlock, and starvation can cause other,
more subtle, problems associated with fairness and suboptimal system utilization. For
example, to avoid starvation, you might require that all philosophers acquire a sema-
phore before picking up a fork. This requirement guarantees that no philosopher
starves, but limits parallelism dramatically (poor system utilization). It is also difficult
to prove that any given solution is fair and does not favor some philosophers at the
expense of others.
The main lesson of dining philosophers is that multithreaded programming is
much harder than sequential programming, especially if you use a conventional
approach to multithreading. The conventional design requires a deep understanding
of the time domain and operating system mechanisms for interthread synchroniza-
tion and communication, such as various kinds of semaphores, monitors, critical sec-
tions, condition variables, signals, message queues, mailboxes, and so on.
Unfortunately, programmers typically vastly underestimate the skills needed to pro-
gram with operating system primitives and therefore underestimate the true costs of
their use. The truth is that only a relatively limited group of systems programmers is
familiar with and comfortable using these mechanisms properly. The majority of us
are likely to introduce subtle bugs that are notoriously hard to reproduce, isolate,
and fix.
Conventional Approach to Multithreading 195
1. Nancy Leveson performed such an analysis in Safeware: System Safety and Computers [Levenson 95].
196 Chapter 7: Introducing the Quantum Framework
inconsistent configuration (partially set for X-ray treatment and partially set for elec-
tron treatment). These exact conditions occurred on April 11, 1986, when the
machine killed a patient (see the sidebar “Therac-25 Fatal Incident”).
Although the Therac-25 software was developed almost three decades ago 2 and
was written in PDP-11 assembly language, it bears many similarities to the Visual
Basic Calculator example discussed in Chapter 1. Both applications are similar, in
that they do not maintain a crisp notion of mode of operation but, rather, represent
modes of operation as a multitude of variables and flags. These variables and flags
are set, cleared, and tested in complex expressions scattered throughout the code so
that it is virtually impossible to determine the mode of the system at any given time.
As demonstrated by the Visual Basic Calculator, this approach leads to subtle bugs in
an application of even a few hundred lines of code. The Therac-25 case, however,
shows that, when additionally compounded with concurrency issues, the ad hoc
approach leads to disastrous, virtually uncorrectable designs. For example, in an
attempt to fix the Therac-25 race condition described earlier, the manufacturer
(Atomic Energy of Canada Limited) introduced another shared variable controlled
by the keyboard handler task that indicated whether the cursor was positioned on
the command line. If this variable was set, then the prescription entry was considered
still in progress and the value of the Tphase state variable was left unchanged. The
following items point out some inherently nasty characteristics of such ad hoc solu-
tions.
• Any individual inconsistency in configuration seems to be fixable by introducing
yet another mode-related (extended state) variable.
• Every new extended state variable introduces more opportunities for inconsisten-
cies in the configuration of the system. Additionally, if the variable is shared
among different threads (or interrupts), it can introduce new race conditions.
• Every such change perpetuates the bad design further3 and makes it exponentially
more difficult (expensive) to extend and fix, although there is never a clear indica-
tion when the code becomes unfixable (prohibitively expensive to fix).
• It is practically impossible to know when all inconsistent configurations and race
conditions are removed. In fact, most of the time during computations, the config-
uration is inconsistent, but you generally won’t know whether it happens during
the time windows open for race conditions.
• No amount of testing can detect all inconsistencies and timing windows for race
conditions.
2. The Therac-25 software has been reused from earlier versions of the Therac machine.
3. This process is otherwise known as architectural decay.
Computing Model of the QF 197
• Any change in the system can affect the timing and practically invalidate most of
the testing.
You might think that the Therac-25 story, albeit interesting, is no longer relevant
to contemporary software practices. This has not been my experience. Unfortunately,
the architectural decay mechanisms just recounted still apply today, almost exactly as
they did three decades ago. The modes of architectural decay haven’t changed much
because they are characteristics of the still widely practiced bottom-up approach to
designing reactive systems mixed with the conventional approach to concurrency.
4. To learn more about ChorusOS you can visit the Web site: http://www.sun.com/software/chorusos
5. To learn more about QNX Neutrino microkernel architecture you can visit the Web site:
http://www.qnx.com/literature/nto_sysarch/kernel3.html
198 Chapter 7: Introducing the Quantum Framework
6. Active objects are ideal Facades (see the Facade design pattern in Gamma and colleagues [Gamma+ 95].)
Computing Model of the QF 199
(a) (b)
g
A
e—
e— A g
B
B
time
e—
In contrast, all interactions result from particle exchange in quantum field theory.
Nature seems to have “invented” specific objects (intermediate vector bosons 7),
7. Intermediate vector bosons include photons mediating electromagnetic interactions, gluons mediating strong
interactions, and bosons W and Z mediating weak interactions.
200 Chapter 7: Introducing the Quantum Framework
8. An alternative is to blindly send every event to all active objects, which is wasteful and as inefficient as unsolic-
ited advertisement (junk mail).
9. This idea is central to all high-energy physics experiments. The experiments probe the “true nature” of elemen-
tary particles because they can reveal only those behaviors that happen spontaneously, regardless of whether
particles are observed or not. The objective of every experiment is to “catch” a particle doing something interest-
ing, but an experiment cannot induce any particular behavior that does not already exist.
202 Chapter 7: Introducing the Quantum Framework
10. For simpler designs, the QF can operate without an RTOS (effectively replacing it). See the discussion in Section
7.3.2 and a concrete implementation in Chapter 9.
Computing Model of the QF 203
Figure 7.3 UML package diagram illustrating relationships among the RTOS, the
QF, and the QF application
concrete
concrete
active
events
objects
QF Application
«active» «active»
EvtA EvtB
ActiveA ActiveB
«framework»
QF
«active»
QHsm QEvent QTimer
QActive
RTOS
Framework
API
Memory
Thread Queue RTOS
Pool
API
Figure 7.4 Sequence diagram showing event exchange in the active object–based
solution of the DPP
The class diagram in Figure 7.5 shows that the QF application comprises the
Table and Philosopher active objects and the specialized TableEvt class. This
diagram has a typical structure for an application derived from a framework.
Concrete application components (active objects and events in this case) derive
from framework base classes (from QActive and QEvent) and use other frame-
work classes as services. For example, every Philosopher has its own QTimer
(quantum timer) to keep track of time when it is thinking or eating.
Figure 7.5 Table and Philosopher active objects and the TableEvt class derived
from the QF base classes
QActive QHsm
QF classes QEvent
The Table and Philosopher active objects derive indirectly from QHsm, so they
are state machines. In fact, your main concern in building the application is elaborat-
ing their statecharts. Figure 7.6a shows the statechart associated with Table. It is
trivial because Table keeps track of the forks and hungry philosophers by means of
extended state variables (myFork[] and isHungry[] arrays, Figure 7.5), rather than
by its state machine.
The Philosopher state machine (Figure 7.6b) clearly shows the life cycle of this
active object consisting of states thinking, hungry, and eating. This statechart
publishes the HUNGRY event on entry to the hungry state and the DONE event on exit
from the eating state because this exactly reflects the semantics of these events. An
alternative approach — to publish these events from the corresponding TIMEOUT
transitions — would not guarantee the preservation of the semantics in potential
future modifications of the state machine.
Figure 7.6 (a) Table statechart and (b) Philosopher statechart; the ^DONE(n)
notation indicates propagation of the DONE event (publishing the
event)
• When a philosopher finishes eating, any hungry neighbor starts eating (in active
object–based design, this is handled by the Table active object; refer to the han-
dling of the DONE event in Figure 7.4)
11. Inventing a metaphor is one of the key practices of eXtreme Programming (XP) [Beck 00].
Roles of the QF 207
solves various problems. The metaphor thus serves as an objective arbiter in resolv-
ing various design conflicts and ultimately guards the conceptual unity of the design.
In this context, the quantum mechanical analogy (metaphor) is the valuable con-
tribution of the QF, and QP, in general. As explained by means of the hydrogen atom
example in Chapter 2 and the virtual photon exchange example in this chapter, the
quantum metaphor consists of the following two elements.
• Reactive systems are analogous to quantum systems, in that they are always
found in strictly defined discrete states (quantum states) and can change their
state only by means of uninterruptible RTC steps (quantum leaps). The states are
naturally hierarchical and must comply with the Liskov Substitution Principle
(LSP) for states. Behavioral inheritance resulting from state nesting is fundamen-
tal because it expresses various symmetries within the system.
• The active object–based computing model is analogous to the quantum field the-
ory, in that the only interaction allowed among reactive objects (hierarchical state
machines) is the explicit exchange of event instances (intermediate virtual parti-
cles). The quantum model of interaction corresponds to the publish–subscribe
model in software.
Admittedly, the quantum metaphor is not as familiar to an average programmer
as the desktop metaphor. However, the physics background necessary to benefit from
this analogy doesn’t go beyond the level of popular science articles. Most impor-
tantly, the quantum metaphor is accurate for concurrent reactive systems.
Although the basic services provided by RTOSs are very much the same, they are
accessible through significantly different APIs. This poses a serious problem for
many companies that want to deploy shared application code on different operating
systems or don’t want to lock their strategic applications into a particular operating
system. A common solution to this problem is to create a proprietary RTOS abstrac-
tion layer with the sole function of isolating the applications from RTOS differences.
Although such indirection layers aren’t complicated, they always incur some over-
head and add to the problem by introducing yet another proprietary RTOS API. 12
An active object–based framework such as the QF offers a more elegant solution.
As mentioned earlier, applications derived from such a framework don’t need to use
low-level RTOS primitives directly — the framework effectively isolates applications
from the underlying RTOS; that is, changing the RTOS on which the framework is
built requires porting the framework code but does not affect applications. At the
same time, an active object–based framework is more than just a thin RTOS wrapper
because it directly supports active object–based multithreading, as opposed to tradi-
tional RTOSs that support only conventional multithreading.
If you use a proprietary homegrown RTOS or just have control over the RTOS
source code, you can consider integrating it with the QF into one entity. In fact,
RTOSs based on microkernel architecture are already very close to implementing an
active object–based framework like the QF. From my experience with several real-
time kernels for Motorola 68000, ARM, and ARM/THUMB microcontrollers, inte-
grating an RTOS into the QF is not more difficult than porting the framework to a
different operating system (porting the QF is discussed in Chapter 9). Interestingly,
because of the spareness of concepts used in the QF, the combined solution is typi-
cally smaller than the sum of its parts.
12. For example, the Rhapsody CASE tool from I-Logix uses an RTOS abstraction layer called Abstract Operating
System [Douglass 99].
Roles of the QF 209
steering system. Although a GPS receiver typically has a powerful 32-bit micro-
controller on board that could easily accommodate additional functions, 13 often a
closed software architecture forces the designers to implement additional functions
in separate hardware. Worse, both the GPS receiver and the extra hardware spend
significant resources on communications and are therefore more complex than neces-
sary for the job at hand. In other words, instead of an integrated single-processor
design, the system unnecessarily becomes a multiprocessor distributed system and
has to cope with all the headaches associated with distributed designs. 14
Moore’s Law
In the 1960s, Gordon Moore [Moore 65] observed that Intel’s memory chips were
doubling in transistor count with every generation, and a new generation appeared
roughly every 18 months. Although Intel has since gotten out of the RAM commodity
business, Moore’s observation, which the press dubbed Moore’s Law, has been
remarkably accurate over the last few decades.
The main reason it is not easy for designers to get different functions to share
the available hardware is the lack of a “software bus” that would enable tighter
software integration, much as hardware buses enable integration of hardware
components. Naturally, the concept of a software bus has been around for a long
time and has been realized in such software architectures as CORBA, DCOM, and
recently .NET. However, these heavy-weight architectures are designed for powerful
desktop and server platforms and are unsuitable for most embedded systems. They
also address a different need — integrating distributed systems communications over
networks.
However, many deeply embedded systems need a simpler, light-weight software
bus to integrate various software components efficiently within a single address
space. An active object–based framework, like the QF, provides such an open archi-
tecture as well as the API for integrating software components (active objects). If the
framework is based on a preemptive kernel, then adding active objects at a lower pri-
ority does not affect the timing of active objects running at a higher priority; 15 thus,
it enables hard real-time functions to be integrated alongside soft real-time functions.
In the case of a GPS navigation system, an original equipment manufacturer
(OEM) of a GPS receiver can provide the hardware and extensible software, which
consists of high-priority active objects implementing the core GPS functions. These
core active objects can be provided in compiled form (e.g., as a class library) because
13. For example, many GPS receivers already have integrated LCD controllers for graphics applications or pulse-
width modulation hardware for vechile steering applications.
14. Refer, for example, to Selic [Selic 00] for discussion of challenges specific to distributed software designs.
15. Active objects do not to share any resources and do not block each other.
210 Chapter 7: Introducing the Quantum Framework
the details of GPS signal tracking and interfacing to proprietary GPS correlator hard-
ware are typically among the most guarded trade secrets. The OEM does not have to
invent an API to open up the software architecture of the product (the QF already
provides such an API), so it can concentrate only on specifying and documenting
events (hooks) produced and consumed by the active objects with core functionality.
With this specification, third-party designers can use the framework API to integrate
additional functionality — such as graphics active objects for a mapping system —
without adding any extra hardware or layers of communication. 16 The resulting
product is simpler, smaller, more reliable, less expensive and gets to market faster.
16. One of the most complex aspects of a commercial GPS receiver is its support for various communications proto-
cols and formats.
Roles of the QF 211
17. Throughout this book, I assume that you have the Microsoft Visual C++ development suite.
212 Chapter 7: Introducing the Quantum Framework
identifiers of your choice rather than generic names. The wizard works by cutting
appropriate snippets of code from its internal templates and pasting them into your
application according to your preferences. The MFC framework facilitates this
approach by prescribing points of customization (framework extension points) and
providing much of the default behavior (which does not need generated code). If
your application falls into one of the supported categories, automatic code genera-
tion can give you a fast head start. However, you will be less lucky if some aspects of
your application lie off the beaten path. In all cases, the generated code is only an
empty skeleton without any specific behavior.
The automated cutting and pasting techniques found in the MFC AppWizard are
the simplest forms of code synthesis. CASE tools that support state machines add sig-
nificantly more advanced capabilities by generating code pertaining to the specific
behavior of the application. This code is almost universally based on the constructive
nature of statecharts, which means that statecharts have sufficiently precise seman-
tics to allow translating them to code. A typical CASE tool with these capabilities
resembles a specialized graphical editor for drawing state machines. The tool pro-
vides a palette of state machine components (states, transitions, pseudostates), which
you drop on the drawing pad and manipulate to construct the desired state machine
topology. Each of the state machine elements has an associated set of properties that
you can modify through specific dialog boxes. For example, properties of the transi-
tion component include a trigger, guard, and action. You supply the guard and
action by typing source code in a concrete programming language into the dialog
box (e.g., C, C++, or Java). Similarly, for the state component, you specify entry
actions, exit actions, and internal transitions, again by typing concrete code into the
state properties dialog box. The main contribution of the CASE tool is the ability
to generate housekeeping code from the graphically defined topology of the state
machine by instantiating a state machine pattern similar to the behavioral inherit-
ance meta-pattern of the QF. On the other hand, the tool merely cuts and pastes the
snippets of code you attached to state machine components via the dialog boxes.
7.4 Summary
In this chapter, I introduced the concept of the Quantum Framework (QF) — a
reusable infrastructure for executing concurrent state machines — which is an
application framework optimized for the embedded real-time systems domain.
The QF hinges on the active object–based computing model, in which concur-
rently executing state machine objects, called active objects, interact with one
another by exchanging event instances. This communication model offers many
advantages over the free threading approach, which poses many challenges, like
race conditions, deadlocks, starvation, and indeterminism, to name just a few. These
Summary 213
Design of the
Quantum Framework
The worst buildings are those whose budget was too great
for the purposes to be served.
— Frederick P. Brooks, Jr.
An active object–based framework, such as the Quantum Framework (QF), sits at the
focus of many conflicting forces that the framework must ultimately resolve in the
applications’ interest. The stakes are high because the applications are so dependent
on the framework that any wrong decision in its design or implementation could ren-
der the framework inadequate for whole classes of applications. Perhaps nowhere is
this more true than in the embedded real-time1 domain. For example, many embedded
systems are extremely cost sensitive, so the framework must be efficient in both mem-
ory and CPU utilization. Moreover, real-time systems are particularly intolerant to any
form of nondeterminism because it can cause the systems to miss deadlines and fail.
1. I intentionally use the terms “embedded” and “real-time” together because, in practice, almost all embedded
systems have real-time constraints.
215
216 Chapter 8: Design of the Quantum Framework
To be effective, the framework must take carefully into account the realities of the
application domain it serves. But what exactly are these realities? The embedded
software domain is so diverse and fragmented that it is necessary first to define the
subset of embedded real-time systems that the QF addresses. Furthermore, it is
important to investigate how embedded real-time systems differ from other com-
puter systems because, only then, will you understand the motivation for such
important QF policies as error and exception handling, memory management, con-
currency handling, event passing, initialization, cleanup, and time management.
Although the focus of this chapter is on the design of the framework, you will
also find concrete code for platform-independent components of the QF. In the next
chapter, I fill in the platform-dependent elements and explain how to port the QF to
different operating systems or to use it stand-alone, without an underlying multitask-
ing kernel.
Code fragments presented in this chapter pertain only to the C++ version of the
framework. The C version (available on the accompanying CD-ROM) is essentially
identical because it implements the same underlying design. Therefore, even if you
are only interested in the C version, you should study the C++ code and explanations
because they apply equally to the C implementation. If the idea of object-oriented
programming in a procedural language like C is new to you, please refer to Appen-
dix A, which describes a set of techniques and idioms that you can use to implement
classes, inheritance, and polymorphism in C.
definition emphasizes that embedded systems pose different challenges and require
different programming strategies than general-purpose computers.
This distinction is important. Perhaps most (unnecessary) complications com-
monly introduced into embedded software have their roots in projecting require-
ments from the desktop onto the embedded real-time domain. I disagree with the
opinion that embedded real-time developers face all the challenges of “regular” soft-
ware development plus the complexities inherent in embedded real-time systems
[ObjecTime 97]. Although each domain has its fair share of difficulties, each also
offers unique opportunities for simplification, so embedded systems programmers
specifically do not have to cope with many problems encountered on the desktop.
Attempts to reconcile the conflicting requirements of both the desktop and the
embedded real-time domain can turn embedded real-time programming into a
daunting process,2 to be sure, but that just makes your job harder than it needs to
be.
2. See the back cover of Doing Hard Time by Bruce Powel Douglass [Douglas 99].
218 Chapter 8: Design of the Quantum Framework
clear, single purpose in life. In contrast, a desktop system doesn’t have such a single
purpose — by definition, it must be able to accommodate many different functions at
different times or even simultaneously. As far as hardware is concerned, no desktop
application can rely on a specific amount of physical memory available to it or on
how many and what kind of disk drives and other peripherals are present and avail-
able at the moment. The software environment is even less predictable. Users fre-
quently install and remove applications from all possible sources. All the time, users
launch, close, or crash their applications — drastically changing the CPU load and
availability of memory and other resources. The desktop operating system has the
tough job of allocating CPU time, memory, and other resources among constantly
changing tasks in such a way that each receives a fair share of the resources and no
single task can hog the CPU. This scheme is diametrically opposed to the needs of an
embedded system, in which a specific task must gain control right now and run until
it produces the appropriate output. Fairness isn’t part of embedded real-time pro-
gramming — getting the job done is.
Over the last half century or so, the software community has concentrated mostly
on effective strategies to cope with the challenges of general-purpose computing. Per-
haps because of this long tradition, the resulting strategies and rules of thumb have
become so well established that programmers apply them without giving them a sec-
ond thought. Yet, the desktop solutions and rules of thumb are often inadequate (if
not outright harmful) for the vast majority of embedded real-time applications.
Programmers of embedded systems can be much more specific than programmers
of general-purpose computers, and specific solutions are always simpler and more
efficient than general ones. The following sections show several areas in which the
design of the QF enables you to take advantage of the specifics of embedded systems
programming and simplifies the implementation compared to traditional solutions
borrowed from the desktop. These areas include (1) handling errors and exceptional
conditions, (2) memory management, (3) passing events, (4) initialization and
cleanup, and (5) time management, which is pretty much everything there is to it!
finding and ultimately fixing it, rather than designing a recovery strategy. This situa-
tion is in contrast to the exceptional condition, which is a specific situation that can
legitimately arise during the system lifetime but is relatively rare and lies off the main
execution path of your software. In contrast to an error, you need to design and
implement a strategy that handles the exceptional condition.
Embedded systems offer many more opportunities than desktop applications to
flag a situation as a bug (that you should prevent from happening), rather than an
exceptional condition (that you must handle). Consider, for example, dynamic mem-
ory allocation (the next section discusses memory management in detail). In any type
of system, memory allocation through malloc() (or the C++ operator new) can fail.
On the desktop, a failed malloc() merely indicates that, at this moment, the operat-
ing system cannot supply the requested memory. In a highly dynamic general-pur-
pose computing environment, this can happen easily. When it happens, you have
options to recover from the situation. One option might be for the application to free
up some memory that it allocated and then retry the allocation. Another choice
could be to prompt the user that the problem exists and encourage her to exit other
applications so that the current application can gather more memory. Yet another
option is to save data to the disk and exit. Whatever the choice, handling the situa-
tion requires some drastic actions, which are clearly off the mainstream behavior of
your application. Nevertheless, you should design and implement such actions
because in a desktop application, a failed malloc() must be considered an excep-
tional condition.
In a typical embedded system, on the other hand, the same failed malloc() prob-
ably should be flagged as a bug.3 That’s because embedded systems offer much fewer
excuses to run out of memory, so when it happens, it’s typically an indication of a
flaw. You cannot really recover from it. Exiting other applications is not an option.
Neither is writing to a nonexistent disk. Whichever way you look at it, it’s a bug no
different from dereferencing a NULL pointer4 or overrunning an array index.5 Instead
of going out of your way in attempts to handle this condition in software (as you
would on the desktop), you should concentrate first on finding the root cause and
then fixing it (I would first look for a memory leak, wouldn’t you?).
The main point here is that many situations traditionally handled as exceptional
conditions on the desktop are in fact bugs in embedded systems. To handle various
3. Embedded systems span such a broad spectrum that there are examples where it makes sense to recover from a
failed malloc(). In such rare cases, you should treat it as an exceptional condition.
4. Thou shalt not follow a NULL pointer, for chaos and madness await thee at its end — the second command-
ment for C programmers by prophet Henry Spencer [Spencer 94].
5. Thou shalt check the array bounds of all strings (indeed, all arrays), for surely where thou typest ‘foo’ someone
someday shall type ‘supercalifragilisticexpialidocious’ — the fifth commandment for C programmers by
prophet Henry Spencer [Spencer 94].
220 Chapter 8: Design of the Quantum Framework
situations in your QF applications correctly, you should not blindly transfer rules of
thumb from other areas of programming to embedded real-time systems. Instead, I
propose that you critically ask the following two probing questions.
1. Can this rare situation legitimately happen in this system?
2. If it happens, is there anything specific that needs to or can be done in the soft-
ware?
If the answer to either of these questions is “yes,” then you should handle the situa-
tion as an exceptional condition; otherwise, you should treat the situation as an error.
The other important point is that errors require the exact opposite programming
strategy than exceptional conditions. The first priority in dealing with errors is to
facilitate finding them. Any attempt to handle a bug as an exceptional condition
results in unnecessary complications of the implementation and either camouflages
the bug or delays its manifestation. (In the worst case, it also introduces new bugs.)
Either way, finding and fixing the bug will be harder.
In the following section, I describe how the QF uses assertions to help catch bugs,
not so much in the framework, but mostly in applications derived from it. You
should extend the same basic strategy to your own code.
1 #ifndef qassert_h
2 #define qassert_h
3
4 #ifdef __cplusplus
5 extern "C" {
6 #endif
7
8 #ifndef NASSERT /* assertions enabled (not disabled)? */
9 /* callback invoked in case assertion fails */
10 extern void onAssert__(const char *file, unsigned line);
11
12 #define DEFINE_THIS_FILE \
13 static const char THIS_FILE__[] = __FILE__
14
15 #define ASSERT(test_)\
16 if (test_) { \
17 } \
18 else onAssert__(THIS_FILE__, __LINE__)
19
20 #define REQUIRE(test_) ASSERT(test_)
21 #define ENSURE(test_) ASSERT(test_)
22 #define INVARIANT(test_) ASSERT(test_)
23 #define ALLEGE(test_) ASSERT(test_)
24
25 #else /* assertions disabled */
26
27 #define DEFINE_THIS_FILE extern const char THIS_FILE__[]
28 #define ASSERT(test_)
29 #define REQUIRE(test_)
30 #define ENSURE(test_)
31 #define INVARIANT(test_)
32 #define ALLEGE(test_)\
33 if (test_) { \
34 } \
35 else
36
37 #endif
38
39 #ifdef __cplusplus
40 }
41 #endif
42
43 #endif /* qassert_h */
222 Chapter 8: Design of the Quantum Framework
Listing 8.1 shows the complete qassert.h header file, which is designed for C,
C++, or mixed C/C++ programming (lines 4–6, 39–41). The main macro ASSERT()
(lines 15–18) tests the expression that you pass to it and invokes the callback func-
tion onAssert__() when the expression evaluates to FALSE. The empty block in the
if statement might seem strange, but you need both the if and the else statements
to prevent unexpected dangling-if problems. Other macros — REQUIRE(),
ENSURE(), and INVARIANT()7 — are intended to validate preconditions, postcondi-
tions, and invariants, respectively. They all map to ASSERT() (lines 20–22) because
their different names serve only to better document the specific intent of the contract.
The callback function onAssert__() (line 10) gives clients the opportunity to
customize behavior when an assertion fails. You need to define onAssert__() some-
where in your program. If you define it in a C++ module, be careful to apply the
extern "C" linkage specification. Entry to onAssert__() is the ideal place to put a
breakpoint if you work with a debugger.
7. The names are a direct loan from Eiffel, the programming language that natively implements Design by Con-
tract.
Handling Errors and Exceptional Conditions 223
contract, however, which in this particular situation declares the failed malloc() a
bug. What does it buy you? It turns every asserted bug, however benign, into a fatal
error. If you haven’t programmed with assertions before, you might think that this
must be backwards: contracts not only do nothing to fix bugs, they also make things
worse! This is exactly the cultural difference of DBC. Recall from the previous section
that the first priority when dealing with bugs is to help catch them. To this end, a bug
that causes a loud crash (and identifies exactly which contract was violated) is much
easier to find than a subtle one that manifests itself intermittently millions of machine
instructions downstream from the spot where you could have easily caught it.
DBC complements the rest of object technology and is as important (if not more)
as classes, objects, inheritance, and polymorphism [ISE 97].9 DBC is especially valu-
able for embedded real-time systems because contracts can cover all those situations
that, in other domains, would require handling as exceptional conditions. As Ber-
trand Meyer [Meyer 97b] observes (and I cannot agree more):
It is not an exaggeration to say that applying Eiffel’s assertion-based O-O
development will completely change your view of software construction …. It puts the
whole issue of errors, the unsung part of the software developer’s saga, in a completely
different light.
I cannot do justice to the subject of all the creative ways in which contracts can
help you detect bugs. The QF will provide you with some concrete examples of strate-
gic, as well as tactical, contracts that are specific to the embedded real-time domain. 10
8. Note the use of ALLEGE() rather than ASSERT(), because the side effect (setting p) is certainly important for
proper program operation, even when assertions are disabled. I am still emphasizing asserting malloc()
because it is such a no-no in desktop programming.
9. It is absolutely amazing that the UML specification does not provide any built in support for such a fundamen-
tal methodology as DBC.
10. If you want to know more, the few references I provided here can give you a good start. In addition, the Internet
is full of useful links.
224 Chapter 8: Design of the Quantum Framework
cialize). This example is just another instance of the Ultimate Hook state pattern
(Chapter 5).
Such state-based exception handling is typically a combination of the Ultimate
Hook and the Reminder state patterns (Chapter 5). Whenever an action within an
active object encounters an exceptional condition, it generates a reminder event and
posts it to self. This event, processed in the following RTC step, triggers the state-
based handling of the exceptional condition.
State-based exception handling offers a safe and language-independent alternative
to the built-in exception-handling mechanism of the underlying programming lan-
guage. As described in Section 3.6 in Chapter 3, throwing and catching exceptions in
C++ is risky in any state machine implementation because it conflicts with the funda-
mental RTC semantics of state machines.
Again, language-based exception handling comes from general-purpose comput-
ing, where designers of software libraries cannot be specific about handling excep-
tional situations. Typically, they don’t have enough context to determine whether a
given circumstance is a bug or an exceptional condition, so they throw an exception
just in case a client chooses to handle the situation. In C++, exception handling
incurs extra run-time overhead, even if you don’t use it. More importantly, exception
handling in C++ (even without state machines) is tricky and can lead to subtle bugs.
As Tom Cargill [Cargill 94] noticed in his paper “Exception handling: A false sense
of security”:
Counter-intuitively, the hard part of coding exceptions is not the explicit throws and
catches. The really hard part of using exceptions is to write all the intervening code in
such a way that an arbitrary exception can propagate from its throw site to its handler,
arriving safely and without damaging other parts of the program along the way.
If you can, consider leaving out C++ exception-handling mechanisms from your
embedded software (e.g., EC++ intentionally does not support exceptions). If you
cannot avoid it, make sure to catch all exceptions before they can cause any damage.
Remember, any uncaught exception that unexpectedly interrupts an RTC step can
wreak havoc on your state machine.
Memory Management 225
Exercise 8.1 Add state-based exception handling to the Reminder state pattern imple-
mentation described in Section 5.2 in Chapter 5. Introduce a state
“fault” and an EXCEPTION event that triggers a transition from the
polling to the fault state. Subsequently, invoke the following
faulty() function in the entry action of the busy state.
void faulty() {
static int faultCtr = 10;
if (--faultCtr == 0) {
faultCtr = 10;
throw "fault";
}
}
• Dynamically allocating and freeing memory can fragment the heap over time to
the point that the program crashes because of an inability to allocate more RAM.
The total remaining heap storage might be more than adequate, but no single
piece satisfies a specific malloc() request.
• Heap-based memory management is wasteful. All heap management algorithms
must maintain some form of header information for each block allocated. At the
very least, this information includes the size of the block. For example, if the
header causes a four-byte overhead, then a four-byte allocation requires at least
eight bytes, so only 50 percent of the allocated memory is usable to the applica-
tion. Because of these overheads and the aforementioned fragmentation, deter-
mining the minimum size of the heap is difficult. Even if you were to know the
worst-case mix of objects simultaneously allocated on the heap (which you typi-
cally don’t), the required heap storage is much more than a simple sum of the
object sizes. As a result, the only practical way to make the heap more reliable is
to massively oversize it.
• Both malloc() and free() can be (and often are) nondeterministic, meaning
that they potentially can take a long (hard to quantify) time to execute, which
conflicts squarely with real-time constraints. Although many real-time operating
systems (RTOSs) have heap management algorithms with bounded, or even deter-
ministic performance, they don’t necessarily handle multiple small allocations effi-
ciently.
Unfortunately, the list of heap problems doesn’t stop there. A new class of prob-
lems appears when you use heap in a multithreaded environment. The heap becomes
a shared resource and consequently causes all the headaches associated with resource
sharing, so the list goes on.
• Both malloc() and free() can be (and often are) non-reentrant; that is, they
cannot be safely called simultaneously from multiple threads of execution. 12
• The reentrancy problem can be remedied by protecting malloc(), free(),
realloc(), and so on internally with a mutex semaphore,13 which lets only one
thread at a time access the shared heap (Section 8.4.1). However, this scheme
could cause excessive blocking of threads (especially if memory management is
nondeterministic) and can significantly reduce parallelism. Mutexes are also sub-
ject to priority inversion (see the sidebar “Priority Inversion, Inheritance, and
12. You need to consult the run-time library accompanying your compiler to link to the right version of reentrant
heap management routines. Be aware, however, that the reentrant versions are significantly more expensive to
call.
13. For example, the VxWorks RTOS from WindRiver Systems protects heap management routines with a mutex
[WindRiver 97].
Memory Management 227
14. Why don’t you use this list when interviewing for an embedded systems programmer position? Awareness of
heap problems is like a litmus test for a good embedded systems programmer.
15. For example, C++ exception-handling mechanism can cause memory leaks when a thrown exception
“bypasses” memory deallocation.
228 Chapter 8: Design of the Quantum Framework
16. That’s why it’s a good idea to reboot your PC every once in a while.
17. It does not necessarily mean that you cannot use any dynamic data structures. For example, the QF uses inter-
nally linked lists, but there is no requirement that they be allocated on the heap.
Memory Management 229
I’ll defer discussion of the first argument (prio) to Section 8.6.3 and concentrate
here on the other arguments. They have the following semantics: qSto is a pointer to
the storage for the event queue (Section 8.6.2 discusses event queues), qLen is the
length of the queue (in units of QEvent*), stkSto is a pointer to the storage location
for the stack,18 and stkLen is the length of the stack (in units of int). With this sig-
nature, you have the complete freedom to allocate both the event queue and the
stack in whichever way you like.
This flexibility could have important performance implications because, often in
embedded systems, some RAM is better than other RAM. For example, the AT91
18. You typically need to allocate a separate stack for each thread of execution.
230 Chapter 8: Design of the Quantum Framework
The first two methods are relatively unsophisticated and have serious drawbacks.
Disabling interrupts completely cuts the CPU off from the outside world and can
increase interrupt latency if the access requires more than a handful of machine
instructions. Disabling task switching is less radical, but it prevents indiscriminately
the execution of all other threads, including high-priority threads totally unrelated to
the resource. A more refined approach is to use a semaphore, because this method
affects only the threads actually competing for the resource. Indeed, almost all multi-
tasking operating systems provide an assortment of semaphores specialized for vari-
ous functions. A semaphore optimized specifically for problems inherent in mutual
exclusion is called a mutual exclusion semaphore, or simply a mutex.
Among all the mutual exclusion mechanisms, the (mutex) semaphore is the most
universal. This mechanism is applied quite often inside RTOSs and in various reen-
trant (thread-safe) libraries, so your application could be using a mutex, even though
you do not employ this mechanism directly. For example, the heap management rou-
tines discussed earlier, such as malloc(), free(), realloc(), and so on, are com-
monly protected by a mutex, so that multiple threads can access the (shared) heap
resource concurrently. However, in spite of the ubiquity (or perhaps just because of
it), mutexes are the most dangerous of all mechanisms (see the sidebar “Priority
Inversion, Inheritance, and Ceiling”).
Priority inversion can be a show stopper in any real-time system; yet, most less
sophisticated RTOSs and real-time kernels don’t support any mechanisms to prevent
it. Even the high-end systems, which have priority inheritance or priority ceiling built
in, don’t enable these mechanisms by default because of the high overhead involved.
Many operating systems recognize the problem and provide workarounds, which
rely on augmenting the mutex implementation to prevent intermediate priority threads
from running when low- and high-priority threads are competing for access to the
shared resource. A mutex can promote a lower priority thread to the higher priority
on a temporary basis while it’s owned by the lower priority thread using one of two
methods: priority inheritance and priority ceiling [Kalinsky 98].
The priority inheritance mutex assures that a thread that owns a resource executes
at the priority of the highest priority thread blocked on that resource. Figure 8.1b
shows an example of the previous scenario, except this time, as soon as the high-prior-
ity thread C tries to access the heap (at time 10), the mutex elevates the priority of A,
which holds the resource, to the level of C. Thread C blocks as before, but A tempo-
rarily inherits its priority and is no longer vulnerable for preemption by B, so A can
complete malloc() and release the mutex at time 12. As soon as this happens, the
mutex lowers the priority of A to its original level. Now C can preempt A, acquire the
mutex, and continue. This time, C easily meets its deadline.
The priority ceiling mutex also temporarily raises the priority of the thread holding
a shared resource, but it uses a fixed priority level (the ceiling priority) that is assigned
to the mutex on its creation. The ceiling priority should be at least as high as the high-
est priority thread working with this mutex. Figure 8.1c shows the scenario. As soon
as A calls malloc() at time 4, its priority elevates to that of C, so now C cannot pre-
empt A as it receives an event at time 5. Instead, C has to wait until A releases the
mutex at time 7 and its priority drops down to the original level. At this point, C runs
to completion (and easily meets its deadline), B runs to completion, and finally A runs
to completion. Choosing a ceiling priority that is too high will effectively lock the
scheduler for other (unrelated) threads. Choosing one that is too low will not protect
against some priority inversions. Nevertheless, priority ceiling has some advantages
over priority inheritance. It is faster, tends to cause fewer context switches, and is
much easier for static timing analysis.
The concepts of priority inheritance and priority ceiling can be combined into a
complex, but completely bulletproof (immune even to deadlocks), system-wide solu-
tion known as the priority ceiling protocol (refer to Sha and colleagues [Sha+ 90] for
more information).
Figure 8.1 Timing diagrams for priority inversion (a), priority inheritance (b), and
priority ceiling (c)
(a)
deadline
running
event (missed)
C ready done
blocked
running
A ready
malloc()
blocked done
0 5 10 15 20 time
(b)
running
deadline
C ready event done
blocked
malloc() done
running
malloc() done
B ready
blocked event
running
A ready
malloc() done
blocked promotion demotion
0 5 10 15 20 time
(c)
running
deadline
C ready event done
blocked
running
malloc() done done
B ready
blocked event
running demotion
A ready
blocked malloc() (promotion) done
0 5 10 15 20 time
More importantly, all mutual exclusion mechanisms lead to coupling among dif-
ferent threads in the time domain. Anytime a high-priority thread, C, shares a
resource with a low-priority thread, A, the high-priority thread must take into
account delays caused by blocking when A exclusively accesses the shared resource.
If A locks the resource for too long, then C might be delayed (and miss its deadline,
for example), even if the priority inversion is correctly avoided. Thus the mutual
exclusion mechanism couples otherwise independent threads A and C, because any
changes to A could have adverse effects on C.
As you will see throughout the rest of this book, the QF consistently helps you to
avoid any form of mutual exclusion by handling all the burdens of mutual exclusion
234 Chapter 8: Design of the Quantum Framework
internally. In all cases when the QF needs to obtain mutually exclusive access (e.g., to
pass events shared among active objects), it uses the simplest safe method (i.e., briefly
disabling interrupts), which is not subject to priority inversions.
The only hazard is that while the active object is blocked, its event queue can over-
flow. However, this indicates a sustained mismatch between the volume of applica-
tion output (production rate) and available bandwidth (consumption rate), which no
queue can handle. You should manage this either by reducing the output or by
increasing the throughput (perhaps by going to a higher baud rate).
Blocking by mutual exclusion in an active object–based system is the software
equivalent of friction in a mechanical system. A well-designed, multitasking system is
like a well-lubricated engine that can run smoothly and efficiently without much
wear and tear — forever. Excessive blocking in a software system, on the other hand,
is like excessive friction in a machine. It can destroy even the best quality parts, and
it eventually brings the whole machine to a grinding halt. You don’t want this to
happen to your software system — lubricate your software machines!
19. See the sidebar “Particle Interaction in Quantum Field Theory” in Chapter 7.
20. This is not really a physical question, but the problem of ownership is interesting from the programming per-
spective. In programming, the owner has the responsibility of destroying the object. As in real life, ownership is
transferable.
236 Chapter 8: Design of the Quantum Framework
intercept it and explicitly send it to somebody else. Neither of the participants can
keep the virtual photon around for any significant time. Rather, the photon is just
loaned briefly for the duration of the interaction and then it must disappear into the
quantum vacuum.21
To translate this scenario into programming lingo, you need to substitute the
names of the main actors. You translate “electron” to “active object”, and “photon”
to “event.” Other concepts translate roughly as follows: “virtual” corresponds to
“dynamic,” “violation of energy conservation” to “memory leak,” and “quantum
vacuum” to “QF.” After translation, the quantum metaphor proposes a mechanism
of passing events that requires dynamic creation and automatic destruction of events.
Active objects do not own events. They receive events as loans that are valid only for
the duration of a single atomic RTC step (a quantum leap). In particular, active
objects cannot intercept and retransmit received events. Active objects also have to
eventually send out any events that they create, because even these events ultimately
don’t belong to them. The responsibility for delivering and automatically destroying
events rests exclusively with the QF, leading in effect to a specific automatic garbage
collection.
Because this model comes from a real-life metaphor, it has a good chance of
being coherent. After all, virtual particle exchange has been working flawlessly
ever since the Big Bang.22 To convince you, I will pose a few probing questions. Why
must the destruction of events be automatic? Well, if the client code (rather than the
framework) were responsible for explicit event destruction, then every event would
have to be handled (and eventually explicitly destroyed) by some active object; other-
wise, the event would leak. But this contradicts the idea of loose coupling, in which
the producer of the event doesn’t know who will consume the event (and if at all).
Why can’t active objects intercept events and keep them around for future ref-
erence? After all, perhaps event parameters contain some information that is
useful for longer than one RTC step. If you allowed active objects to intercept
events (thus acquiring ownership of events), the framework would have to be
notified somehow to spare these events from destruction. In the quantum meta-
phor, this would correspond to an energy-intensive process of converting virtual
particles into real ones, which would lead to a convoluted implementation that
nature doesn’t favor.
The QF event-passing mechanism that emerges from this analysis involves
dynamic event allocation, subscribing to events, publishing events, event multicast-
21. If you still need to know who owns that darn photon, then you can think of it as belonging to the quantum vac-
uum.
22. Okay, the Big Bang itself might be exactly the one (pretty big though) violation of the uncertainty principle.
Passing Events 237
ing (in case of multiple subscriptions), and automatic event recycling. In the follow-
ing sections, I cover all of these features in turn.
The most obvious drawback of a fixed-block-size heap is that it does not support
variable-sized blocks. Consequently, the blocks have to be oversized to handle the
biggest possible allocation. Such a policy is often too wasteful if the actual sizes of
allocated objects (events in this case) vary a lot. A good compromise is often to use
not one, but a few heaps with blocks of different sizes — for example, small,
medium, and large.
For the sake of the following discussion, the term “event pool” stands for a fixed-
block-size heap customized specifically to hold events. Assume that a quantum event
pool class QEPool encapsulates a fixed-block-size heap and provides the myEvtSize
attribute for accessing the size of the events managed by the pool. (In Chapter 9, you
will find concrete implementations of the QEPool class.) Further assume that the
238 Chapter 8: Design of the Quantum Framework
class provides three methods: init(), get(), and put() for pool initialization,
event allocation, and event recycling, respectively.
from the small-block pool and an event of 135 bytes from the big-block pool (Figure
8.2).
although straightforward, could easily triple the size of the method. This is just one
example of how contracts can lead to simpler code.
The QF::create() method (Listing 8.2, lines 19–35) is a straightforward imple-
mentation of the event allocation policy discussed earlier. The method scans through
locPoolPtr[] starting from pool ID = 1, and as soon as it finds a pool that can
accommodate the requested size (line 23), it obtains a memory block from this pool
(line 25). Subsequently, QF::create() asserts that the pool has not run out of
blocks (line 26). This is an example of a strategic contract. Alternative approaches
could be to return an error code, to let clients decide whether this is an error or
exceptional condition, or to block on the pool and wait until other active objects
release some events (Section 8.4 argues why blocking is a bad idea). The contract in
line 26 is an important design decision; it treats running out of pool space as an error
not less severe than, for example, running out of stack space.
Typically, you will not use QF::create() directly, but through the Q_NEW()
macro.
#define Q_NEW(evtT_, sig_) ((evtT_ *)QF::create(sizeof(evtT_), (sig_)))
This macro dynamically creates a new event of type evT_ (all event types in the QF
are subclasses of QEvent) with the signal sig_. It returns a pointer to the event
already downcast to the event type evT_*. The contract in line 26 of Listing 8.2
guarantees that the pointer is valid, so you don’t need to check the pointer (unlike
the value returned from malloc(), which you should always check).
23. For example, the Java 1.1 Event Model is a publish–subscribe architecture for delivering events from event
sources (subjects) to action listeners (observers).
Passing Events 241
24. The current implementation of the QF designates four bits per active object in the 32-bit QSubscrList, but
you can easily change both the size of QSubscrList and the number of bits per active object.
25. Real-time constraints practically exclude systems that do not support thread priorities. However, the QF can be
ported to almost any operating system, not necessarily real-time systems (e.g., Windows), in which case you just
make up a unique priority for each active object.
242 Chapter 8: Design of the Quantum Framework
Figure 8.4 Inserting a new priority into the subscriber list at bit n (see
QF::subscribe(), Listing 8.3)
p=2
QSubscrList bit n+4
bit n
bit 31 bit 0
p=2
For subsequent efficient event delivery, the method needs to keep the subscriber
list sorted in descending order of priorities (i.e., higher priorities at lower bit num-
bers). This sorting is achieved when the for loop scans for the right slot in which to
26. I’m concerned at this point only with the integrity of the subscriber list and not whether the critical section
extends the interrupt latency. I assume that subscriptions happen at the startup transient, when interrupt latency
does not matter yet.
Passing Events 243
insert the new priority, as well as in some creative bit shifting and bit masking in
lines 10 through 12 (see also Figure 8.4).
Publishing events through QF::publish() (Listing 8.4), on the other hand, takes
an event instance as the sole parameter. This method is designed to be callable from
both a task and an interrupt. In the precondition (lines 2, 3), QF::publish() checks
whether the signal associated with the event is in range and whether the event is not
already in use. “In use” here means that the event instance has been published but
the framework hasn’t recycled it yet. You cannot publish such an event instance
because it would conflict with the event-passing mechanism of the QF discussed ear-
lier. This mechanism prohibits, among other things, intercepting events and publish-
ing them again. To remember whether it is in use, every event stores the number of
uses in the useNum attribute, which must be 0 for an unused event (Sections 8.5.3
and 8.5.4).
27. The concrete way of accessing an event queue depends on the platform-dependent implementation.
Passing Events 245
28. Strictly speaking, the contract in line 9 of Listing 8.4 only covers delivery to the highest priority subscriber.
However, event delivery to other subscribers is also consistently associated with a similar contract
(QF::propagate(), Listing 8.5).
29. Chapter 10 discusses the issues associated with sizing event queues.
246 Chapter 8: Design of the Quantum Framework
priority-based schedule (see Section 8.4), this more economical approach is indistin-
guishable from true multicasting.
A preemptive, priority-based scheduler allocates the CPU to the highest priority
thread ready to run. Suppose that the QF truly multicasts several copies of an event
to several active objects simultaneously. However, in a single-CPU system, only one
thread can execute at a time — the highest priority thread ready to run. After the
multicast, it is the highest priority subscriber to the event. It runs to completion and
relinquishes the CPU to the next highest priority subscriber, which again runs to
completion, and so on. This sequence continues until finally the lowest priority
active object on the subscriber list gets a chance to process the event. As you can see,
the scheduling algorithm automatically arranges sequential processing, in which only
one copy of the event is in use at any given time.
The QF arranges for an identical sequence of processing, as a preemptive, prior-
ity-based scheduler would. As you recall from Section 8.5.2, subscriber lists are
ordered in the descending order of subscriber priority. The first entry on the list (at
the lowest bit number) corresponds to the highest priority subscriber and is the first
active object that gets the event in the QF::publish() method. Subsequently,
QActive::run() (Listing 8.8 later in this chapter) propagates the event to the next
subscriber on the list by invoking QF::propagate().
events fall into this category, see Section 8.8). Such events should be annihilated (line
14).
In line 3, the routine asserts that the signal of the event is in range. In line 4, the
routine obtains the subscriber list through the same table lookup as QF::publish().
In line 5, the subscriber list is right-shifted to put the current subscriber into the least
significant bit position. In addition, this shift automatically gets rid of the subscribers
serviced so far (they “fall off” the least significant bit). If the subscriber list is still not
empty (line 6), then QF::propagate() extracts the current subscriber priority in line
7 and increments the event use number, e->useNum, in line 8 to indicate the next use
of the event. Lines 9 and 10 are identical to lines 8 and 9 of QF::publish() (line 10
corresponds to the guaranteed event delivery strategic contract).
The sequential multicast mechanism implemented in QF::publish() and
QF::propagate() closely approximates a “true” simultaneous multicast only under
the following conditions. First, all subscribers must receive the same, unaltered event
instance. To this end, the signature of the state handler (Chapter 4) declares the event
immutable (QEvent const *e), which should prevent alterations to the event.30 Sec-
ond, the sequence of processing exactly follows the priorities of the subscribers only
if active objects do not block in the middle of RTC processing. Such blocking (gener-
ally a bad idea, see Section 8.4) could unpredictably change the processing sequence.
Finally, the sequence will be correct only under a preemptive, priority-based sched-
uler. The QF specifically addresses only this case because no other scheduling algo-
rithm gives a better task-level response; consequently, the preemptive, priority-based
scheduler is the most common choice for hard real-time systems. Other scheduling
algorithms that could be advantageous in “soft” real-time systems might result in
differences between true and sequential event multicasting; then again, these differ-
ences should be tolerable for meeting only soft deadlines.
30. Alterations to a received event instance would be like scribbling in a book borrowed from a library.
31. Such a situation can arise easily in the early stages of modeling, when the application is simply incomplete.
248 Chapter 8: Design of the Quantum Framework
subscriber list (Listing 8.4, line 12). The other is QF::propagate(), which recycles
an event after the last subscriber on the list has processed it (Listing 8.5, line 14).
32. For systems with very constrained resources, you might consider deriving QActive from the simpler, nonhier-
archical state machine QFsm (Chapter 3) rather than from QHsm.
Active Objects 249
Concrete
Philosopher Table
active objects
Figure 8.6 Event queue of an active object holding pointers to event instances
«active» «active»
ProducerB Consumer
ISR Internal
statechart of
the Consumer
Pointers to active object
event instances
:Event
:Event
Pool event
Pool
instances
From the description so far, it should be clear that the event queue is quite a
sophisticated mechanism. One end of the queue — the end where producers insert
events — is obviously shared among many threads and must provide an adequate
mutual exclusion mechanism to protect the internal consistency of the queue. The
other end — the end from which the local thread extracts events — must provide a
mechanism for blocking this thread when the queue is empty. In addition, an event
queue must manage a buffer of events, typically organized in a FIFO structure.
As shown in Figure 8.6, the QF event queues do not store actual events, only
pointers to event instances. Typically, these pointers point to event instances allo-
cated dynamically from event pools (Section 8.5.1), but they can also point to stati-
cally allocated events. You need to specify the maximum number of event pointers
that a queue can hold at any one time (the capacity of the queue) in
QActive::start() (see the following section). The correct sizing of event queues
depends on many factors and generally is not a trivial task. Chapter 10 discusses siz-
ing event queues.
Many commercial RTOSs natively support a queuing mechanism in the form of
message queues (sometimes called mailboxes or message mailboxes). A message queue
typically maps well to the event queue described here. Standard message queues are
far more flexible than required by active objects because they typically support vari-
able-length data (not only pointer-sized data) and allow multiple-write as well as mul-
tiple-read access (the QF requires only single-read access). Usually, message queues
also allow blocking when the queue is empty and when the queue is full, and both
types of blocking can be timed out. (Naturally, all this extra functionality, which you
don’t really need, comes at an extra cost in CPU and memory usage.) Before you
accept the queuing mechanism provided in your RTOS, check how the RTOS imple-
Active Objects 251
ments mutual exclusion in the queue. The right way is to treat the message queue as
the first-class kernel object (like a semaphore for example) and to implement mutual
exclusion by briefly disabling interrupts. The problematic way is to protect the mes-
sage queue with a mutex, because this might lead to the priority inversion problem
discussed in Section 8.4.1.
In Chapter 9, you will find an example of an event queue built from a message
queue of an RTOS, as well as an example of an event queue implemented from
scratch.
34. In some implementations of the QF, active objects can share a common thread of execution (I’ll show an exam-
ple in Chapter 9). However, the reference design presented here assumes a separate thread for every active
object.
35. Chapter 9 shows concrete implementations for specific platforms.
252 Chapter 8: Design of the Quantum Framework
The comments in the signature of the thread routine are supposed to denote a
platform-specific calling convention (e.g., __cdecl, __near, __far, etc.), return
type, and other arguments potentially required by the underlying multitasking ker-
nel. Note that the thread routine is not a method of the QActive class because, typi-
cally, only a free function can serve as a thread’s entry point. 38 On the other hand,
most RTOSs allow you to pass at least one generic pointer to the thread routine,
which QActive::start() uses to pass the active object.
Thread processing happens in QActive::run() (Listing 8.8). This method starts
by executing the initial transition (line 2) of the state machine associated with the
active object (see the QHsm::init() method in Chapter 4, page 107). This transi-
tion is an appropriate place to initialize extended state variables, subscribe to events,
initialize the hardware managed by the active object, and so on. Subsequently, the
thread routine enters a typical endless loop (line 3) of waiting for the event to arrive
through the event queue (line 4), before dispatching it for RTC processing through
the dispatch() method inherited from QHsm (line 5) and propagating it to other
subscribers (line 6). Propagating events is part of multicasting and automatic event
recycling, which are discussed separately in Section 8.5.3 and 8.5.4, respectively.
1 void QActive::run() {
2 QHsm::init(); // execute initial transition
3 for (;;) { // for-ever
4 QEvent *e = myEqueue->get(); // get event; block if queue empty
36. You can think of priority 0 as corresponding to the idle task, which has the absolute lowest priority not accessi-
ble to user threads.
37. For example, the VxWorks kernel Wind uses a numbering scheme in which priority 0 corresponds to the high-
est and 255 to the lowest priority tasks [WindRiver 97].
38. In the C version of the QF, you can use the “C+” class “method” directly because it is already a free function.
Initialization and Cleanup 253
Please note the static allocation of storage for the subscriber list (subscrSto) and
event pool (evtPoolSto) and the subsequent passing of this memory over to the
framework. The QF::init() method is defined as follows.
void QF::init(QSubscrList subscr[], unsigned maxSignal) {
locSubscrList = subscr; // point to the user-provided storage
locMaxSignal = maxSignal; // remember look-up table boundary
osInit(); // call OS-dependent QF initialization
}
Although correctly sizing the subscriber list lookup table is straightforward (it
must accommodate all signals in the system), QF::poolInit() (covered in Section
8.5.1) requires a more tricky decision — proper sizing event pools. In general, the
capacity of event pools is related to how many event instances you can “sink” in
your system, which is discussed in more detail in Chapter 10.
have it stop voluntarily either by returning from its thread routine, or by explicitly
calling QActive::stop().39 Of course to “commit suicide,” in that way, the active
object must not block (on its own event queue, for example). In addition, before dis-
appearing, the thread should release all the resources acquired during its lifetime.
Unfortunately, making a thread stop voluntarily cannot be preprogrammed generi-
cally at the framework level and always requires some work on the application pro-
grammer’s part.
Because the active object’s thread routine is organized into an infinite loop (List-
ing 8.8), the preferred way to end a thread is to call QActive::stop() from within
the thread. Perhaps the best place to invoke this method is the entry action of the
explicit final state (see Section 5.1.3 in Chapter 5).
You can use QActive::stop() to terminate one active object at a time. Com-
plete shutdown of the whole application, however, requires waiting until the last
active object voluntarily stops; only then is the shutdown process complete. This is
perhaps the most difficult aspect of the process (how would you know which object
is the last?). The shutdown of an application is very important for desktop applica-
tions, to be sure, but much less so in embedded systems. The embedded programs
almost never have a need to shutdown gracefully because the job of the typical
embedded application is never finished. Most embedded applications have to con-
tinue as long as the power is on. Therefore, you can simply ignore the graceful shut-
down process in most cases.
39. The concrete implementation of QActive::stop() strongly depends on the underlying operating system.
40. Timed blocking on a semaphore might be applicable when blocking an active object thread not related to other
active objects (Section 8.4.2).
256 Chapter 8: Design of the Quantum Framework
1 void QF::tick() {
2 register QTimer *t, *pt;
3 QF_ISR_PROTECT();
4 for (t = pt = locTimerListHead; t; t = t->myNext) {
5 if (--t->myCtr == 0) {
6 // queue cannot overflow
7 ALLEGE(t->myActive->enqueue(&t->myToutEvt));
8 if (t->myInterval) { // periodic timer?
9 t->myCtr = t-> myInterval; // rearm the timer
10 pt = t;
11 }
12 else { // one-shot timer, disarm by removing from the list
13 if (t == locTimerListHead) {
14 locTimerListHead = pt = t->myNext;
15 }
16 else {
17 pt->myNext = t->myNext;
18 }
19 t->myActive = 0; // mark the timer free to use
20 }
21 }
22 else {
23 pt = t;
24 }
25 }
26 QF_ISR_UNPROTECT();
27 }
At every clock tick, QF::tick() scans the linked list of timers (Listing 8.9, line 4)
and decrements the myCtr down-counter of each timer in the list. When the counter
drops to 0, the routine inserts the requested event into the recipient’s event queue
(line 7). As usual, this operation is associated with the guaranteed event delivery con-
tract. For a periodic timer (with a non-0 myInterval), the routine rearms the myCtr
down-counter to the interval value myInterval (line 9). Otherwise, it is a one-shot
timer and must be disarmed (lines 12–20). Disarming the timer corresponds to
removing it from the linked list. However, because it is only a unidirectional (single-
linked) list, the additional pt (previous timer) pointer is necessary.
258 Chapter 8: Design of the Quantum Framework
The QF::tick() method is primarily designed for invocation from the interrupt
context. For most embedded platforms, the macros QF_ISR_PROTECT() and
QF_ISR_UNPROTECT() (Listing 8.9, lines 3 and 26) can translate to nothing because
an ISR is always safe from preemptions by a task. For some platforms (e.g., Win-
dows), however, you don’t have easy access to the clock tick interrupt and you are
forced to invoke QF::tick() from a task context (Chapter 9). A critical section is
necessary in this case.
8.9.1 QF Interface
The QF class encapsulates all top-level QF services. The class is unusual (Listing 8.10)
because it has only static member functions and no data members. Such a class has
only one instance and is equivalent to a module.43 The use of a C++ class here is only
to group related functions to avoid pollution of the global namespace. A specific QF
namespace is not used because some older and embedded C++ (EC++) compilers
don’t support namespaces [EC++ 01]. Table 8.1 summarizes the public interface of
the QF class.
1 class QF {
2 public:
3 static char const *getVersion();
4 static void init(QSubscrList subscr[], unsigned maxSignal);
5 static void poolInit(void *poolSto,
6 unsigned nEvts, unsigned evtSize);
7 static void tick();
8 static QEvent *create(unsigned evtSize, QSignal sig);
9 #define Q_NEW(evtT_, sig_) \
10 ((evtT_ *)QF::create(sizeof(evtT_), (sig_)))
11 static void subscribe(QActive *a, QSignal sig);
12 static void unsubscribe(QActive *a, QSignal sig);
13 static void publish(QEvent *e);
42. The other interesting class QEvent has no methods and so it’s not described here.
43. The QF class specifically does not use the Singleton design pattern because it has no need for extensibility by sub-
classing and the additional cost of accessing the Singleton via the instance() method is not justified.
QF API Quick Reference 259
Method Description
getVersion() Return pointer to an immutable version string.
Initialize the framework (requires memory for the subscriber
init()
list lookup table). Must be invoked only once.
Initialize the event pool (requires a memory buffer for the
poolInit() pool). Can be invoked several times to initialize event pools
of different sizes.
Process one clock tick (should be called periodically from the
tick()
clock tick ISR).
Dynamically create an event instance of the specified size.
Running out of free events in an event pool causes a contract
create()
violation. This method should be used through the Q_NEW()
macro, not directly.
Subscribe to a specified signal (should be called by an active
subscribe() object). Multiple active objects can subscribe to the same sig-
nal.
Unsubscribe a specified signal (should be called by the sub-
unsubscribe() scriber active object). Unsubscribing a signal that is not sub-
scribed by the given active object causes a contract violation.
publish() Publish an event (can be called from a task or interrupt).
Perform background processing (defined only for
background()
foreground/background systems, see Chapter 9).
Perform a platform-dependent cleanup (should be called only
cleanup()
on application exit).
260 Chapter 8: Design of the Quantum Framework
Method Description
The QActive constructor, which like the QHsm constructor, takes
the initial pseudostate handler as the argument. The constructor
QActive()
is protected to prevent direct instantiation of the QActive class
(the class is abstract, i.e., intended only for inheritance).
Explicitly start an active object thread of execution. The caller
needs to assign a unique priority to every active object in the sys-
tem (assigning a priority that has already been used causes a con-
tract violation). The priority must conform to the numbering
scheme of QF and not to the priority numbering scheme of the
underlying operating system. The lowest priority accessible to
start() active objects is 1; higher priorities correspond to higher urgency.
The caller also needs to commit memory for the event queue and
for the execution stack. Some platforms allocate this memory
internally, in which case the concrete implementation of the
method could require passing NULL pointers for event queue stor-
age, execution stack storage, or both. The start() method trig-
gers the initial transition in the active object’s HSM.
Post an event directly to the active object’s event queue using the
FIFO policy. The primary intention of this method is to enable
postFIFO() the Reminder state pattern (Chapter 5, Section 5.2) and to send
notifications from aggregated components to the active object’s
state machine (Chapter 5, Section 5.4).
Post an event directly to the active object’s event queue using the
LIFO policy. The primary intention of this method is to enable
postLIFO() the Reminder state pattern (Chapter 5, Section 5.2) and to send
notifications from aggregated components to the active object’s
state machine (Chapter 5, Section 5.4).
Stop the execution thread associated with the active object and
unsubscribe all signals. The stop() method should be invoked
stop()
from the context of the terminating active object. Caution: con-
trol never returns to the calling thread in this case.
262 Chapter 8: Design of the Quantum Framework
1 class QTimer {
2 public:
3 QTimer() : myActive(0) {} // default ctor
4 void fireIn(QActive *act, QSignal sig, unsigned nTicks);
5 void fireEvery(QActive *act, QSignal sig, unsigned nTicks);
6 void disarm();
7 void rearm(unsigned nTicks);
8 private:
9 void arm(QActive *act, QSignal sig, unsigned nTicks);
10 private:
11 QTimer *myNext; // to link timers in the list
12 QEvent myToutEvt; // timeout event instance to send
13 QActive *myActive; // active object to send the timeout event to
14 unsigned short myCtr; // running clock-tick downcounter
15 unsigned short myInterval; // interval for periodic-timer
16 friend class QF;
17 };
Method Description
Arm a timer for a single shot in nTicks number of clock
ticks. After timing out, the timer is automatically dis-
fireIn() armed and can be reused. Arming a timer that has already
been armed causes a contract violation (precondition fail-
ure).
Arm a timer to fire periodically every nTicks number of clock
ticks. A periodic timer is automatically rearmed by every time-
fireEvery()
out. Arming a timer that has already been armed causes a con-
tract violation (precondition failure).
Summary 263
Method Description
Disarm an armed timer (one-shot or periodic) so that it
doesn’t fire a timeout event. The caller must not assume
that no more timeouts arrive after the call because some
disarm()
timeouts could still be in the event queue. Disarming an
unarmed timer causes a contract violation (precondition
failure).
Rearm an armed timer to fire in nTicks number of clock
ticks. The method affects only the next shot and does not
change the interval of a periodic timer; that is, rearming a
periodic timer changes its phasing but not its period. The
rearm()
number of nTicks is arbitrary (but positive) and could
exceed the interval (so the method allows both extending
and shrinking the next tick). Rearming an unarmed timer
causes a contract violation (precondition failure).
8.10 Summary
The design of the Quantum Framework (QF) addresses the particular needs of
embedded real-time systems dedicated to a specific function that requires timely
responses to external events.
Embedded real-time systems necessitate different programming strategies than
general-purpose computers such as desktop PCs. Many established programming
techniques and rules of thumb from the desktop are not only inadequate but harmful
to the majority of embedded real-time applications. The QF carefully avoids tech-
niques that can be problematic in embedded systems and uses policies that allow you
to take advantage of the specific nature of embedded systems programming.
The QF policy for error and exception handling hinges on the observation that,
compared to the desktop, the specifics of embedded systems allow you to flag many
more situations as errors (which need to be found and fixed, but not handled) than
as exceptional conditions (which require handling). To facilitate finding and fixing
errors (bugs), the QF consistently applies DBC. To handle exceptions, the QF pro-
poses using state machine–based exception handling instead of the programming
language–based technique of throwing and catching exceptions.
The basic design philosophy of the QF with respect to memory management is
not to commit any memory internally but leave to the clients the instantiation of any
framework-derived objects and the initialization of the framework with memory that
it needs for operation. This policy allows you to completely avoid the heap (free
store), which often causes problems in embedded real-time systems.
264 Chapter 8: Design of the Quantum Framework
Implementations of the
Quantum Framework
There is no trick in building large systems quickly; the
quicker you build them, the larger they get!
— David Parnas
265
266 Chapter 9: Implementations of the Quantum Framework
1. RTKernel-32 is a product of On Time Software. A fully functional evaluation version of this product, as well as
documentation, is available on the accompanying CD-ROM.
2. Throughout this book, I assume that you use Microsoft Visual C++ v6.0. Porting the QF to DOS requires a dif-
ferent (16-bit) compiler. However, because of the tremendous popularity of DOS, I hope that you can find an
older C/C++ compiler for DOS relatively easily.
3. A good rule of thumb is that reusable components will take twice the effort of a one-shot component [Yourdon
92].
Code Organization 267
In Chapter 8, I constructed the root of the QF design tree by extracting the com-
monalities in the QF design. In this chapter, I concentrate on the branches and leaves
of the tree. These elements are formed by the concrete QF ports, which all make a
small Parnas family of the QF.
4. The µC/OS real-time kernel [Labrosse 92a, 92, 99] is an example of a system that internally preallocates mem-
ory for kernel objects.
268 Chapter 9: Implementations of the Quantum Framework
not need to know or care that such a data type even exists; that is, the applications
wouldn’t care except that they need to instantiate concrete active objects that contain
thread instances inherited from QActive. The application code becomes dependent
on the details of the framework base classes because, ultimately, the application has
to allocate the memory (preferably statically) for all objects. To do so, the C/C++
compiler needs to know the size of each object [Stroustrup 91]. In comparison, sys-
tems that preallocate internal objects can better hide platform dependencies and can
expose only the platform-independent interface in the header files.
6. The division of the interface into package scope (wide) and public scope (narrow) reflects high cohesion within
the package and loose coupling to the outside world.
7. Most C/C++ compilers allow you to specify multiple include directories (typically through the -I option).
8. Figure 9.1 also demonstrates how you can replicate the same directory structure for multiplatform QF applica-
tions (e.g., see the structure of the QDPP directory).
9. An OS event object is a platform-dependent attribute of the event queue class (Section 9.3.3) that blocks the
active object thread when the queue is empty.
270 Chapter 9: Implementations of the Quantum Framework
QF (Quantum Framework)
public scope
header files Include
Qassert.h Qf.h Qf_dos.h platform-
Qactive.h Qfsm.h Qf_rtk32.h dependent
Qepool.h Qhsm.h Qf_win32.h public scope
Qequeue.h Qtimer.h ... header files
Qevt.h for QF ports
platform-
independent
Source
source files
Qactive.cpp Qf.cpp Qtimer.cpp
platform-
Qepool.cpp Qfsm.cpp Qfpkg.h
independent
Qequeue.cpp Qhsm.cpp
pakcage scope
DOS (port) header file
dos.cpp qf_dos.lib port.h
platform- platform-
dependent Win32 (port) dependent
source fles package scope
win32.cpp port.h header files
other Relase
QF ports
qf_win32.lib
through 19 include various elements of the QF. Note that not all ports require you to
include all elements. For example, the RTKernel-32 port (discussed in Section 9.6)
does not need either an event queue (qequeue.h) or an event pool (qepool.h)
because these elements are based on the native implementation of the underlying
RTOS (RTKernel-32).
Code Organization 271
Listing 9.2 Public scope header file qf_win32.h for Win32 port
1 #ifndef qf_win32_h
2 #define qf_win32_h
3
4 #include <windows.h>
5
6 // Win32-specific event object, event-queue and thread types...
7 #define QF_OS_EVENT(x_) HANDLE x_;
8 #define QF_EQUEUE(x_) QEQueue x_;
9 #define QF_THREAD(x_) HANDLE x_;
10 #define Q_STATIC_CAST(type_, expr_) static_cast<type_>(expr_)
11
12 // include framework elements...
13 #include "qevt.h"
14 #include "qhsm.h"
15 #include "qequeue.h" // Win32 needs event-queue
16 #include "qepool.h" // Win32 needs event-pool
17 #include "qactive.h"
18 #include "qtimer.h"
19 #include "qf.h"
20
21 #endif // qf_win32_h
Listing 9.3 Package scope header file Cpp/Win32/port.h for the Win32 port
1 #ifndef port_h
2 #define port_h
3
4 #include "qf_win32.h"
5 #include "qfpkg.h"
6
7 // Win32-specific critical section operations
8 extern CRITICAL_SECTION pkgWin32CritSect;
9 #define QF_PROTECT() EnterCriticalSection(&pkgWin32CritSect)
10 #define QF_UNPROTECT() LeaveCriticalSection(&pkgWin32CritSect)
11 #define QF_ISR_PROTECT() QF_PROTECT()
272 Chapter 9: Implementations of the Quantum Framework
tions is to disable interrupts on entry to the section and enable interrupts again on
exit. Such a section of code is called the critical section.
Note: The time spent in a critical section should be kept to a minimum because
this time directly affects the interrupt latency. However, as long as the criti-
cal sections introduced in the QF take no more time than when disabling
interrupts elsewhere in the system (e.g., inside the underlying kernel or
device drivers), the critical sections do not extend the maximum interrupt
latency. As you will see, the QF disables interrupts only very briefly, which
should not affect the maximum interrupt latency.
For portability of code, the QF defines entry to and exit from a critical section in
the QF_PROTECT() and QF_UNPROTECT()10 macros to protect and unprotect a criti-
cal section, respectively (Listing 9.3, lines 9, 10). The definition of these macros
depends on the platform and the compiler. For example, on the DOS platform (as on
most embedded platforms), the macros can directly switch interrupts off and on. For
the x86 microprocessor, use the following macros.
#define QF_PROTECT() __asm{cli}
#define QF_UNPROTECT() __asm{sti}
Here, the macros use in-line assembly to execute x86 instructions. The format is
specific to the Microsoft compiler. CLI (clear interrupt flag) and STI (set interrupt
flag) disable and enable interrupts, respectively. You could also use _disable() and
_enable(), both declared in <dos.h>, to achieve the same effect.
However, on some platforms, you cannot disable and enable interrupts easily, and
the operating system provides different ways of protecting indivisible sections of
code. For example, the package-level header file for the Win32 port shows the defini-
tion of the Microsoft Windows–specific implementation of the critical section (List-
ing 9.3, lines 9, 10).
:QEPool 0
myFree
user-allocated
myNfree used used
buffer for events
myEvtSize
myNtot
As shown in Figure 9.2, the QEPool class manages a contiguous buffer of memory
that the clients use to preallocate storage for events. QEPool tracks memory by
chaining all unused memory blocks in a simple linked list (the free list). This tech-
nique is standard for organizing stack-like data structures, where the structure is
accessed like a stack in the LIFO manner from one end only.12 QEPool also uses a
handy trick to link free blocks together in the free list without consuming extra stor-
age for the pointers [Lafreniere 98, Labrosse 99]. By chaining together free (rather
than used) memory blocks, QEPool can reuse the blocks for other things, such as
linked list pointers. This use implies that the block size must be big enough to hold a
pointer.
the current number of free blocks in the pool (myNfree), and the lowest number of
free blocks remaining in the pool (myNmin). This last attribute, called the low-water
mark, tracks the worst-case pool utilization, which like the worst-case stack utiliza-
tion provides a valuable data point for the final fine tuning of your application. 13
13. You can check the myNmin data member in the debugger or through a memory dump.
276 Chapter 9: Implementations of the Quantum Framework
The way the QF manages event pools mitigates many of the risks associated with
using them directly. For example, the QF returns events to the same pool from which
they were allocated. Also, allocating events through the Q_NEW() macro (Section
8.4.1 in Chapter 8) is safer than direct invocation of QEPool::get() (actually, QF::
create()) because the macro uses event types consistently to allocate an event of
sufficient event size from the pool and to type cast (downcast) the returned event
pointer. As long as you don’t change the type of this pointer (e.g., by explicitly cast-
ing it to something else), you should not overrun memory.
14. For example, RTKernel-32 supports message queues, which I use later (Section 9.6) to demonstrate an RTOS-
based implementation of event queues.
278 Chapter 9: Implementations of the Quantum Framework
:QEQueue
myFrontEvt
myStart
myEnd
myHead
myTail
myNtot
user-allocated
myNused ring buffer of
pointers to
events
Figure 9.3 shows the roles and relationships among the QEQueue class attributes.
All outgoing events must pass through the myFrontEvt data member, which opti-
mizes queue operation by frequently bypassing buffering; in addition, it serves as a
queue status indicator (more about that later in this section). The pointers myStart,
myEnd, myHead, and myTail manage a ring buffer that the clients must preallocate
as a contiguous array of pointers of type QEvent*. Events are always extracted from
the buffer at the tail of the queue, the location to which myTail points in the ring
buffer. New events are typically inserted at the head of the queue, the location to
which myHead points in the ring buffer. Inserting events at the head and extracting
from the tail corresponds to FIFO operations (the putFIFO() method). QEQueue
also allows you to insert new events at the tail, which corresponds to LIFO opera-
tions (the putLIFO() method). Either way, the tail pointer always increments when
15. See the discussion following the Reminder state pattern in Section 5.2 in Chapter 5.
Common Elements 279
the event is extracted, as does the head pointer when an event is inserted. Using the
pair of pointers myStart and myEnd confines the range of the head and tail pointers
to the ring buffer. When either the myHead or myTail pointer equals myEnd, which
points to one location past the preallocated buffer, then the pointers (myHead or
myTail) are wrapped around to the myStart location. The effect is a clockwise
crawling of the pointers around the buffer, as indicated by the arrow in Figure 9.3.
Other data attributes of the QEQueue class include the total number of events in
the ring buffer (myNtot), the current number of events in the buffer (myNused), and
the maximum number of events ever placed in the buffer (myNmax). This last
attribute (the high-water mark) tracks the worst-case queue utilization, which pro-
vides a valuable data point for fine-tuning your application.16
16. You can inspect the myNmax data member in the debugger or through a memory dump.
280 Chapter 9: Implementations of the Quantum Framework
Listing 9.8 Extracting events from the event queue with QEQueue::get()
1 QEvent *QEQueue::get() {
2 register QEvent *e; // event to return to the caller
3 QF_PROTECT();
4 if (myFrontEvt == 0) { // is the queue empty?
5 QF_EQUEUE_WAIT(this); // wait for event to arrive directly
6 } // NOTE: QF_EQUEUE_WAIT() leaves the critical section
7 e = myFrontEvt;
8 if (myNused) { // buffer not empty?
9 --myNused; // one less event in the ring-buffer
10 myFrontEvt = *myTail; // remove event from the tail
11 if (++myTail == myEnd) {
12 myTail = myStart;
13 }
14 }
15 else {
16 myFrontEvt = 0; // queue empty
17 QF_EQUEUE_ONEMPTY(this); // used only in foreground/background
18 }
19 QF_UNPROTECT();
20 ENSURE(e);
21 return e;
22 }
Listing 9.8 shows how the QEQueue::get() method extracts events from the
queue. This method is called only from the thread routine of the active object that
owns this queue. If the owner thread calls get() and the queue is empty (the
myFrontEvt attribute is not set, line 4), the method blocks at line 5 and waits
indefinitely for the framework to insert an event into the queue. Blocking is a
platform-dependent operation, and the routine handles it through a platform-
dependent macro. This macro is designed to be invoked from a critical section
and to restore the critical section on its return.17 At line 7, the queue cannot be
empty anymore — it either was not empty to begin with or it just received an
event after blocking.18 The event at the front of the queue is copied for delivery to
the caller (line 7). If the ring buffer contains events (line 8), an event is extracted
from the tail and moved into the front event (line 10). Otherwise, the front event
is cleared, which indicates that the queue is empty (line 16). For nominal (multi-
threading) operation, you can ignore the macro in line 17 because it is used only
in the case of foreground/background processing, which I cover in Section 9.4. Note
17. For example, Listing 9.3 (lines 21–26) shows a Win-32-specific definition of QF_EQUEUE_WAIT().
18. This is true because only one thread can extract events from the queue.
Common Elements 281
that QEQueue::get() always returns a valid event pointer to the caller, as asserted
in the postcondition in line 20.
Listing 9.9 Inserting events into the event queue with QEQueue::putFIFO()
19. For example, Listing 9.3 (lines 27–29) shows a Win32-specific definition of QF_EQUEUE_SIGNAL().
282 Chapter 9: Implementations of the Quantum Framework
returns success (line 24). Note that there is no need to leave the critical section before
the last return because the QF_EQUEUE_SIGNAL() macro has done it already.
Listing 9.10 Inserting events into the event queue with QEQueue::putLIFO()
main() {
. . . // initialization
for (;;) { // for-ever
doA(); // perform action A
if (...) {
doB(); // conditionally perform action B
}
doC(); // perform action C
} // loop back
}
The timing diagram in Figure 9.4 shows a typical execution profile of a fore-
ground/background system. In the absence of interrupts, the background loop (List-
ing 9.11) executes action A, then B, then C, then loops back to A, and so on. Not all
actions in the loop need to execute every time — some might be conditional (such as
action B). When an interrupt occurs (Figure 9.4, time 8), the background loop sus-
pends, and the CPU switches context to the foreground (ISR). The foreground typi-
cally communicates with the background code through shared memory. The
background is responsible for protecting this memory from potential corruption (by
disabling interrupts when accessing the shared variables). When the foreground
284 Chapter 9: Implementations of the Quantum Framework
relinquishes control of the CPU, the background always resumes exactly at the point
it was interrupted. If the foreground makes some information available to the back-
ground, this information (however urgent) must wait for processing until the correct
background routine gets its turn to run. In the worst case, this delay can take the full
pass through the loop and is called the task-level response. Task-level response is
nondeterministic because it depends on the conditional execution within the back-
ground loop, as well as on the time the foreground preempts the background (as
indicated in Figure 9.4 with loop periods T1, T2, T3, etc.). Any change in either the
foreground or background code affects the timing.
active
ISR
inactive
running
T1 T3
A T2 T4
B not executed
waiting
running
B
waiting
running
C
waiting
0 5 10 15 20 time
main() {
QF::init(. . .); // initialize QF
QF::poolInit(. . .); // initialize at least one event-pool
activeA.start(. . .); // start active object A
activeB.start(. . .); // start active object B
activeC.start(. . .); // start active object C
// hook clock tick ISR (platform-dependent)
for (;;) { // for-ever
DOS: The QF without a Multitasking Kernel 285
20. To use more than eight active objects, you need to increase the size of pkgReadyList (e.g., to 16 bits) and to
extend the binary logarithm lookup table accordingly (this action can be expensive, so you might have to use
more creative lookup techniques).
286 Chapter 9: Implementations of the Quantum Framework
Figure 9.5 Binary logarithm lookup table maps byte value to most significant bit
most-significant bit
8
7
6
5
4
3
2 Y = log 2(x)
1
0 2 4 8 16 32 64 128 byte
significant bit set in the byte. Figure 9.5 shows the structure of this lookup table,
which turns out to be simply the binary logarithm (log base 2). The value returned
from the lookup calculation is the priority, which in turn is used to resolve the
pointer to the active object. This pointer serves in the next line (11) to extract the
event from the queue of the active object. The event is then dispatched to the active
object (line 12) and propagated to the other subscribers (line 13). Not surprisingly,
these two lines are identical to lines 12 and 13 in the thread routine discussed in
Chapter 8 (Listing 8.8), which QF::background() replaces in the foreground/back-
ground architecture.
The pkgReadyList variable is the central element of the design. The event queue
implementation in this case must always keep the ready list consistent with the state
of the event queues. This requirement implies that pkgReadyList is shared between
the background and the foreground because ISRs can change the state of event
queues by publishing events. (In fact, event passing should be the only way in which
the foreground communicates with the background.) As already mentioned in the
previous section, the background code is responsible for protecting any shared vari-
ables from corruption. Nonetheless, the background loop from Listing 9.13 accesses
pkgReadyList in lines 9 and 10 without any protection. Generally speaking, you
could disable interrupts before accessing pkgReadyList (before line 9) and enable
them after line 10 (and for completeness, after line 14 as well). In this particular case,
however, the critical section can be optimized away, because ISRs can only add
events to event queues (by setting bits in the ready list) but can never remove events
(by clearing the bits). Therefore, even if an ISR preempts QF::background()
between lines 9 and 10 (or somewhere in the middle of line 10) and sets some addi-
tional bits in pkgReadyList, the code handles it just fine. Any bit set in the ready list
still corresponds to a nonempty event queue.
The event queue implementation, on the other hand, must protect any access to
the shared ready list pkgReadyList. Listing 9.14 shows the definition of the plat-
form-dependent macros pertaining to event queues, which appear in the platform-
independent event queue code in Section 9.3.3.
DOS: The QF without a Multitasking Kernel 287
1 #include "qassert.h"
2 #include "port.h"
3
4 // foreground code ...
5 enum { TICK_VECTOR = 0x08 };
6 static void (__cdecl __interrupt __far *dosISR)(void);
7 void __cdecl __interrupt __far ISR(void) {
8 QF::tick();
9 _chain_intr(dosISR);
10 }
11 // background code ...
12 main() {
DOS: The QF without a Multitasking Kernel 289
13 QF::poolInit(...);
14 activeA.start(...);
15 . . . // start other active objects...
16 dosISR = _dos_getvect(TICK_VECTOR);
17 _disable();
18 _dos_setvect(TICK_VECTOR, ISR); // hook the clock tick ISR
19 _enable();
20
21 for (;;) { // for-ever
22 QF::background(); // background processing
23 if (_kbhit()) { // any key pressed?
24 break; // break out of the loop and shutdown
25 }
26 }
27 QF::cleanup();
28 _disable();
29 _dos_setvect(TICK_VECTOR, dosISR);
30 _enable();
31 return 0;
32 }
Listing 9.16 (lines 5–10) pertain to the foreground code, which consists of one
ISR — the clock tick. This ISR (lines 7–10) invokes QF::tick() and chains to the
standard DOS clock interrupt.
The rest of Listing 9.16 pertains to the background process. After the standard
initialization of the framework (lines 13–15), the ISR is hooked up (lines 16–19) and
followed by the background loop. The loop invokes QF::background() in line 22
and polls for keyboard input, providing a way to break out of the loop and terminate
the application. The DOS-specific cleanup consists mostly of restoring the original
interrupt handler (lines 28–30).
Exercise 9.1 Install the evaluation version of On Time RTOS-32 on your PC from the
accompanying CD-ROM and port the QF to On Time’s RTTarget-32.
For the purpose of this discussion, you can view RTTarget-32 as a 32-bit
protected-mode DOS. The QF port to RTTarget-32 can use the same
source code as DOS, except for system initialization and when connect-
ing the clock tick interrupt. Refer to the On Time documentation
[OnTime 01] and to the code of the sample application Serint (both
provided in the On Time evaluation kit). Compare your port with the
solution on the CD-ROM.
290 Chapter 9: Implementations of the Quantum Framework
21. Code and data in qf_dos.lib for DOS (large memory model, release version) take 5.5KB.
22. A cooperative multitasking kernel requires RAM for task control blocks and separate per-task stacks, as well as
ROM for the kernel code.
23. For smaller systems, you might be more interested in the C version of the QF, which is available on the accom-
panying CD-ROM.
Win32: The QF on the Desktop 291
24. Several techniques for improving real-time performance of Microsoft Windows exist (e.g., see [Epplin 98]).
292 Chapter 9: Implementations of the Quantum Framework
Note: Using the Win32 CRITICAL_SECTION object can lead to priority inversion
on Win32 platforms; NT and 9x have different methods of handling it.
Interestingly, 9x appears to handle it the proper way by boosting the
lower priority thread with the lock to the same priority as the thread that
is waiting for the lock. NT randomly boosts priorities of lower priority
threads until the lock is released. See Microsoft Knowledge Base article
Q96418 for more details [MicrosoftKB 01].
In Section 9.2, you saw the package scope header file for the Win32 port (Listing
9.2). The QF_PROTECT() macro resolves to the EnterCriticalSection() Win32
API call, and QF_UNPROTECT() resolves to the matching call LeaveCritical-
Section() (Listing 9.3, lines 9, 10). Because QF::tick() runs in the task context,
it also needs to be protected; therefore, the QF_ISR_PROTECT() and QF_ISR_
UNPROTECT() macros also resolve to the same Win32 calls. All these macros use
only one package scope, critical-section object: pkgWin32CritSect.
The QF port to Win32 demonstrates how to use the event pool and event queue
classes (Sections 9.3.1 and 9.3.2) in a multithreaded environment. Porting the event
pool class is trivial because the only platform-dependent element is the critical sec-
tion, which has already been handled. The event queue class, on the other hand, is
more problematic because it needs to block the corresponding active object when the
queue is empty. To do so, the event queue uses the Win32 event object (not to be con-
fused with the event instances exchanged among active objects). To block the calling
thread, the event queue uses the WaitForSingleObject() Win32 call (see the fol-
lowing code snippet of the QF_EQUEUE_WAIT() macro taken from Listing 9.3, lines
21–26). Conversely, to unblock the thread, the QF_EQUEUE_SIGNAL() macro resolves
to SetEvent() (Listing 9.3).
#define QF_EQUEUE_WAIT(q_) \
QF_UNPROTECT(); \
do { \
WaitForSingleObject((q_)->myOsEvent, INFINITE); \
} while ((q_)->myFrontEvt == 0); \
QF_PROTECT()
myFrontEvt (Listing 9.8, line 16). After retrieving the event, active object A starts
processing. However, active object B preempts A and publishes a new event for
active object A. QEQueue::putFIFO() signals active object A’s queue (it invokes
SetEvent() via the QF_EQUEUE_SIGNAL() macro) because the queue is empty (List-
ing 9.9 line 6). When active object A comes to retrieve the next event, the queue is
not empty (myFrontEvt isn’t cleared), so the queue does not attempt to block. This
time, nobody inserts events to the empty queue of active object A. When active
object A calls QEQueue::get() again, it attempts to block because myFrontEvt is
cleared. However, the state of the Win32 event is signaled, and WaitForSingle-
Object() returns immediately without blocking. The do–while loop in the QF_
EQUEUE_WAIT() macro saves the day by enforcing the second call to WaitFor-
SingleObject() — this time truly blocking the active object A until myFrontEvt is
set. Note also that the QF_EQUEUE_WAIT() macro tests the myFrontEvent attribute
outside of a critical section, which is safe in this case because only one thread (the
owner of the queue) can extract events from the queue.
Listing 9.17 Win32 critical section and execution thread integrated into the QF
#include "qf_win32.h"
main() {
QF::init(. . .);
QF::poolInit(. . .);
activeA.start(. . .);
. . .
for (;;) { // for-ever
RTKernel-32: The QF with a Preemptive Priority-Based Kernel 295
Note: The example in Listing 9.18 assumes a Win32 console application. If you
want to use the QF in Microsoft Windows GUI applications, you should
arrange for a WM_TIMER message to trigger the invocation of QF::tick()
from WinMain(). Often, you can use state machines in Microsoft Windows
GUIs without the QF (see the Quantum Calculator example in Chapter 1).
25. Sleep() has a resolution of only 18.2Hz (55ms) on Win 9x platforms. The granularity of the system timer in
the NT family is much finer (10ms).
296 Chapter 9: Implementations of the Quantum Framework
26. RTKernel-32 is a product of On Time Software. The accompanying CD-ROM contains a fully functional eval-
uation version of RTKernel-32 and other On Time products, as well documentation and vendor information
(see also http://www.on-time.com).
27. Among other development environments, you can use RTKernel-32 in conjunction with Microsoft Visual C++.
28. I am not associated with On Time Software in any way. I just have used their product and like it.
29. Chapter 10 presents a simple QF application and explains how to cross-debug it with RTKernel-32.
RTKernel-32: The QF with a Preemptive Priority-Based Kernel 297
mask interrupts at the level of the programmable interrupt controller, which consists
of two cascaded Intel 8089A-compatible chips. You can mask and unmask individ-
ual IRQ lines with the RTKernel-32 functions RTKDisableIRQ() and RTKEn-
ableIRQ(), respectively.30
Listing 9.19 Package scope port.h include file for the RTKernel-32 port
1 #ifndef port_h
2 #define port_h
3
4 #include "qf_rtk32.h"
5 #include "qfpkg.h"
6
7 // RTK32-specific critical section operations
8 #define QF_PROTECT() RTKDisableInterrupts()
9 #define QF_UNPROTECT() RTKEnableInterrupts()
10 //#define QF_PROTECT() __asm{cli}
11 //#define QF_UNPROTECT() __asm{sti}
12 #define QF_ISR_PROTECT()
13 #define QF_ISR_UNPROTECT()
14
15 // RTK32-compiler-specific cast
16 #define Q_STATE_CAST(x_) reinterpret_cast<QState>(x_)
17
18 // RTK32-specific event pool
19 class QEPool { // "Quantum" Event Pool
20 friend class QF;
21 RTKMemPool myRTKPool; // RTK memory pool
22 unsigned short myEvtSize; // maximum event size (in bytes)
23 };
24
25 // RTK32-specific event pool operations
26 #define QF_EPOOL QEPool
27 #define QF_EPOOL_INIT(p_, poolSto_, nEvts_, evtSize_) \
28 if (1) { \
29 ASSERT(poolSto_ == 0); \
30 (p_)->myEvtSize = evtSize_; \
31 RTKAllocMemPool(&(p_)->myRTKPool, evtSize_, nEvts_); \
32 } else
33 #define QF_EPOOL_GET(p_, e_) \
34 ((e_) = (QEvent *)RTKGetBuffer(&(p_)->myRTKPool))
35 #define QF_EPOOL_PUT(p_, e_) \
36 RTKFreeBuffer(&(p_)->myRTKPool, e_);
30. The QF does not use this method to implement a critical section, but the technique is helpful — for example, to
mask interrupts while installing an interrupt handler for a particular IRQ.
298 Chapter 9: Implementations of the Quantum Framework
37
38 // the following constant may be bumped up to 15 (inclusive)
39 // before redesign of algorithms is necessary
40 enum { QF_MAX_ACTIVE = 15 };
41
42 #endif // port_h
Commercial RTOSs frequently support real-time memory management in the
form of fixed block–size heaps. RTKernel-32 calls them memory pools (see
[OnTime 01, Real-Time Memory Management]). The RTKernel-32 memory
pools offer almost the entire functionality required by the QF event pools, except
for a method to check the block size of the pool. Therefore, the package scope
header file (Listing 9.19) wraps the QEPool class around the RTKernel-32 memory
pool RTKMemPool (lines 19–23). The QEPool class supplies the missing piece of
information in the myEvtSize attribute. Lines 26 through 36 define the platform-
dependent event pool operations: initializing a pool, obtaining an event, and releas-
ing an event. All these operations rely on the RTKernel-32 services RTKAllocMem-
Pool(), RTKGetBuffer(), and RTKFreeBuffer(), respectively.
Listing 9.20 Public qf_rtk32.h include file for the RTKernel-32 port
1 #ifndef qf_rtk32_h
2 #define qf_rtk32_h
3
4 #include <rtk32.h>
5
6 // RTK-32-specific event queue and thread types
7 #define QF_EQUEUE(x_) RTKMailbox x_;
8 #define QF_THREAD(x_) RTKTaskHandle x_;
9 #define Q_STATIC_CAST(type_, expr_) static_cast<type_>(expr_)
10
11 #include "qevent.h"
12 #include "qhsm.h"
13 #include "qactive.h"
14 #include "qtimer.h"
15 #include "qf.h"
16
17 #endif // qf_rtk32_h
Listing 9.21 RTKernel-32 mailbox and execution thread integrated into the QF;
identifiers in boldface indicate RTK-32 function calls
37
38 void QActive::stop() {
39 QF::remove(this);
40 RTKTerminateTask(&myThread);
41 }
42
43 int QActive::enqueue(QEvent *e) {
44 return RTKPutCond(myEqueue, &e);
45 }
46
47 void QActive::postFIFO(QEvent *e) {
48 REQUIRE(e->useNum == 0); // event must not be in use
49 ALLEGE(RTKPutCond(myEqueue, &e));
50 }
51
52 void QActive::postLIFO(QEvent *e) {
53 REQUIRE(e->useNum == 0); // event must not be in use
54 ALLEGE(RTKPutFrontCond(myEqueue, &e));
55 }
Listing 9.21 shows the platform-dependent implementation details. In line 22,
QActive::start() creates an RTKernel-32 mailbox capable of holding qLen
pointer-sized messages. The active object’s thread routine, QActive::run() (lines
5–13), retrieves event pointers from the mailbox by means of the indefinitely block-
ing call to RTKGet(). QActive::enqueue() (lines 43–45), QActive::postFIFO()
(lines 47–50), and QActive::postLIFO() (lines 52–55) are implemented with con-
ditional (nonblocking) calls to either RTKPutCond() or RTKPutFrontCond(). As
always in the QF, these calls are associated with the guarantee of event delivery con-
tracts (Section 8.5.2 in Chapter 8).
In lines 26 through 32 of Listing 9.21, QActive::start() creates an RTKer-
nel-32 thread. No remapping of priorities is necessary in this case because RTKer-
nel-32 uses the same priority numbering as the QF. Finally, QActive::stop() (lines
38–41) terminates the thread by means of the RTKTerminateTask() call.
Listing 9.22 RTKernel-32 initialization and setup of the clock tick ISR
1 #include "qf_rtk32.h"
2
3 enum { TIMER_IRQ = 0, TICKS_PER_SEC = 100};
4
5 static RTKIRQDescriptor rtk32ISR;
6 static void RTKAPI ISR(void) { //high-level Interrupt Service Routine
7 QF::tick();
8 RTKCallIRQHandlerFar(&rtk32ISR); // chain to RTK-32 ISR
9 }
10
11 main() {
12 RTKernelInit(RTK_MIN_PRIO /* this task priority */);
13 KBInit();
14
15 RTKDisableIRQ(TIMER_IRQ);
16 RTKSaveIRQHandlerFar(TIMER_IRQ, &rtk32ISR);
17 RTKSetIRQHandler(TIMER_IRQ, ISR); // hook up the custom ISR
18 RTKEnableIRQ(TIMER_IRQ);
19
20 // set up the ticking rate consistent with TICKS_PER_SEC
21 CLKSetTimerIntVal((unsigned)(1e6/TICKS_PER_SEC + 0.5));
22 RTKDelay(1); // wait for the value to take effect
23
24 QF::init(. . .);
25 QF::poolInit(. . .);
26 activeA.start(. . .);
27 . . .
28 getc(stdin); // block the main thread until the user hits ENTER
29 QF::cleanup();
30 return 0;
31 }
Exercise 9.2 Prepare another port of the QF to RTKernel-32 using the QEQueue class
instead of the RTKernel-32 mailbox. Apply the RTKernel-32 binary
semaphore to block the calling thread when the queue is empty. Com-
pare your port with the solution on the CD-ROM.
Null modem
serial cable
x86 Target
Boot floppy with
x86 Host RTTarget-32 debug monitor
Exercise 9.3 Prepare both the host and target machines as shown in Figure 9.6 and as
described in the On Time documentation for RTOS-32 (included in the
RTOS-32 evaluation kit). Follow the On Time documentation [OnTime
01] to prepare the boot floppy and executable for the Hello example
application. Next, use Microsoft Developer Studio to download the code
to the target and single-step through the code in the debugger.
9.7 Summary
In this chapter, I filled in the necessary implementation details you need to
adapt the QF to specific computer platforms. The QF is designed for deployment
as a fine-granularity class library that you statically link with your applications.
The physical structure of the code anticipates platform dependencies up front
and allows an open-ended number of different QF ports.
You have seen QF ports to three very different software architectures, which
demonstrates the high portability of the QF and the flexibility of the design. The
spectrum of possible QF applications starts with low-end embedded systems without
a multitasking kernel, through desktop machines, to real-time systems running under
31. In fact, the evaluation version of RTKernel-32 allows you to execute programs only after downloading code
from the host to the target (the fully licensed version supports several ways to bootstrap the target).
Summary 303
the control of a commercial RTOS. In all of these areas, the QF has many benefits to
offer, and you can use it in commercial products.
Every QF port is provided with full source code and is illustrated with sample
applications (described in the next chapter) that you can execute on your PC.
Although I discussed only the C++ version of the framework in this chapter, the
accompanying CD-ROM provides the equivalent C version as well.
304 Chapter 9: Implementations of the Quantum Framework
10
Chapter 10
Sample Quantum
Framework Application
The Golden Gate Bridge was built to withstand gales and strong
currents,… eleven workers died during the construction completed in
May 1937 … over 100,000 cars cross over it every day …
— San Francisco Tourist Guide
In the last two chapters, I dragged you through the internal workings of the Quan-
tum Framework (QF). The way the QF is coded internally resembles the construction
of a bridge over turbulent waters, and at times, it is like balancing on the edge of a
cliff without a safety net. Virtually every line of code, as with every step on a tight-
rope, poses risks. Are all possible scenarios of preemptions taken into account? Are
all sensitive code fragments protected with critical sections? Are the critical sections
short enough? Are priority inversions ruled out? Is the framework code watertight?
Well, I sure hope so,1 but this is neither productive nor the fun way of developing
1. I have thoroughly tested the code, but I cannot guarantee that it is correct. If you believe that you’ve found a
bug, please contact me at the address listed in the Preface.
305
306 Chapter 10: Sample Quantum Framework Application
software: it’s hard, it’s slow, it’s risky — it’s the conventional approach to multi-
threaded programming. However, the struggle is over. The bridge is now open for
traffic, so everybody can cross it quickly and comfortably, without taking much risk.
The QF offers you a faster, safer, and more reliable way of developing concurrent
software. A QF-based application has no need to fiddle directly with critical sections,
semaphores, and other such mechanisms. You can program without the constant fear
of race conditions, deadlocks, starvation, priority inversions, and other perils inher-
ent to traditional concurrent programming. Yet, your QF applications can reap all
the benefits of multithreading.
My goal in this chapter is to explain how to generate a QF application. First, I
explain the implementation of the active object–based solution to the dining philoso-
phers problem (DPP) introduced in Chapter 7, then I show you how to deploy the
code on different platforms. As always, every step is illustrated with executable
examples.
The second part of this chapter concentrates on the rules, heuristics, caveats, and
costs associated with using an active object–based framework in general and the QF
in particular. You can think of these issues as a small price to pay for the convenience
of application development — like the toll for crossing a bridge.
list of signals and event classes does not need to be complete and almost certainly
will grow and change as you progress through the development.
A natural place for declaring signals and event classes is the package scope header
file Cpp\Qdpp\Include\port.h (Figure 9.1 in Chapter 9) because all modules need
to share these elements.
1 enum DPPSignals {
2 HUNGRY_SIG = Q_USER_SIG, //sent by philosopher when becoming hungry
3 DONE_SIG, // sent by philosopher when done eating
4 EAT_SIG, // sent by Table to let a philosopher eat
5 TIMEOUT_SIG, // timeout to end thinking or eating
6 //... insert new signals here
7 MAX_SIG // keep this signal always *last*
8 };
9
10 struct TableEvt : public QEvent {
11 int philNum; // philosopher number
12 };
For smaller applications, such as the DPP, it is convenient to define all signals in
one enumeration (rather than in separate enumerations or, worse, as preproces-
sor #define macros). An enumeration automatically guarantees the uniqueness
of signals3 (Listing 10.1, lines 1–8). An additional bonus, as the result of an enumer-
ation, is automatic tracking of the total number of signals through the last element in
the enumeration (e.g., MAX_SIG in line 7). You need to know the total number of sig-
nals to allocate the subscriber list lookup table and initialize the framework (Section
8.5.2 in Chapter 8). Note that the user signals must start with the offset Q_USER_SIG
(line 2) to avoid overlapping the reserved signals.
Listing 10.1 defines only one generic EAT_SIG signal (line 4), rather than a spe-
cific EAT signal for each philosopher (see Exercise 10.1 for an alternative solution).
This design decision represents a trade-off between generality and performance. A
generic EAT_SIG signal makes changing the number of participating philosophers
easy4 but requires that each Philosopher state machine filters out only the events
pertaining to this particular Philosopher instance. Of course, generating and dis-
patching events that most subscribers ignore (four out of five) is wasteful and nega-
tively affects performance.
Lines 10 through 12 of Listing 10.1 show an example of an event class with
parameters. As described in Section 4.1.1 in Chapter 4, you specify event parameters
by subclassing the QEvent base class. You must derive an event class for every set of
specific event parameters in your application. Typically, you will end up with fewer
event classes than signals, because not every signal requires a unique set of parame-
ters and some signals don’t need parameters at all. For example, the DPP application
needs only one specific parameter — the philosopher number declared in the
TableEvt class. The TableEvt class is associated with signals HUNGRY_SIG,
DONE_SIG, and EAT_SIG, but not with TIMEOUT_SIG, which does not require
parameters.
You should keep your event classes simple. In particular, avoid introducing con-
structors or protecting data members because (as described in Section 4.1.1 in Chap-
ter 4) constructors aren’t invoked when you dynamically create events with the
Q_NEW() macro. Protecting event data members just hinders access to event parame-
ters from within state handlers. As a reminder of the light-weight character of events,
Listing 10.1 uses struct rather than class for TableEvt (line 10).
Exercise 10.1 Change the definition of signals in Listing 10.1 by replacing the generic
EAT_SIG signal with a set of signals (EAT0_SIG, EAT1_SIG, …), repre-
senting permission to eat for a specific philosopher. Modify the imple-
mentations of the Table and Philosopher active objects accordingly.
Figure 10.1 Numbering of philosophers and forks (see the LEFT() and RIGHT()
macros in Listing 10.3).
RIGHT(n) LEFT(LEFT(n))
n LEFT(n)
RIGHT(n) LEFT(n)
Listing 10.3 State machine elements of the Table active object; boldface indicates
the QF services
30 }
31 else { // philosopher "n" has to wait for free forks
32 isHungry[n] = !0; // mark philosopher "n" hungry
33 }
34 return 0;
35 case DONE_SIG:
36 n = ((TableEvt *)e)->philNum; // extract sender’s ID
37 ASSERT(n < N); // ID must be in range
38 printf("Philospher %1d is thinking\n", n);
39 myFork[LEFT(n)] = myFork[n] = FREE; // free-up forks
40 m = RIGHT(n); // check the right neighbor
41 if (isHungry[m] && myFork[m] == FREE) { // should it eat?
42 myFork[n] = myFork[m] = USED; // pick up its forks
43 isHungry[m] = 0; // mark philosopher "m" not hungry anymore
44 pe = Q_NEW(TableEvt, EAT_SIG);
45 pe->philNum = m; // grant "m" permission to eat
46 QF::publish(pe);
47 printf("Philospher %1d is eating\n", m);
48 }
49 m = LEFT(n); // check the left neighbor
50 n = LEFT(m); // left fork of left neighbor
51 if (isHungry[m] && myFork[n] == FREE) { // should it eat?
52 myFork[m] = myFork[n] = USED; // pick up its forks
53 isHungry[m] = 0; // mark philosopher "m" not hungry anymore
54 pe = Q_NEW(TableEvt, EAT_SIG);
55 pe->philNum = m; // grant "m" permission to eat
56 QF::publish(pe);
57 printf("Philospher %1d is eating\n", m);
58 }
59 return 0;
60 }
61 return (QSTATE)&Table::top;
62 }
Listing 10.3 shows the implementation of the Table statechart.5 In the initial
transition (lines 5–13), you see how Table subscribes to the HUNGRY_SIG and
DONE_SIG signals (lines 6, 7). These signals trigger the internal transitions in the
serving state, defined in lines 15 through 62. This ordinary state handler imple-
ments a specific policy for resolving contentions over the forks. The numbering con-
vention for philosophers and forks shown in Figure 10.1, as well as the extensive
comments in Listing 10.3, should help you understand the algorithm. The novelty
here is creating, initializing, and publishing events (in lines 26–28, 44–46, and 54–
56, respectively). Note that occasionally the Table object grants two permissions to
eat in response to one DONE_SIG (see also the sequence diagram in Figure 7.4 in
Chapter 7).
Listing 10.4 Philosopher active object derived from the QActive class
45 Q_TRAN(&Philosopher::thinking);
46 return 0;
47 case Q_EXIT_SIG:
48 pe = Q_NEW(TableEvt, DONE_SIG);
49 pe->philNum = myNum;
50 QF::publish(pe);
51 return 0;
52 }
53 return (QSTATE)&Philosopher::top;
54 }
Exercise 10.2 Modify the Philosopher::eating() state handler from Listing 10.5 to
intercept the generic EAT_SIG signal, and assert that the signal is not
addressed to this Philosopher instance. Hint: assert the converse of the
guard in line 30.
Exercise 10.3 Find the DOS version of the QF DPP application on the accompanying
CD-ROM and execute it on your PC (qdpp.exe runs in native DOS or
in a DOS window on Microsoft Windows).
Exercise 10.4 Find the Win32 version of the QF DPP application on the accompanying
CD-ROM and execute it on your PC.
Exercise 10.5 Find the RTKernel-32 version of the QF DPP application on the accom-
panying CD-ROM. Prepare the host/target environment as described in
Section 9.6 in Chapter 9 and download the application for execution on
the target machine.
10.1.5 N otes
You might object rightly that the QF-based solution to the DPP is bigger (when mea-
sured in lines of code) than a typical traditional solution. However, as I try to dem-
onstrate in the following discussion, none of the traditional approaches to DPP are in
the same class as the active object–based solution.
The active object–based solution might be a bit larger than the traditional solu-
tion, but the QF-based code is straightforward and free of all concurrency hazards.
In contrast, any traditional solution deals directly with low-level mechanisms, such
as semaphores or mutexes, and therefore poses a risk of deadlock, starvation, or sim-
ply unfairness in allocating CPU cycles to philosopher tasks.
However, what sets the active object–based solution truly apart is its unparalleled
flexibility and resilience to change. Consider, for example, how the initial problem
could naturally evolve into a more realistic application. For instance, starting a con-
versation seems a natural thing for philosophers to do. To accomplish such a new
feature (interaction among the philosophers), a traditional solution based on block-
ing philosopher threads would need to be redesigned from the ground up because a
hungry (blocked) philosopher cannot participate in the conversation. Blocked
threads are unresponsive.
A deeper reason for the inflexibility of the traditional solution is using blocking
to represent a mode of operation. In the traditional solution, when a philosopher
wants to eat and the forks aren’t available, the philosopher blocks and waits for the
forks. That is, blocking is equivalent to a very specific mode of operation (the hun-
gry mode), and unblocking represents a transition out of this mode. Only the fulfill-
ment of a particular condition (the availability of both forks) can unblock a hungry
Rules for Developing QF Applications 315
philosopher. The whole structure of the intervening code assumes that unblocking
can only happen when the forks are available (after unblocking, the philosopher
immediately starts to eat). No other condition can unblock the philosopher thread
without causing problems. Blocking is an inflexible way to implement modal behav-
ior.
In contrast, the active object–based computing model clearly separates blocking
from the mode (hungry) of operation and unblocking from the signaling of certain
conditions (the availability of forks). Blocking in active objects corresponds merely
to a pause in processing events and does not represent a particular mode of the active
object. Keeping track of the mode is the job of the active object’s state machine. A
blocked philosopher thread in the traditional solution can handle only one occur-
rence (the availability of the forks). In contrast, the state machine of a Philosopher
active object is more flexible because it can handle any occurrences, even in the hun-
gry state. In addition, event passing among active objects is a more powerful com-
munication mechanism than signaling on a semaphore. Apart from conveying some
interesting occurrence, an event can provide detailed information about the qualita-
tive aspects of the occurrence (by means of event parameters).
The separation of concerns (blocking, mode of operation, and signaling) in active
object–based designs leads to unprecedented flexibility, because now, any one of
these three aspects can vary independently of the others. In the DPP example, the
active object–based solution easily extends to accommodate new features (e.g., a
conversation among philosophers) because a Philosopher active object is as
responsive in the hungry state as in any other state. The Philosopher state machine
can easily accommodate additional modes of operation. Event-passing mechanisms
can also easily accommodate new events, including those with complex parameters
used to convey the rich semantic content of the conversation among philosophers.
6. The QF does not allow you to block an empty event pool or a full event queue.
316 Chapter 10: Sample Quantum Framework Application
Exercise 10.6 Note that only the Table active object in the DPP application sends out-
put to the screen by calling printf(). Why is calling printf() from the
Philosopher state machine not such a good idea?
7. Of course, these guarantees can be made only when the QF is based on a true, priority-based, real-time kernel
with correctly implemented event pools and event queues (see Section 9.3.3 in Chapter 9).
8. Occasionally, you might use a semaphore to implement blocking on an external device, but never to synchronize
with other active objects (see Section 8.5.4 in Chapter 8).
Heuristics for Developing QF Applications 317
Exercise 10.7 Philosopher active objects in the DPP application think and eat for a
fixed number of clock ticks. To make this application more interesting,
you might want to introduce random timeouts (by calling rand()). Why
is the standard random number generator inappropriate to use in the
Philosopher active objects?
• Most of the time, you can concentrate only on the internal state machines of
active objects and ignore their other aspects (such as threads of execution and
event queues). In fact, generating a QF application consists mostly of elaborating
on the state machines of active objects. The powerful behavioral inheritance
meta-pattern (Chapter 4) and the basic state patterns (Chapter 5) can help you
with that part of the problem.
This list could go on for a long time. In fact, an in-depth coverage of the active
object–based paradigm could easily fill entire book. The few basic guidelines listed
here are intended just to get you started.
Note: Selic and colleagues [Selic +94] present perhaps the most comprehensive
discussion of active object–based computing from a variety of angles,
including analysis, design, tools, and process issues. Douglass [Douglas 99]
presents unique state patterns, safety-related issues, and a process applica-
ble to real-time development.
Instead of repeating here what you can find elsewhere, I devote the rest of this
chapter to the practical issue of sizing event queues and event pools. This subject,
although important to any real-life project, is not covered in the literature (at least
not in the specific context of active object–based systems).
9. In some active object–based systems, events are allocated from the heap instead of from event pools. Whichever
way it is done, the memory must be sized adequately.
Sizing Event Queues and Event Pools 319
sized defaults. In fact, you should do exactly the same thing: create massively over-
sized event queues and event pools in the early stages of development.
At some point, however, the problem will catch up with you. Ultimately, you need
to deploy the software on production hardware with minimal RAM. Even before
that, however, you need to develop a sense of the right size of event queues and event
pools in order to know how to oversize them in the first place. For some applications
(like the DPP), an event queue of length five is massively oversized, whereas in other
cases, such a queue is inadequate.
The correct sizing of event queues and event pools is especially important in QF
applications because the QF offers no excuses to overflow an event queue or to run
out of events in a pool. These situations are both treated as first-class bugs (Chapters
8 and 9), no different than running out of execution stack space, with potential con-
sequences that are just as disastrous.
increase dramatically. Even if you apply a fudge factor, such as adding 30 percent
extra capacity, you cannot absolutely trust the empirical method [Kalinsky 01].
The alternative technique relies on a static analysis of event production and event
consumption. The QF uses event queues in a rather specific way (e.g., there is only
one consumer thread); consequently, the production rate P(t) and the consumption
rate C(t) are strongly correlated.
For example, consider a QF application running under a preemptive, priority-
based scheduler.10 Assume further that the highest priority active object receives
events only from other active objects (but not from ISRs). Whenever any of the lower
priority active objects publishes an event for the highest priority object, the scheduler
immediately assigns the CPU to the recipient. The scheduler makes the context
switch because, at this point, the recipient is the highest priority thread ready to run.
The highest priority active object awakens and runs to completion, consuming any
event published for it. Therefore, the highest priority active object really doesn’t need
to queue events (the maximum length of its event queue is 1).
Exercise 10.8 The Table active object from the DPP application is the highest priority
active object that receives events only from the Philosopher active
objects, so utilization of the Table event queue should not go beyond 1.
Verify this fact by using the empirical method. Use the QF RTKernel-32
port from Exercise 9.2 in Chapter 9, because this version is based on the
preemptive, priority-based kernel and uses the QEQueue class (so you can
inspect the high-water mark myNmax).
When the highest priority active object receives events from ISRs, then more
events can queue up for it. In the most common arrangement, an ISR produces only
one event per activation. In addition, the real-time deadlines are typically such that
the highest priority active object must consume the event before the next interrupt.
In this case, the object’s event queue can grow, at most, to two events: one from a
task and the other from an ISR.
You can extend this analysis recursively to lower priority active objects. The max-
imum number of queued events is the sum of all events that higher priority threads
and ISRs can produce for the active object within a given deadline. The deadline is
the longest RTC step of the active object, including all possible preemptions by
10. The following discussion also pertains approximately to foreground/background systems with pri-
ority queues (see Section 9.4 in Chapter 9). However, the analysis is generally not applicable to desk-
top systems (e.g., Microsoft Windows or desktop Linux), where the concept of thread priority is
much fuzzier.
Sizing Event Queues and Event Pools 321
higher priority threads and ISRs. For example, in the DPP application, all Philoso-
pher active objects perform very little processing (they have short RTC steps). If the
CPU can complete these RTC steps within one clock tick, the maximum length of the
Philosopher queue would be three events: one from the clock-tick ISR and two 11
from the Table active object.
Exercise 10.9 Apply the empirical method to determine the event queue utilization of
Philosopher active objects in the DPP application. Verify that the event
queues of higher priority philosophers are never longer than those of
lower priority philosophers (make sure you run the application long
enough). Extend the RTC step of the Philosopher state machine (e.g.,
spend some CPU cycles in a do-nothing loop) and observe when the
event queue of the lowest priority philosopher goes beyond 3. Look at
the event queue utilization of higher priority active objects.
The rules of thumb for the static analysis of event queue capacity are as follows.
• The size of the event queue depends on the priority of the active object.
Generally, the higher the priority, the shorter the necessary event queue. In
particular, the highest priority active object in the system immediately con-
sumes all events published by the other active objects and needs to queue
only those events published by ISRs.
• The queue size depends on the duration of the longest RTC step, including
all potential (worst-case) preemptions by higher priority active objects and
ISRs. The faster the processing, the shorter the necessary event queue. To
minimize the queue size, you should avoid very long RTC steps. Ideally, all
RTC steps of a given active object should require about the same number of
CPU cycles to complete.
• Any correlated event production can negatively affect queue size. For exam-
ple, sometimes ISRs or active objects produce multiple event instances in
one RTC step (e.g., the Table active object occasionally produces two per-
missions to eat). If minimal queue size is your priority, you should avoid
such bursts by, for example, spreading event production over many RTC
steps.
Remember also that the static analysis pertains to a steady-state operation after
the initial transient. On startup, the relative priority structure and the event produc-
tion patterns might be quite different. Generally, it is safest to start active objects in
11. Why two? See Section 10.1.2 and the discussion of Exercise 10.9 on the CD-ROM.
322 Chapter 10: Sample Quantum Framework Application
the order of their priority, beginning from the lowest priority active objects because
they tend to have the biggest event queues.
Exercise 10.10 Estimate the event pool size for the DPP application and compare it to
the empirical measurement. Hint: inspect the low-water mark (the
myNmin attribute) of the QEPool class.
When you use more event pools (the QF allows up to three, see Section 8.4.1 in
Chapter 8), the analysis becomes more involved. Generally, you need to proceed as
with event queues. For each event size, you determine how many events of this size
can accumulate at any given time inside the event queues and can otherwise exist as
temporaries in the system.
The minimization of memory consumed by event queues, event pools, and execu-
tion stacks is like shrink-wrapping your QF application. You should do it toward the
end of application development because it stifles the flexibility you need in the earlier
stages. Note that any change in processing time, interrupt load, or event production
patterns can invalidate both your static analysis and the empirical measurements.
However, it doesn’t mean that you shouldn’t care at all about event queues and event
pools throughout the design and early implementation phase. On the contrary,
understanding the general rules for sizing event queues and pools helps you conserve
memory by avoiding unnecessary bursts in event production or by breaking up
excessively long RTC steps. These techniques are analogous to the ways execution
stack space is conserved by avoiding deep call nesting and big automatic variables.
System Integration 323
10.6 Summary
The internal implementation of the QF uses the traditional techniques, such as criti-
cal sections and message queues. However, after the infrastructure for executing
active objects is in place, the development of QF-based applications can proceed
much easier and faster. The higher productivity comes from active objects that can be
programmed in a purely sequential way while the application as a whole still can
take full advantage of multithreading.
Generating a QF application involves defining signals and event classes, elaborat-
ing state machines of active objects, and deploying the application on a concrete
platform. The high portability of the QF enables you to develop large portions of the
code on a different platform than the ultimate target.
Active object–based applications tend to be much more resilient to change than
applications based on the traditional approach to multithreading. This high adaptabil-
ity is rooted in the separation of concerns in active object–based designs. In particular,
active objects use state machines instead of blocking to represent modes of operation
and use event passing instead of unblocking to signal interesting occurrences.
Programming with active objects requires some discipline on the part of the pro-
grammer because sharing memory and resources is prohibited. The experience of
many people has shown that it is possible to write efficient applications without
324 Chapter 10: Sample Quantum Framework Application
breaking this rule. Moreover, the discipline actually helps to create software products
that are safer, more robust, and easier to maintain.
You can view event queues and event pools as the costs of using active objects.
These data structures, like execution stacks, trade some memory for programming
convenience. You should start application development with oversized queues,
pools, and stacks and shrink them only toward the end of product development. You
can combine basic empirical and analytical techniques for minimizing the size of
event queues and event pools.
When integrating the QF with device drivers and other software components, you
should avoid sharing any non-reentrant or mutex-protected code among active
objects. The best strategy is to localize access to such code in a dedicated active
object.
11
Chapter 11
Conclusion
I would advise students to pay more attention to the fundamental ideas
rather than the latest technology. The technology will be out-of-date
before they graduate. Fundamental ideas never get out of date.
— David Parnas
For many years, I have been looking for a book or a magazine article that describes a
truly practical and reasonably flexible1 way of coding statecharts in a mainstream
programming language such as C or C++. I have never found such a technique.
I believe that this book is the first to provide what has been missing so far — a
flexible, efficient, portable, maintainable, and truly practical implementation of
statecharts that takes full advantage of behavioral inheritance. This book is perhaps
also the first to offer complete C and C++ code for a highly portable statechart-based
framework for the rapid development of embedded, real-time applications.
My vision for this book, however, goes further than an explanation of the code.
By providing concrete implementations of fundamental concepts, such as behavioral
1. I have never been satisfied with the techniques that require explicit coding of transition chains (see Chapter 3)
because it leads to inflexible, hard-to-maintain code and practically defeats the purpose of using statecharts in
the first place.
325
326 Chapter 11: Conclusion
inheritance and active object–based computing, the book lays the groundwork for a
new programming paradigm, which I call Quantum Programming (QP).
This last chapter summarizes the key elements of QP, how it relates to other
trends in programming, and what impact I think it might have in the future.
2. That is, programmers still need to learn the concepts. There is no way around that. However, they can skip
learning a tool.
Key Elements of QP 327
3. As described in Section 2.2.9 in Chapter 2, state diagrams are incomplete without a large amount of textual
information that details the actions and guards.
4. Such models correspond roughly to spike solutions in eXtreme Programming (XP).
328 Chapter 11: Conclusion
4. It lets you modify state machine topology easily at any stage of development. A
correctly structured state machine implementation is often easier to modify than
the corresponding state diagram.
Through support for executable prototypes, QP offers a light-weight alternative
to heavy-weight and high-ceremony CASE tools, for which rapid prototyping has
always been one of the biggest selling points. In fact, QP imitates many good features
of design automation tools. For example, the QF is conceptually similar to the
frameworks found in many such tools. The only significant difference between QP
and CASE tools is that the tools typically use a visual modeling language (e.g.,
UML), whereas QP uses C++ or C directly. In this respect, QP represents the view
that the levels of abstraction available in the conventional programming languages
haven’t yet been exhausted and that you do not have to leave these languages in
order to work directly with higher level concepts, such as hierarchical state machines
and active objects.
through the process7 in a few short weeks, rather than several years — the time it
took me. When you learn one implementation, you practically learn them all because
you understand the concepts. Tools and notations come and go, but truly fundamen-
tal concepts remain.
11.2 Propositions of QP
As I have indicated throughout this book, none of the elements of QP, taken sepa-
rately, are new. Indeed, most of the fundamental ideas have been around for at least
7. If you are a C programmer interested in QP, you might need to go through the exercises exactly in the order I
describe. First, study OOP in C (see Appendix A) and only then study QP in C.
8. Inventing a good metaphor is one of the key practices of eXtreme Programming [Beck 00].
330 Chapter 11: Conclusion
a decade. The contributions of QP are not in inventing new algorithms or new theo-
ries of design (although QP propagates a method of design that is not yet main-
stream); rather, the most important contributions of QP are fresh views on existing
ideas.
Challenging established views is important. An analogy from physics helps illus-
trate the point. Albert Einstein’s [Einstein 1905] famous publication marks the birth
of special relativity, not because he invented new concepts but because he challenged
the established views on the most fundamental ideas, such as time and space. How-
ever, and what is perhaps less well-known, in the very first sentence of his 1905 arti-
cle, Einstein gives his reason for shaking the foundations — the asymmetry between
Newton’s mechanics and Maxwell’s electromagnetism. Yes, the lack of symmetry
was enough for Einstein to question the most established ideas. Ever since, the most
spectacular progress in physics has been connected with symmetries.
In this sense, QP pays special attention to symmetries. The hydrogen atom exam-
ple from Chapter 2 shows how nesting of states arises naturally in quantum systems
and how it always reflects some symmetry of a system. This issue alone requires you
to consider hierarchical states as fundamental, not merely a nicety, as some method-
ologists suggest. QP further observes that behavioral inheritance is the consequence
of another symmetry — this time between hierarchical state machines and class tax-
onomies in OOP. Behavioral inheritance and class inheritance are two facets of the
same fundamental idea of generalization. Both, if used correctly, are subject to the
same universal law of generalization: the Liskov Substitution Principle (LSP) (see
Section 2.2.2 in Chapter 2), which requires that a subclass can be freely substituted
for its superclass.
The deep similarities among quantum physics, QP, and OOP allow me to make
some predictions. The assumption is that QP might follow some of the same devel-
opments that shaped quantum mechanics and OOP.
9. The first OO language was Simula 67, created in Scandinavia in 1967 to aid in solving modeling problems.
10. Some of these languages are characterized as being object based rather than fully object oriented [Booch 94].
Propositions of QP 331
their inception, these ideas have been supported by visual tools, such as Harel’s
[Harel+ 98] Statemate. However, as demonstrated in this book, the concepts are
viable also with nonvisual programming languages.
At this time, behavioral inheritance and an active object–based computing model
are just external add-ons to C++ or C. However, they lend themselves to being
natively supported by a quantum programming language, in the same way that
abstraction, inheritance, and polymorphism are natively supported by OO program-
ming languages.
The rationale for such a language is the usefulness of QP concepts in program-
ming reactive systems and the relatively low complexity of the implementation.
Behavioral inheritance is no more difficult to implement than polymorphism and is
probably easier than implementing multiple inheritance with virtual base classes in
C++. Yet, language-based support for behavioral inheritance offers arguably many
more benefits to programmers, especially to the embedded, real-time software com-
munity.
Integration of QP into a programming language could have many benefits. First, a
compiler could check the consistency and well formedness of state machines, thereby
eliminating many errors at compile time. Second, the compiler could simplify the
state machine interface for the clients (e.g., remove some artificial limitations of the
current QP implementation). Third, the compiler could better optimize the code.
Many possibilities exist for realizing such a quantum language. One option could
be to loosely integrate the QF into a programming language, as with built-in thread
support in Java.
Therefore, the integrated system would not be bigger than the RTOS itself, and my
experience indicates that it would actually be smaller. Third, such an integrated
RTOS would provide a standard software bus11 for building open architectures.
11. Section 7.3.3 in Chapter 7 discusses the concept of the QF as a software bus.
12. For example, SystemC is an emerging standard of C/C++ class libraries that also includes a simulation kernel
that supports hardware modeling concepts (http://www.systemc.org).
An Invitation 333
11.3 An Invitation
This book, and even my speculative propositions, has only barely scratched the
surface of possibilities that the widespread adoption of fundamental concepts
such as behavioral inheritance and active object–based computing can bring.
Just think of the explosion of ideas connected with OOP. QP is based on no less
fundamental ideas and therefore will eventually make a difference in the soft-
ware community.
If you are interested in advancing the QP cause, you can become involved in
many areas.
• Port the QF to new operating systems and platforms, such as Linux,
VxWorks, QNX, eCos, MicroC/OS, and others.
• Provide replacements for conventional RTOSs by tightly integrating the
QF with schedulers.
• Use behavioral inheritance meta-pattern to capture and document new
state patterns precisely.
• Implement QP in languages other than C and C++ — for example, in
Java.
• Explore the possibilities of implementing a quantum programming lan-
guage, perhaps by modifying an open-source C or C++ compiler.
• Publish reusable, active object components.
• And so much more.
I have opened the official QP Web site at http://www.quantum-leaps.com. I
intend this site to contain ports, application notes, links, answers to frequently
asked questions, upgrades to the QF, and more. I also welcome contact regard-
ing QP through the e-mail address on this site.
334 Chapter 11: Conclusion
A
Appendix A
“C+” — Object-Oriented
Programming in C
C makes it easy to shoot yourself in the foot; C++ makes it harder,
but when you do, it blows away your whole leg.
— Bjarne Stroustrup
335
336 Appendix A: “C+” — Object-Oriented Programming in C
A.1 Abstraction
As a C programmer, you already must have used abstract data types (ADTs). For
example, in the standard C run-time library, the family of functions that includes
fopen(), fclose(), fread(), and fwrite() operates on objects of type FILE. The
FILE ADT is encapsulated so that the clients have no need to access the internal
attributes of FILE. (Have you ever looked at what’s inside the FILE structure?) The
1. The original cfront C++ compiler translated C++ into C, which is perhaps the most convincing argument that all
C++ constructs can be implemented in plain C.
2. For example, Borland Turbo Assembler v4.0 [Borland 93] directly supports abstraction, inheritance, and poly-
morphism; therefore, it can be considered an object-oriented language.
3. I’d like to apologize to Marshall S. Wenrich of Software Remodeling Inc. for stealing the “C+” name from him.
Abstraction 337
Access control is the next aspect that Listing A.1 addresses with a coding conven-
tion. In C, you can only indicate your intention for the level of access permitted to a
particular attribute or method. Conveying this intention through the name of an
attribute or a method is better than just expressing it in the form of a comment at the
declaration point. In this way, unintentional access to class members in any portion
of the code is easier to detect (e.g., during a code review). Most object-oriented
designs distinguish the following levels of protection.
• Private — accessible only from within the class
• Protected — accessible only by the class and its subclasses
• Public — accessible to anyone (the default in C)
My convention is to use the double-underscore suffix (foo__) to indicate private
attributes and the single-underscore suffix (foo_, FooDoSomething_()) to indicate
protected members. Public members do not require underscores (foo, FooDoSome-
thing()). Typically, you don’t need to specify private methods in the class interface
(in the .h header file) because you can hide them completely in the class implementa-
tion file (declare them static in the .c implementation file).
Optionally, a class could provide one or more constructors and a destructor for
initialization and cleanup, respectively. Although you might have many ways to
instantiate a class (different constructors taking different arguments), you should
have just one way to destroy an object. Because of the special roles of constructors
and destructors, I consistently use the base names Ctor (FooCtor, FooCtor1) and
Xtor (FooXtor), respectively. The constructors take the me argument when they ini-
tialize preallocated memory, and return pointer to the initialized object when the
attribute structure can be initialized properly, or NULL when the initialization fails.
The destructor takes only the me argument and returns void.
As in C++, you can allocate objects statically, dynamically (on the heap), or auto-
matically (on the stack). However, because of C syntax limitations, you generally
can’t initialize objects at the definition point. For static objects, you can’t invoke a
constructor at all, because function calls aren’t permitted in a static initializer. Auto-
matic objects (objects allocated on the stack) must all be defined at the beginning of a
block (just after the opening brace ‘{’). At this point, you generally do not have
enough initialization information to call the appropriate constructor; therefore, you
often have to divorce object allocation from initialization. Some objects might
require destruction, so it’s a good programming practice to explicitly call destructors
for all objects when they become obsolete or go out of scope. As described in Section
A.3, destructors can be polymorphic.
Inheritance 339
Exercise A.2 Using typedef, define QPseudoState as a pointer to the member func-
tion of class QHsm, taking no arguments (other than me) and returning
void.
Exercise A.3 Using typedef, define another type QState as a pointer to the member
function of class QHsm, taking an immutable pointer to QEvent
(QEvent const *) and returning QPseudoState.
A.2 Inheritance
Inheritance is a mechanism that defines new and more specialized classes in terms of
existing classes. When a child class (subclass) inherits from a parent class (super-
class), the subclass then includes the definitions of all the attributes and methods that
the superclass defines. Usually, the subclass extends the superclass by adding
attributes and methods. Objects that are instances of the subclass contain all data
and can perform all operations defined by both the subclass and its parent classes.
You can implement inheritance in a number of ways in C. The objective is to
embed the parent attributes in the child so that you can invoke the parent’s methods
for the child instances as well (inheritance). One of the techniques is to use the pre-
processor to define class attributes as a macro [Van Sickle 97]. Subclasses invoke this
macro when defining their own attributes as another preprocessor macro. “C+”
implements single inheritance by literally embedding the parent class attribute struc-
ture as the first member of the child class structure. As shown in Figure A.1(c), this
arrangement lets you treat any pointer to the Child class as a pointer to the Parent
340 Appendix A: “C+” — Object-Oriented Programming in C
class. In particular, you can always pass this pointer to any C function that expects a
pointer to the Parent class. (To be strictly correct in C, you should explicitly upcast
this pointer.) Therefore, all methods designed for the parent class are automatically
available to child classes; that is, they are inherited.
Example of Inheritance in C
Seasoned C programmers often intuitively arrive at designs that use inheritance. For
example, in the original µC/OS Real-Time Kernel, Jean Labrosse defines a type
OS_EVENT [Labrosse 92]. This abstraction captures a notion of an operating system
event, such as a semaphore, a mailbox, or a message queue. The µC/OS clients never
deal with OS_EVENT directly, because it is an abstract concept. Such an abstract class
captures the commonality among inter-task synchronization mechanisms and enables
uniform treatment of all operating system events.
The evolution of this concept in subsequent versions of µC/OS is interesting. In the
original version, no OS_EVENT methods exist; the author replicates identical code for
semaphores, mailboxes, and message queues. In MicroC/OS-II [Labrosse 99],
OS_EVENT has been fully factored and is a separate class with a constructor
(OSEventWaitListInit()) and methods (OSEventTaskRdy(), OSEventTask-
Wait(), OSEventTaskTO()). The methods are subsequently reused in all specializa-
tions of OS_EVENT, such as semaphores, mailboxes, and message queues. This reuse
significantly simplifies the code and makes it easier to port to different microprocessor
architectures.
Figure A.1 (a) UML class diagram showing the inheritance relationship between
Child and Parent classes; (b) declaration of Child structure with
embedded Parent as the first member super; (c) memory alignment of
a Child object
This simple approach works only for single inheritance (one-parent classes) because
a class with many parent classes cannot align attributes with all of those parents.
I name the inherited member super to make the inheritance relationship between
classes more explicit (a loan from Java). The super member provides a handle for
accessing the attributes of the superclass. For example, a grandchild class can access
Polymorphism 341
Exercise A.5 Provide definition of the Calc class constructor CalcCtor() and the
destructor CalcXtor(). Hint: don’t forget to explicitly con-
struct/destroy the superclass QHsm.
A.3 Polymorphism
Conveniently, subclasses can refine and redefine methods inherited from their parent
classes. More specifically, a class can override behavior defined by its parent class by
providing a different implementation of one or more inherited methods. For this pro-
cess to work, the association between an object and its methods cannot be estab-
lished at compile time.4 Instead, binding must happen at run time and is therefore
4. Some subclasses might not even exist yet at the time the superclass is compiled.
342 Appendix A: “C+” — Object-Oriented Programming in C
called dynamic binding. Dynamic binding lets you substitute objects with identical
interfaces (objects derived from a common superclass) for each other at run time.
This substitutability is called polymorphism.
Perhaps the best way to appreciate dynamic binding and polymorphism is to look
at some real-life examples. You can find polymorphism in many systems (not neces-
sarily object-oriented) often disguised and called hooks or callbacks.
As the first example, I’ll examine dynamic binding implemented in hardware.
Consider the interrupt vectoring of a typical microprocessor system, an x86-based
PC. The specific hardware (the programmable interrupt controller in the case of the
PC) provides for the run-time association between the interrupt request (IRQ) and
the interrupt service routine (ISR). The IRQ is an asynchronous message sent to the
system by asserting one of the pins, and the ISR is the code executed in response to
an IRQ. Interrupt handling is polymorphic because all IRQs are handled uniformly
in hardware. Concrete PCs (subclasses of the GenericPC class), such as YourPC and
MyPC (Figure A.2), can react quite differently to the same IRQ. For example, IRQ4
can cause YourPC to fetch a byte from COM1 and MyPC to output a byte to LPT2.
«abstract»
GenericPC
IRQ1()
IRQ2()
IRQ3()
...
YourPC MyPC
«abstract»
MS_DOS_DeviceDriver
dhLink
dhAttributes
dhStrategy
dhInterrupt
dhNameOrUnits
«abstract» «abstract»
StrategyRoutine()
CharacterDeviceDriver BlockDeviceDriver
InterruptRoutine()
Figure A.4 Dynamic binding in MS-DOS as implemented by the Int 21h functions
«abstract»
MS_DOS
Int21Function00h()
Int21Function01h()
Int21Function02h()
...
MS_DOS_5.0 MS_DOS_6.22
Object (again, a loaner from Java). The VTABLEs themselves require a separate and
parallel class hierarchy, because the virtual methods, as well as the attributes, need to
be inherited. The root abstract base class for the VTABLE hierarchy is the
ObjectVTABLE class. Listing A.2 provides the “C+” declaration of these two base
classes.
Listing A.2 Declaration of the Object and ObjectVTABLE abstract base classes
1 CLASS(Object)
2 struct ObjectVTABLE *vptr__; /* private vpointer */
3 METHODS
4 /* protected constructor 'inline'... */
5 # define ObjectCtor_(_me_) \
6 ((_me_)->vptr__ = &theObjectVTABLE, (_me_))
7 /* destructor 'inline'... */
8 # define ObjectXtor_(_me_) ((void)0)
9 /* dummy implementation for abstract methods */
10 void ObjectAbstract(void);
11 /* RTTI */
12 # define ObjectIS_KIND_OF(_me_, _class_) \
13 ObjectIsKindOf__((Object*)(_me_), &the##_class_##Class)
14 int ObjectIsKindOf__(Object *me, void *class);
15 END_CLASS
Polymorphism 345
16
17 CLASS(ObjectVTABLE)
19 ObjectVTABLE *super__; /* pointer to superclass' VTABLE */
20 void (*xtor)(Object *); /* public virtual destructor */
21 METHODS
22 END_CLASS
23
24 extern ObjectVTABLE theObjectVTABLE; /* Object class VTABLE */
The Object class is declared in Listing A.2 (lines 1–15). Its only attribute is the
private virtual pointer vptr__ (line 2). The Object class is abstract, which means
that it is not intended to be instantiated (only inherited from) and therefore protects
its constructor ObjectCtor_() and destructor ObjectXtor_(). Other facilities
supplied by the Object class include a dummy implementation ObjectAbstract()
(line 10), to be used for abstract (pure virtual) methods, and a simple run-time type
identification (RTTI), defined as the ObjectIS_KIND_OF() macro (lines 12, 13).
The purpose of the ObjectVTABLE class (Listing A.2, lines 17–22) is to provide
an abstract base class for the derivation of VTABLEs. The first private attribute
super__ (line 19) is a pointer to the VTABLE of the superclass. You can identify it
with the generalization arrow pointing from the subclass to the superclass in the
UML class diagram.5 The second attribute (line 20) is the virtual destructor, which
subsequently is inherited by all subclasses of ObjectVTABLE. Consistent with the
“C+” convention, it is defined as a pointer to a function that takes only the me
pointer and returns void. Although there can be many instances of the attribute
class, there should be exactly one instance of the VTABLE for any given class — a
VTABLE singleton.6 This sole instance for any given class Class is called the
ClassVTABLE. The VTABLE instance for the Object class (theObjectVTABLE) is
declared in line 24.
The hierarchies of the attribute classes (rooted in the Object class) and VTABLEs
(rooted in the ObjectVTABLE class) must be exactly parallel. The following macro
SUBCLASS() encapsulates the construction of a subclass (see Exercise A.5).
#define SUBCLASS(class_, superclass_) \
CLASS(class_) \
superclass_ super_;
5. That is why the arrow denoting inheritance points from the subclass to the superclass.
6. Here, I only use the name of the Singleton design pattern [Gamma+ 95] to denote a class with a single instance
(lowercase singleton), not necessarily to apply the pattern strictly.
346 Appendix A: “C+” — Object-Oriented Programming in C
Similarly, building the VTABLE hierarchy and declaring the VTABLE singletons can
be encapsulated in the VTABLE() macro.
#define VTABLE(class_, superclass_) }; \
typedef struct class_##VTABLE class_##VTABLE; \
extern class_##VTABLE the##class_##VTABLE; \
struct class_##VTABLE { \
superclass_##VTABLE super_;
VTABLE singletons, as with all other objects, need to be initialized through their
own constructors, which the preprocessor macros can automatically generate. The
body of the VTABLE constructor can be broken into two parts: (1) copying the
inherited VTABLE and (2) initializing or overriding the chosen function pointers.
The first step is generated automatically by the macro BEGIN_VTABLE().
compiler doesn't know that QHsm is related to Object by inheritance and treats these
types as completely different.
If you don’t want to provide the implementation for a given method because it is
intended to be abstract (a pure virtual in C++), you should still initialize the corre-
sponding function pointer with the ObjectAbstract() dummy implementation. An
attempt to execute ObjectAbstract() aborts the execution through a failed asser-
tion, which helps detect unimplemented abstract methods at run time. The definition
of every VTABLE constructor opened with BEGIN_VTABLE() must be closed with the
following END_VTABLE macro:
#define END_VTABLE\
((ObjectVTABLE*)me)->super__ = ((Object*)t)->vptr__; \
return (ObjectVTABLE *)me; \
}
The attribute and virtual method class hierarchies mostly grow independently.
However, they are coupled by the VPTR attribute, which needs to be initialized to
point to the appropriate VTABLE singleton, as shown in Figure A.5. The appropriate
place to set up this pointer is, of course, the attribute constructor. You must set up
this pointer after the superclass constructor call because the constructor sets the
VPTR to point to the VTABLE of the superclass. If the VTABLE for the object under
construction is not yet initialized, the VTABLE constructor should be called. These
two steps are accomplished by invoking the VHOOK() macro.
1 #define VHOOK(class_) \
2 if (((ObjectVTABLE *)&the##class_##VTABLE)->super__== 0) \
3 ((Object *)me)->vptr__ = class_##VTABLECtor(me); \
4 else \
5 ((Object *)me)->vptr__ = \
6 (ObjectVTABLE *)&the##class_##VTABLE
To determine whether the VTABLE has been initialized, the macro VHOOK()
checks the super__ attribute (Listing A.4, line 2). If the attribute is NULL (value
implicitly set up by the guaranteed in C static pointer initialization), then the VTABLE
constructor must be invoked (line 3) before setting up the VPTR; otherwise, just the
VPTR must be set up (lines 5-6). Note that because VHOOK() is invoked after the
superclass constructor, the VTABLE of the superclass is already initialized by the
same mechanism applied recursively, so the whole class hierarchy is initialized prop-
erly.
348 Appendix A: “C+” — Object-Oriented Programming in C
Finally, after all the setup work is done, you are ready to use dynamic binding.
For the virtual destructor (defined in the Object class), the polymorphic call takes
the form
(*obj->vptr__->xtor)(obj);
where obj is assumed to be of Object* type. Note that the obj pointer is used in
this example twice: once for resolving the method and once as the me argument.
In the general case, you deal with Object subclasses rather than the Object class
directly. Therefore you have to upcast the object pointer (on type Object*) and
downcast the virtual pointer vptr__ (on the specific VTABLE type) to find the func-
tion pointer. These operations, as well as double-object pointer referencing, are
encapsulated in the macros VPTR(), VCALL(), and END_CALL.
#define VPTR(class_, obj_) \
((class_##VTABLE *)(((Object *)(obj_))->vptr__))
#define VCALL(class_, meth_, obj_) \
(*VPTR(class_, _obj_)->meth_)((class_*)(obj_)
#define END_CALL )
The virtual destructor call on behalf of object foo of any subclass of class Object
takes the following form.
VCALL(Object, xtor, foo)
END_CALL;
If a virtual function takes arguments other than me, they should be sandwiched
between the VCALL() and END_CALL macros. The virtual function can also return a
result. For example, in
result = VCALL(Foo, computeSomething, obj), 2, 3,
END_CALL;
obj points to a Foo class or any subclass of Foo, and the virtual function compute-
Something() is defined in FooVTABLE. Note the use of the comma after VCALL().
As you can see, dynamic binding requires only one more assembly instruction
than static binding. Additionally, you need to dereference VPTR (the me pointer
is already in the ebx register) and place the address of the VTABLE into the eax
register. The call also requires one more memory access to fetch the address of
the appropriate function from the VTABLE.
350 Appendix A: “C+” — Object-Oriented Programming in C
To complete the picture, now consider the virtual call overhead on a RISC
(reduced instruction set computing) architecture using an ARM 7 instruction set.
; static binding: ShapeXtor(c)
mov a1,v1 ; move “me” (in v1) into a1 (argument1)
bl _ShapeXtor ; static call (branch with link)
In this case, the static call is extremely fast, with only two instructions, and does not
involve any data accesses, thanks to the ARM branch-and-link instruction bl.
Unfortunately, the dynamic call cannot take advantage of the bl instruction because
the address cannot be statically embedded in the bl opcode; therefore, an additional
instruction that saves the return address into the link register (lr) is necessary. Oth-
erwise, dynamic binding overhead is very similar to that of the CISC processor and
involves two additional data accesses (the two highlighted ldr instructions) to deref-
erence the VPTR and to dereference the function pointer.
A.5 Summary
OOP is a design method rather than the use of a particular language or tool. Indeed,
as David Parnas [Brooks 95] writes:
Instead of teaching people that OO is a type of design, and giving them design
principles, people have taught that OO is the use of a particular tool. We can write
good or bad programs with any tool. Unless we teach people how to design, the
languages matter very little.
OO languages support OO design directly, but you can also successfully imple-
ment OO design in other languages, such as C. Abstraction, inheritance, and poly-
morphism are nothing but design meta-patterns at the C level. Many C
programmers, very likely you as well, have been using these fundamental patterns in
some form or another for years, often without clearly realizing it. As with all design
patterns, the three patterns combined allow you to work at a higher (OO) level of
abstraction by introducing their specific naming conventions and idioms.
In this appendix, you learned “C+,” which is one specific set of such C conven-
tions and idioms that achieves performance and maintainability of code comparable
Guide to Notation
The good thing about bubbles and arrows, as opposed to
programs, is that they never crash.
— Bertrand Meyer
In this appendix, I describe the graphical notation that I use throughout the book. 1
The notation should be compatible with version 1.4 of the UML specification 2
[OMG 01]. The timing diagrams are not part of the UML. I adapted them from
Douglass [Doublass 99].
1. In this appendix, I do not include the informal data structure diagrams that show particular C or C++ data
structures at run time.
2. I prepared all diagrams with Visio ™ Technical v4.0. The accompanying CD-ROM contains the Visio stencil
that I used.
353
354 Appendix B: Guide to Notation
• A class is always denoted by a box with the class name in bold type at the top.
Optionally, just below the name, a class box can have an attribute compartment
that is separated from the name by a horizontal line. Below the attributes, a class
box can have an optional method compartment.
• The UML notation allows you to distinguish abstract classes, which are classes
intended only for derivation and cannot have direct instances. Figure B.1c shows
the notation for such classes. The abstract class name appears in italic font.
Optionally you may use the «abstract» stereotype. If a class has abstract methods
(pure virtual member functions in C++), they are shown in an italic font as well.
• Sometimes it is helpful to provide pseudocode of some methods by means of a
note (Figure B.1c).
• Finally, a class box can also show the visibility of attributes and methods, as
in Figure B.1d.
Figure B.2 Different presentation options for the generalization and specialization
of classes
Subclass1
«abstract» attribute Superclass
Superclass
attribute :
attribute DataType other
... ... potential
method() subclasses
...
Superclass
Figure B.2 shows the different presentation options for inheritance (the is-a-kind-
of relationship). The generalization arrow always points from the subclass to the
superclass. The right-hand side of Figure B.2 shows an inheritance tree that indicates
an open-ended number of subclasses.
Class Diagrams 355
WholeClass
part1 1..*
part1 : Part1Class Part1Class
part2 : Part2Class*
...
composite aggregation
0..1 unidirectional navigability
0..*
aggregation
Part2Class bidirectional navigability
Calc QHsmTst
event
state parameters actions sent
stateName trigger guard events
entry/ action1() EVT (a, b) [guard()] / action(), ^EVT_A
exit/ x=3 stateA stateB
EVT(a, b)[guard()]/
action2() transition
...
Figure B.5 shows the presentation options for states and the notation for a state
transition. A state is always denoted by a rectangle with rounded corners. The name
of the state appears in bold type at the top. Optionally, right below the name, a
state can have an internal transition compartment separated from the name by a
horizontal line. The internal transition compartment can contain entry actions
(actions following the reserved symbol entry), exit actions (actions following the
reserved symbol exit), and other internal transitions (e.g., those triggered by EVT
in Figure B.5).
A state transition is represented as an arrow originating at the boundary of the
source state and pointing to the boundary of the target state. At a minimum, a tran-
sition must be labeled with the triggering event. Optionally, the trigger can be fol-
lowed by event parameters, a guard, a list of actions, and a list of events that have
been sent.
Figure B.6 Composite state, initial transitions, and the final state
compositeStateName superstate
initial entry/ action1()
A
transition exit/ x=3
EVT/ stateC stateA
final state substate
entry/
C
STOP stateB C
B
Figure B.6 shows a composite state (superstate) that contains other states (sub-
states). Each composite state can have a separate initial transition to designate the
initial substate. Although Figure B.6 shows only one level of nesting, the substates
can be composite as well.
Sequence Diagrams 357
Figure B.7 shows composite stateA with the orthogonal regions (and-states) sepa-
rated by a dashed line and two pseudostates: the dynamic choice point and deep his-
tory.
objects
thinking thinking
changes
TIMEOUT of state
hungry HUNGRY(n)
events
EAT(n)
eating
TIMEOUT focus
of control
hungry
HUNGRY(m)
TIMEOUT
thinking
DONE(n)
eating EAT(m)
running
T3
running
malloc() done done
T2
ready event
blocked
running
T1
ready done
malloc() demotion
blocked
(promotion)
0 5 10 15 20 time
C
Appendix C
CD-ROM
In a double-speed CD-ROM, the pits go flying by the focused spot at a
rate of 1,200,000 pits per second. As they do, the servo system adjusts
the tracking and the focus of the laser beam so that it remains within
about ±0.1µm of the track center and within about ±2µm of the correct
focus position even though the CD-ROM disc may be wobbling from
side-to-side by ±70µm (44 track diameters) and up-and-down by
±600µm as it rotates. The performance of this opto-electronic system is
truly remarkable.
— Murray Sargent III and Richard L. Shoemaker
The Personal Computer from the Inside Out
The companion CD-ROM contains all the source code and executable images men-
tioned in the book, including several ports of the Quantum Framework (QF). The disc
also includes answers to the exercises scattered throughout the book, the Evaluation
Version of On Time RTOS-32 v4.0, Visio™ stencils used to create the diagrams in this
book, and several references in Adobe Portable Document Format (PDF). 1
1. A copy of Adobe Acrobat Reader™ is included on the CD-ROM for your convenience.
359
360 Appendix C: CD-ROM
The CD-ROM is designed for maximum usefulness, even without installing any
of it on your hard drive. In particular, you can browse the source code, execute
examples, and read PDF documents directly from the CD.
The disc comes with an HTML-based index page, index.htm, in its root direc-
tory (Figure C.1). It is automatically activated if the CD autoinsert notification is
enabled on your system. You need a Web browser (e.g., Microsoft Internet Explorer
or Netscape Navigator) to view the index.
C.2 Installation
The Installation HTML page on the CD contains links to three self-extracting instal-
lation programs, which you can install by simply clicking on the links under
Microsoft Windows.
On Microsoft Windows NT/2000, you need to define the environment variable and
the path in the corresponding System dialog box.
C.4 Resources
The CD-ROM contains several of the references (listed in the Bibliography) in PDF.
Among others, the documents include the OMG UML Specification v1.4 [OMG 01]
and the On Time RTOS-32 Manual [OnTime 01].
For your convenience, the CD also contains the Visio™ stencil and template that I
used to draw all the diagrams in this book. One of the main goals of QP is to provide
a light-weight alternative to heavy-weight and expensive design automation tools.
Such tools typically come with a drawing package to create various diagrams. In
fact, most of the tools, if they are used at all, end up as overpriced drawing packages.
To this end, a good drawing program does as much for you as a fancy CASE tool.
As described on the Resources page, you incorporate the stencil into Visio by
copying <CD>:\Resources\Visio\Software-UML.vss to your Visio\Stencils
directory and <CD>:\Resources\Visio\Software-UML.vst to your Visio\Tem-
plate directory.
I have tried both the stencil and the template with the newer version of Visio
(Visio 2000), and they seem to work correctly.
Resources 363
Barr, Michael. 1999. Programming Embedded Systems in C and C++. O'Reilly &
Associates.
Brooks, Frederick P. 1987. “No Silver Bullet: Essence and Accidents of Software
Engineering.” Computer, April, 10-19.
Brooks, Frederick. 1995. The mythical man-month. Anniversary ed. Addison Wesley.
Cargill, Tom. 1994. “Exception Handling: A False Sense of Security.” C++ Report,
November-December.
365
366 Bibliography
Douglass, Bruce Powell. 1998, State machines and state charts. Pts. 1 and 2.
Proceedings of the Embedded Systems Conference, Spring. Chicago.
Douglass, Bruce Powell. 1999. Doing hard time, developing real-time systems with
UML, objects, frameworks, and patterns. Addison Wesley.
Douglass, Bruce Powel. 1999. Real-Time UML, Second Edition: Developing Efficient
Objects for Embedded Systems, Addison-Wesley.
Duby, Carolyn. 2001. Class 203: Implementing UML statechart diagrams. Proceedings
of Embedded Systems Conference, Fall. San Francisco.
Einstein, Albert. 1905. Zur Elektrodynamik bewegter Körper. Annalen der Physik und
Chemie. IV Folge, Band 17, 891–921.
Englebart, D., and W. English. 1968. A research center for augmented human intellect.
AFIPS Conference Proceedings, Fall Joint Computer Conference, 9–11 December,
395–410. San Francisco.
Fowler, Martin, Kent Beck, John Brant, William Opdyke, Don Roberts. 1999.
Refactoring: Improving the Design of Existing Code, Addison-Wesley.
Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. 1995. Design
patterns, elements of reusable object-oriented software. Addison Wesley.
Halzen, Francis, Alan D. Martin. 1984. Quarks and Leptons: An Introductory Course
in Modern Particle Physics, John Wiley & Sons.
Harel, David. 1987. Statecharts: A visual formalism for complex systems. Science of
Computer Programming (no. 8), 231–274.
Harel, David, and Michal Politi. 1998. Modeling Reactive Systems with Statecharts,
The STATEMATE Approach. McGraw-Hill.
Hewitt, Carl, P. Bishop, and R. Steiger. 1973. “A universal, modular actor formalism for
artificial intelligence”, 3rd International Joint Conference on Artificial
Intelligence, 235–245.
Horstmann, Cay S. 1995. Mastering Object-Oriented Design in C++, John Wiley &
Sons.
Labrosse, Jean J., MicroC/OS-II, The Real-Time Kernel. CMP Books, 1999,
ISBN 0-87930-543-6
Leveson, Nancy, and Clark S. Turner. An investigation of the Therac-25 accidents. IEEE
Computer, July.
Lippman, Stanley. 1996. Inside the C++ object model. Addison Wesley.
Merritt, Rick. 2002. Software model needs overhaul. EE Times, 2 January (no. 1199),
5. (www.eet.com)
Meyer, Bertrand. 1997. Object-oriented software construction. 2nd ed. Prentice Hall.
Meyer, Bertrand. 1997. "UML: The Positive Spin", Cutter IT Journal,Volume X, No.
3 (former Ed Yourdon's American Programmer).
Meyer, Bertrand. 1997. Letters from readers (response to the article “Put it in the
contract: The lessons of Ariane” by Jean-Marc Jézéquel, and Bertrand Meyer), IEEE
Computer, Vol. 30, No. 2, February, 8–9, 11.
Murphy, Niall. 2001b. Assert yourself. Embedded Systems Programming, May, 27–32.
Parnas, David L. 1976. On the design and development of program families. IEEE
Transactions On Software Engineering (SE-2, 1), March, 1–9.
Petzold, Charles. 1996. Programming Windows 95, The Definite Developer’s Guide
to the Windows 95 API. Microsoft Press, 1996.
Schlaer, S., Steve Mellor. 1991. Object Lifecycles: Modeling The World in States.
Yourdon Press.
Sha, L., R. Rajkumar, J.P. Lehocsky. 1990. Priority Inheritance Protocols: An Approach
to Real-Time Synchronization. IEEE Transactions on Computers, September,
1,175-1,185.
Selic, Bran. 2000. Distributed Software Design: Challenges and Solutions. Embedded
Systems Programming, November, 127-144.
370 Bibliography
Selic, Bran, Garth Gullekson, Paul T. Ward. 1994. Real-Time Object Oriented
Modeling. John Wiley & Sons.
Stroustrup, Bjarne. 1991. The C++ Programming Language Second Edition. AT&T
Bell Telephone Laboratories, Inc.
WindRiver Systems, Inc. GNU Toolkit User’s Guide, 2.6, October 1995, Part#:
DOC-11091-ZD-01
Yourdon, E. 1994. Decline and Fall of the American Programmer. Prentice Hall PTR;
November 10.
Index
Symbols heuristics for 317
rules for programming 315
µC/OS 254, 267, 340
activity graphs 42
actor 190–191
A See also active object
abstract class 340, 342, 344 ad hoc approach 103, 196, 290
UML notation for 354 additive complexity 35
abstract method 137, 347–348 Adobe Acrobat Reader 362
UML notation for 354 ADT (abstract data types) 336
Abstract Operating System 208 AECL (Atomic Energy of Canada Limited) 196
abstraction 105, 336 aggregate 63, 86–87
and LSP 34 aggregation 151, 355
as meta-pattern 21, 81, 120, 333, 335– UML notation for 355
339 agile methodologies xii
inheritance 32, 105 Agile Modeling xii
levels of 5, 15, 17, 131, 165, 328 ALLEGE() macro 222
state hierarchy 51, 184 Amber, Scott xii
access control 338 and-decomposition of states 34, 159
private 338 See also orthogonal regions
protected 338 angular momentum 49, 51–52
public 338 conservation of 50
See also visibility of class members ANSI C 121
action 27–30, 41, 43, 49, 53, 55, 59, 67, 224, application
253, 327 framework 190
coding transitions 98–99 shutdown 255
sequence of 38 applicationintentionally incomplete 264
UML notation for 356 architectural decay 27, 196–197
undefined syntax of 41 and guards 26
See also entry/exit action ARM (Advanced RISC Machines) 350
See also internal transition ARM7TDMI 230
action listener 240 See also Atmel, AT91 microcontroller
active object 20, 190–191, 197, 248–253 arming a timer 256
as Observer 243 ASSERT() macro 106–107, 222
priority 241 assertions 220
active object computing 191, 315 asynchronous event
and programming discipline 316 dispatching 151
flexibility of 315 exchange 20, 198
Atmel Corporation
AT91 microcontroller family 229–230
371
372 Index
fine granularity 267 guard 26, 41, 43, 48, 50, 76, 102–104, 154,
finite state machine 159, 183–185, 193, 212, 311, 327
See FSM and architectural decay 26
flowchart 42, 76 implementation of 76, 99
vs. statechart 41 sequence of evaluation 43
focus of control UML notation for 40, 356
UML notation for 357 GUI (graphical user interface) 3
foreground processing 283–284
fork pseudostate
See pseudostate, fork H
fragmentation hard real-time 209
lengthy processing 143 Harel statecharts 24
of heap 226 Harel, David x, 4, 24, 41–42
framework extension points 212 has a (has-a-component) relationship 248
free() 225, 231 heap
FSM (finite state machine) 24 fixed-block-size 237
and repetitions 32 infinite 228
Optimal 69, 83, 88 heap problems 225–228
vs. HSM (hierarchical state machine) 155 block allocation overheads 226
See also state machine fragmentation 226, 228
fudge factor 320 heap as a shared resource 226
functor 181 massively oversizing heap 226
nondeterminism 226
priority inversion 226
G sharing 226
Gamma, Erich 18, 33 Heisenberg uncertainty principle 51
garbage collection 227, 236 Hejlsberg, Anders xi–xii
generalization 354 heuristics for active object–based systems 318
arrow 345, 354 Hewitt, Carl 191
general-purpose computer hierarchical states 4, 31–32
vs. embedded system 216 semantics of 5
global namespace 258 symmetries 32
GNU gcc high-water mark 279
-fvtable-thunks option 175 history
GPS receiver x, 207–209, 242 clearing 164
graceful shutdown 255 See also pseudostate
graphical notation 353 history mechanism 39, 127
guaranteed cleanup 92, 148 Horrocks, Ian 6, 78
and class destructor 36 host machine 301
and exit action 36 housekeeping code vii, 15, 43, 211–212
guaranteed event delivery 210, 245 HSM (hierarchical state machine) 31–32, 155
guaranteed initialization 92, 148 and opportunity for reuse 32
and class constructor 36 malformed 126
and entry action 36 See also QHSM class
Index 377
V W
variabilities WaitForSingleObject() 292
in frameworks 266 watchdog timer 256
See also variables, automatic well formedness 331
variables Win32 290–295, 314
automatic 250, 322 API 12, 193
vcall thunk 174 console application 295
VCALL() macro 348 Windows xiv, 83, 132, 136
VHOOK() macro 347 9x 292
virtual bosons 201 and QF 266
virtual destructor 345 as reactive system 12
virtual memory 227 CE 217
virtual particle 200, 235 NT 292
virtual photon 200, 235 WindRiver Systems 252
virtual pointer 177, 343 BetterState 44
virtual state handler 75, 171 VxWorks 230, 232, 252
virtual table 175, 177, 343 WinMain() 12, 56, 86, 136, 295
visibility of class members 354 WM_COMMAND 12, 104
Visio™ stencil 362 WM_TIMER 295
Visual Basic Calculator 196 worst-case pool utilization 275
visual modeling language 328 worst-case queue utilization 279
visual programming vii, 41
visual tools 211, 326
visualSTATE engine 190 X
VMETHOD() macro 346 XP (eXtreme Programming) xii, 57, 206, 327,
VPTR (virtual pointer) 121, 343 329
VPTR() macro 178, 348
VTABLE 121–122, 125, 178, 343
VTABLE() macro 346
Z
VxWorks Zeeman effect 49
See WindRiver, VxWorks zero overhead principle 82
What’s on the CD?
The companion CD-ROM contains all the source code and executable images mentioned
in the book, including several ports of the Quantum Framework (QF). The disc also
includes answers to the exercises scattered throughout the book, the Evaluation Version of
On Time RTOS-32 v4.0, Visio™ stencils used to create the diagrams in this book, and sev-
eral references in Adobe Portable Document Format (PDF).1
The CD-ROM is designed for maximum usefulness, even without installing any of it
on your hard drive. In particular, you can browse the source code, execute examples, and
read PDF documents directly from the CD.
The disc comes with an HTML-based index page, index.htm, in its root directory. It
is automatically activated if the CD autoinsert notification is enabled on your system.
You need a Web browser (e.g., Microsoft Internet Explorer or Netscape Navigator) to
view the index.
For more information on the source code structure, installation, answers to exercises,
or other CD resources, see Appendix C, beginning on page 359.
1. A copy of Adobe Acrobat Reader™ is included on the CD-ROM for your convenience.