An RTOS (Real-Time Operating System) is
the most universally accepted way of designing and implementing embedded
software. It is the most sought-after component of any system that outgrows the
venerable "Super loop". But it is also the design strategy that
implies a certain programming paradigm, which leads to particularly brittle
designs that often work only by chance. That paradigm is sequential programming
based on blocking.
Blocking occurs any time you wait explicitly in line
for something to happen. All RTOS provides an assortment of blocking
mechanisms, such as time-delays, semaphores, event flags, mailboxes, message
queues, and so on. Every RTOS task, structured as an endless loop, must use at
least one such blocking mechanism, or else it will take all the CPU cycles.
Typically, however, tasks block not in just one place in the endless loop, but
in many places scattered throughout various functions called from the task
routine. For example, in one part of the loop, a task can block and wait for a
semaphore that indicates the end of an ADC conversion. In other parts of the
loop, the same task might wait for an event flag indicating a button press, and
so on.
Problem with Blocking:
This excessive
blocking is insidious because it appears to work initially, but almost always
degenerates into an unmanageable mess. The problem is that while a task is
blocked, the task is not doing any other work and is not responsive to other
events. Such a task cannot be easily extended to handle new events, not just
because the system is unresponsive, but mostly since the whole structure of the
code past the blocking call is designed to handle only the event that it was
explicitly waiting for.
You might think
that the difficulty of adding new features (events and behaviors) to such
designs is only important later when the original software is maintained or
reused for the next similar project. I disagree. Flexibility is vital from day
one. Any application of nontrivial complexity is developed over time by
gradually adding new events and behaviors. The inflexibility makes it
exponentially harder to grow and elaborate an application, so the design
quickly degenerates in the process known as architectural decay.
The mechanisms of architectural decay of RTOS-based
applications are manifold, but perhaps the worst is the unnecessary
proliferation of tasks. Designers, unable to add new events to unresponsive
tasks are forced to create new tasks, regardless of coupling and cohesion.
Often the new feature uses the same data and resources as an already existing
feature (such features are called cohesive). But unresponsiveness forces you to
add the new feature in a new task, which requires caution with sharing the
common data. So mutexes and other such blocking mechanisms
must be applied and the vicious cycle tightens. The designer ends up spending
most of the time not on the feature at hand, but on managing subtle,
intermittent, unintended side-effects.
What is the solution then?
We have to have a
paradigm shift from sequential programming to event-driven. To tackle
these problems experienced software developers avoid blocking as much
as possible. Instead, they use the Active Object design pattern. They structure
their tasks in a particular way, as “message pumps”, with just one blocking
call at the top of the task loop, which waits generically for all
events that can flow to this particular task. Then, after this blocking call,
the code checks which event arrived and based on the type of the event the
appropriate event handler is called. The pivotal point is that these event
handlers are not allowed to block, but must quickly return to
the “message pump”. This is, of course, the event-driven paradigm applied on
top of a traditional RTOS.
While you can implement Active
Objects manually on top of a conventional RTOS, an even better way is to
implement this pattern as a software framework, because a framework is the best-known
method to capture and reuse a software architecture. You can already see how
such a framework already starts to emerge, because the “message pump” structure
is identical for all tasks, so it can become part of the framework rather than
being repeated in every application.
This also illustrates the most important characteristics of a framework called inversion of control. When you use an RTOS, you write the main body of each task and you call the code from the RTOS, such as delay(). In contrast, when you use a framework, you reuse the architecture, such as the “message pump” here, and write the code that it calls. The inversion of control is very characteristic of all event-driven systems. It is the main reason for the architectural reuse and enforcement of the best practices, as opposed to re-inventing them for each project at hand.
But there is more, much more to the Active Object framework. For example, a framework like this can also provide support for state machines (or better yet, hierarchical state machines), with which to implement the internal behavior of active objects. This is exactly how you are supposed to model the behavior in the UML (Unified Modeling Language).
As it turns out, active objects provide a sufficiently high level of abstraction and the right level of abstraction to effectively apply to the model. This is in contrast to a traditional RTOS, which does not provide the right abstractions. You will not find threads, semaphores, or time delays in the standard UML. But you will find active objects, events, and hierarchical state machines.
An AO framework and a modeling tool beautifully complement each other. The framework benefits from a modeling tool to take full advantage of the very expressive graphical notation of state machines, which are the most constructive part of the UML. The modeling tool benefits from the well-defined “framework extension points” designed for customizing the framework into applications, which in turn provide well-defined rules for generating code.
In summary, RTOS and super loop aren’t the only game in town. Actor frameworks, such as Akka, are becoming all the rage in enterprise computing, but active object frameworks are an even better fit for deeply embedded programming.


Comments
Post a Comment