Designing Real-time Software
Designing Realtime software involves several steps. The basic steps are
listed below:
This is the first stage of Realtime Software design. Here the software team
understands the system that is being designed. The team also reviews at the
proposed hardware architecture and develops a very basic software architecture.
This architecture definition will be further refined in Co-Design.
Use Cases are also used in this stage to analyze the system. Use cases are
used to understand the interactions between the system and its users. For
example, use cases for a telephone exchange would specify the interactions
between the telephone exchange, its subscribers and the operators which maintain
the exchange.
Once the software architecture has been defined, the hardware and
software teams should work together to associate software functionality to
hardware modules. The software handling is partitioned between different
processors and other hardware resources with the following key considerations:
- The software functionality should be partitioned in such a fashion that
processors and links in the system do not get overloaded when the system is
operating at peak capacity. This involves simulating the system with the
proposed software and hardware architecture.
- The system should be designed for future growth by considering a scalable
architecture, i.e. system capacity can be increased by adding new hardware
modules. The system will not scale very well if some hardware or software
module becomes a bottleneck in increasing system capacity. For example,
Xenon scalability will be limited if the CAS processor in the system is
assigned a lot of work. As this processor is shared, the software running on
the CAS card would become a scalability bottleneck.
- Software modules that interact very closely with each other should be
placed on the same processor, this will reduce delays in the system. Higher
system performance can be achieved by this approach as inter-processor
message communication taxes the CPU as well as link resources.
This stage is sometimes referred to as Co-Design as the hardware and software
teams work together to define the final system architecture. This is an
iterative process. Changes in system architecture might result in changes in
hardware and/or software architecture.
The next step in Realtime system design is the careful
analysis of the system to define the software modules.
- Determine all the features that the system needs to support.
- Group the various features based on the type of work they perform.
Identify various sub-systems by assigning one subsystem for one type of
features. For example, for the Xenon switch the groups would be Call Handling, System Maintenance, Operator
Interface etc.
- Identify the tasks that will implement the software features. Clearly
define the role of each task in its subsystem.
- Within each subsystem, classify and group the features appropriately and
associate the various tasks constituting the subsystem. For example, the
Call Handling subsystem in Xenon would support features like:
- V5.2 Originating to ISUP Outgoing Call
- V5.2 Originating to V5.2 Terminating Call
- Conference Call
- Toll free call
A typical Realtime system is composed of various task entities distributed
across different processors and all the inter-processor communication takes
place mainly through messages. Feature Design defines the software features in terms of message interactions
between tasks. This involves detailed specification of message interfaces. The
feature design is generally carried out in the following steps:
- Specify the message interactions between different tasks in the system
- Identify the tasks that would be controlling the feature. The controlling
tasks would be keeping track of progress of feature. Generally this is
achieved by running timers.
- The message interfaces are defined in detail. All the fields and their possible
values are identified.
Feature Design Guidelines
- Keep the design simple and provide a clear definition of the system
composition.
- Do not involve too many tasks in a feature.
- Disintegrate big and complex features into small sub features.
- Draw message sequence charts for a feature carefully. Classify the legs of
a scenario for a feature in such a way that similar message exchanges are
performed by taking the common leg.
- Provide a clear and complete definition of each message interface.
- To check possible message loss, design timer based message exchanges.
- Always consider recovery and rollback cases at each stage of feature
design. One way of doing this is to keep a timer for each feature at the
task that controls the mainline activity of the feature. And then insert the
timeout leg in the message sequence charts of the feature.
- To avoid overloading of message links choose design alternatives that
include fewer message exchanges.
Designing a task requires that all the interfaces that the task needs to
support should be very well defined. Make sure all the message parameters and
timer values have been finalized.
Selecting the Task Type
Once the external interfaces are frozen, select the type of task/tasks that
would be most appropriate to handle the interfaces:
- Single State Machine: The tasks
functionality can be implemented in a single state machine. The V5.2
Call task in Xenon is a good example of a task of this type.
- Multiple State Machines: The task
manages multiple state machines. Such tasks would typically include a
dispatcher to distribute the received messages to an appropriate state
machine. Such tasks would create and delete state machine objects as and
when required. The E1
Manger in Xenon exemplifies such a task.
- Multiple Tasks: This type of tasks are
similar to the multiple state machine tasks discussed above. The main
difference is that the task now manages multiple tasks. Each of the managed
tasks implements a single state machine. The manager task is also
responsible for creating and deleting the single state machine tasks. The V5.2
Manager task is a good example of a task of this category.
- Complex Task: This type of task would
be required in really complex scenarios. Here the manager task manages other
tasks which might be managing multiple state machines.
Selecting the State Machine Design
After choosing the type of task, the designer should consider dividing the
message interface handling into a sequence of state transitions. Two different
type of state machines can be supported:
- Flat State Machines: This is the most
frequently used type of state transition. For example, the states of a call would be "Awaiting Digits", "Awaiting Connect",
"Awaiting Release", "Awaiting On-hook" etc. This type of
state division does not scale very well with increasing complexity. For a
complex system this technique results in a state explosion, with the task
requiring hundreds of states.
- Hierarchical State Machines: Here the
states are viewed as a hierarchy. For example the states covered above would
map to "Originating : Awaiting Digits", "Originating:
Awaiting Connect", "Releasing : Awaiting Release",
"Releasing : Awaiting On-hook". The total number of states is the
same here, but the main difference is that some states inherit from an
"Originating" base state and others inherit from
"Releasing" base state. The total number of message handlers in
each state would be reduced drastically, as all the messages that have
common handling in all the originating states would be just handled in the
"Originating" base state. In addition to this, the handlers of
inheriting states can further refine the base state handling by taking
additional actions.
Task Design Guidelines
- Do not complicate the design by introducing too many states. Such designs
are very difficult to understand. Follow a simple rule of thumb, if you are
having difficulty choosing the name of state, you may have identified the
wrong state.
- Do not complicate the design by having too few states. If all the states
in the system have not been captured in the state machine design, you will
end up with lot of flags and strange looking variables which will be needed
to control the message flow in the jumbo states.
- Keep the data-structure definitions simple. See if a simpler
data-structure would do the same job just as well.
- Out of memory conditions should be handled. Tasks can and will run out of
memory. So handle the out of memory conditions gracefully. This can lead to
a lot of "if clutter" so consider exception handling as an option.
- All of the legs of the defined scenarios should be handled. This is
easier said than done. Many times all the scenario legs identified in the
feature design stage may not cover all the possible error scenarios for your
task.
- Make sure that all the allocated resources are de-allocated at the end.
Again it is very easy to miss out on this one. Many times designers forget
to release resources in the error legs.
- Consider using a hierarchical state machine to simplify the state machine
design.
- Consider using Object Oriented programming languages like C++. Contrary to
popular belief, languages like C++ might turn out to be more efficient in
runtime performance because of better locality of reference. (Most objects
would be referring to data that is contained in the same object, thus
improving the locality of reference)
|