by Kelvin Nilsen
Published August 2014
Learn why Java SE is a good choice for implementing real-time systems, especially those that are large, complex, and dynamic.
This article is Part 1 of a two-part series that describes some of the software engineering principles that serve as the foundation upon which modern real-time software systems are constructed. The presented methods and techniques have been proven in many successfully deployed Java SE applications, including a variety of telecommunications infrastructure devices; automation of manufacturing processes, ocean-based oil drilling rigs, and fossil fuel power plants; multiple radar systems; and the modernization of the US Navy's Aegis Warship Weapons Control System with enhanced ballistic missile defense capabilities.
Unlike traditional business applications, real-time software is expected to deliver results under very stringent timing constraints. A computation that is performed at the wrong time—either too early or too late—is considered to be wrong, even if the computed numbers are exactly right. In the context of this article, the term real-time software means software that is engineered to comply with real-time constraints.
In other contexts, the term real-time software is occasionally used to describe software that is interactive with a human user. Real-time stock quotes, for example, might be delivered to your computer screen as soon as you type your inquiry. However, unless the provider of your stock quotes has taken precautions to guarantee that the quote appearing on your screen is no more than a certain number of milliseconds (ms) old, that use of the term is different from how the term is used in this article.
Engineers of real-time software often make a distinction between soft real-time systems and hard real-time systems. For purposes of this series, hard real-time systems follow a software engineering approach that requires all compliance with timing constraints to be validated prior to the execution of the software system. In other words, the engineer proves that the hard real-time application will satisfy all timing constraints by thoroughly analyzing the worst-case execution paths for every task, by analyzing the worst-case execution times for every machine instruction on each worst-case execution path (including delays resulting from cache misses and pipeline stalls associated with contention for processing resources), and by ensuring lower bounds on the time separation between consecutive firings of each task.
Systems that are engineered according to hard real-time discipline have no need to check at runtime whether deadlines are missed. However, hard real-time methodologies are extremely inefficient on most modern computing platforms. This is because it is very difficult to prove that moderately complex software will always benefit from the memory cache, speculative execution, and pipelining optimizations that are engineered into modern hardware platforms. So the very conservatively scheduled hard real-time system often ends up utilizing less than 5 percent of the hardware platform's commonly observed full-speed capacity.
Soft real-time systems, on the other hand, follow an approach under which execution times and task execution frequencies are analyzed more through empirical techniques (measurements and statistical characterizations), and the software itself is written to accommodate the possibility that timing constraints will occasionally be missed.
The use of Java SE APIs in the implementation of real-time systems is most appropriate for soft real-time development. Using Java SE for hard real-time development is also possible, but generally requires the use of more specialized techniques such as the use of NoHeapRealtimeThread
abstractions, as described in the Real-Time Specification for Java (JSR 1), or the use of the somewhat simpler ManagedSchedulable
abstractions of the Safety Critical Java Technology specification (JSR 302).
It is also important to distinguish real-time engineering, as it is described in this series, from performance engineering. An e-commerce web server, for example, might have been carefully engineered to support an average of 1,000 transactions per second. That is different from saying that every transaction must be completed in 1 ms. It could be that some transactions require hundreds of ms and others are completed in less than 1 ms, as long as the average of all transactions is 1 ms. It could also mean that each transaction requires an average of 4 ms from start to end, but the system has the ability to concurrently execute four transactions at a time.
The benefits of the Java language are especially valuable in real-time applications that are large, complex, and dynamic. Software engineers are motivated to select Java SE when their projects require dynamic code updates, coordination between multiple teams of developers, integration of independently developed software components, support for multiple hardware or operating system platforms, or support for multiple software configurations as product requirements evolve over multiple years or even decades.
Projects that can be implemented entirely by one or two developers in a year's time are more likely to be implemented in a less powerful language such as C or C++, especially if it is critical that the end product minimize consumption of battery power or high-volume production costs. Such projects are less likely to appreciate the benefits of Java, and are often able to justify the higher software engineering and maintenance costs associated with the choice to use an older language.
As an example, this article looks at a modern automobile's traction monitoring system. Traction control makes a good study because it is simple enough to be presented in this article and, at the same time, it is complex enough to motivate mastery of the relevant principles of real-time software engineering.
The code for this sample application is contrived for the purposes of teaching the principles of real-time programming and does not represent the most appropriate implementation of a traction control system. Likewise, certain characteristics of the sensor architecture, such as the ability to read and report the rotational angle of a rapidly turning wheel to within four decimal digits of precision every 8 ms, are contrived for the purposes of simplifying the discussion.
Due to the extreme cost pressure on the components of a consumer vehicle, Java SE would probably not be the language of choice for a commercial implementation of traction control unless Java SE was being used to control multiple subsystems, thereby distributing its costs. When memory constraints are severe, more memory-efficient versions of Java can be used, such as Java ME or the draft JSR 302 standard (safety-critical Java).
The traction monitoring system, which is typically part of a much larger and more complex real-time system, detects slippage of tires during braking and acceleration. In a modern automobile, traction monitoring is often integrated with antilock braking, antislip acceleration, tire pressure monitoring, suspension control, transmission control, and even fuel injection and ignition control.
Our sample application focuses on detection of traction loss. We assume that the traction monitoring system has access to sensors that detect each of the following values:
We measure the speed and acceleration on each wheel independently by comparing the values produced by the rotational angle sensor for consecutive readings. Assume a wheel diameter of 24 inches. The circumference of the wheel is calculated as shown in Figure 1:
Figure 1. Calculation of wheel circumference
Assume further that the operating constraints on the vehicle limit its speed to no more than 150 miles per hour. The dimensional analysis in Figure 2 shows that the minimum time for a wheel rotation is 28.56 ms:
Figure2. Dimensional analysis
Given these assumptions, as long as we sample the wheel's rotational angle no less frequently than once every 28.56 ms, we are sure that the measured rotational angle between two consecutive readings is accurate. If we measure less frequently, it is possible that our measured angle is less than the wheel's actual rotation by one or more complete rotations of the wheel.
In order to monitor both speed and acceleration, it is necessary to perform trend analysis on readings from the wheel sensor. We have chosen to base our trend analysis on the most recent eight consecutive readings from that sensor, and we have configured our system to capture a wheel angle measurement every 8 ms. It is important for each reading to be timed at exactly the right moment. If the measurement is performed just 1 ms late, the calculated speed will be off by the amount shown in Figure 3:
Figure 3. Calculated speed
Thus, we impose a real-time constraint on our system that each wheel sensor reading be performed within 400 microseconds (μs) of its intended sample time. This limits the error in speed calculations to 5 percent.
For trend analysis, we perform the symbolic analysis required to calculate a least-squares best-fit line segment to represent the scatter diagram of instantaneous velocity versus time. Consider the sample data shown in Table 1, which was collected by an actual run of the program described in this article.
Table 1. Simulated wheel sensor data
Time of Measurement (x values) | Measured Angle | Angular Difference from Previous Measurement | Distance Traveled in Feet (y values) |
---|---|---|---|
-0.008 s | 105.3335 | ||
0 s | 137.8311 | 32.4976 | 0.5672 |
0.008 s | 170.3579 | 32.5268 | 0.5677 |
0.016 s | 202.9141 | 32.5562 | 0.5682 |
0.024 s | 235.4997 | 32.5856 | 0.5687 |
0.032 s | 268.1146 | 32.6149 | 0.5692 |
0.040 s | 300.7588 | 32.6442 | 0.5698 |
0.048 s | 333.4324 | 32.6736 | 0.5703 |
0.056 s | 6.1353 | 32.7039 | 0.5708 |
For the straight-line equation in the form y = mx + b, the formulas for calculating the slope m and the y-intercept b of the best-fit line are as shown in Figure 4:
Figure 4. Formulas for calculating the slope and y-intercept
A graph of this data is shown in Figure 5.
Figure 5. Speeds measured by rotational angle sensor
During the time span represented by these measurements, the tire was known to be accelerating at a constant rate of 8 ft/sec/sec. In other words, every second, it was traveling 8 ft/sec faster than in the previous second. Any jitter in measurement of wheel angles is manifested as outliers in the trend analysis. If two consecutive measurements are taken less than 8 ms apart, the reading is manifested below the fitted line. If two consecutive measurements are taken more than 8 ms apart, the reading is manifested above it.
The measurements in Figure 5 were taken on a lightly loaded Microsoft Windows 7 machine running the Java HotSpot Client VM. If the host computer had been more heavily loaded, or if the Java SE application threads had been preempted by garbage collection or by dynamic adaptive just-in-time (JIT) compilation, the measured data points would not be so tightly aligned.
The astute reader might ask why we don't simply measure the time at which each measurement is taken and "plot that" rather than assuming every measurement is taken exactly 8 ms after the previous measurement. That would be a reasonable alternative approach, except for the following observations.
Choosing to assume that measurements are taken at a fixed periodic interval is one of the distinguishing characteristics of typical real-time software system designs.
The slope of the best-fit line represents the measured acceleration. The sensor system has correctly approximated that the acceleration is 8 ft/sec/sec, as shown by the calculation in Figure 6:
Figure 6. Acceleration calculation
The sample application is structured as a number of event handlers. Each event handler is a thread that waits for a periodic release, executes the event handling code, and then suspends itself until the next periodic release. The traction monitoring system comprises the following event handlers:
WheelSensor
event handlers, one per wheel, with a period of 8 msBrakeMonitor
event handler, with a period of 16 msSteeringMonitor
event handler with a period of 24 msAcceleratorMonitor
event handler with a period of 32 msThe following additional threads participate as part of the underlying infrastructure:
PeriodicDelay
thread, which provides interconnection between the dispatcher thread and the main thread, with a period of 100 msCode for the AcceleratorMonitor event handler is shown in Listing 1.
[1] package traction;
[2]
[3] import com.atego.srt.EventHandler;
[4] import simulation.IOServer;
[5]
[6] public class AcceleratorMonitor extends EventHandler {
[7]
[8] private double acceleration;
[9] private final IOServer io;
[10] public AcceleratorMonitor(IOServer io, int priority) {
[11] super(priority);
[12] this.io = io;
[13] acceleration = 0.0;
[14] }
[15] public synchronized void handleAsyncEvent() {
[16] acceleration = io.getAcceleration();
[17] }
[18] public synchronized double getAcceleratorLevel() {
[19] return acceleration;
[20] }
[21] }
Listing 1. Implementation of AcceleratorMonitor
The AcceleratorMonitor
class extends the abstract com.atego.srt.EventHandler
class, which is from an open-source library that provides capabilities similar to the services discussed in Real-Time Specification for Java (Addison Wesley Longman, 2000). The concrete implementation overrides the handleAsyncEvent
method, providing user code that is to be executed every time the event handler is fired.
In this case, the event handler simply fetches the acceleration reading from the IOServer
object passed in to the AcceleratorMonitor
's constructor and stores the returned value in a private instance variable. When the application desires to know the current acceleration level, it invokes the getAcceleratorLevel
method. Note that both the handleAsyncEvent
and getAcceleratorLevel
methods are synchronized. This is because these two methods, which are normally executed by different threads, share access to the same acceleration
variable. Synchronization is required both to ensure coherent access to the two-word data value and to enforce visibility across thread boundaries.
In the provided source code, the IOServer
object is an abstract representation of the test vehicle. It provides simulation data as guided by a real-time script. It would be straightforward to replace the IOServer
argument with a subclass that provides access to a real vehicle's sensor system or with an interface to gaming controls for a brake pedal, accelerator, and steering wheel.
The AcceleratorMonitor
object is instantiated within Main.java
and assigned to the local accelerator_monitor
variable. To arrange for periodic execution of the AcceleratorMonitor
, it is passed as an argument to a PeriodicEvent
constructor, as shown in the following excerpt from Main.java
:
PeriodicEvent accelerator_event =
new PeriodicEvent(dispatcher, ACCELERATOR_SENSOR_PERIOD, accelerator_monitor);
The dispatcher
object takes responsibility for firing the PeriodicEvent
with the specified period.
The event handling code for the BrakeMonitor
and SteeringMonitor
objects is essentially the same as for the AcceleratorMonitor
and is not discussed in the article.
The WheelSensor
event handler is more interesting because it takes responsibility for the trend analysis associated with the estimation of instantaneous speed and acceleration.
The code for the event handler is shown in Listing 2. This event handler is executed every 8 ms. With each invocation, it fetches the current wheel angle and updates the running tallies on the variables required to update the best-fit analysis of speed trends.
Note that, where possible, we avoid iterating over all elements of the scatter chart. In particular, in lines 26 through 28, we update the sum_y
and average_y
values by subtracting the overwritten value and adding in the newly computed distance value. Note also that we don't recompute the values of the x_values
array each time the event handler executes. Those values are set once in the constructor, which is not shown, and never change.
[1] long num_samples, least_squares_tag;
[2]
[3] public synchronized void handleAsyncEvent() {
[4] double rotation = io.getWheelRotation(wheel_id);
[5] double adjusted_rotation = (rotation < previous_angle) ? (
[6] rotation + 360: rotation);
[7] double rotational_delta = adjusted_rotation - previous_angle;
[8] double this_distance = (
[9] (rotational_delta / 360.0) * WHEEL_CIRCUMFERENCE);
[10] previous_angle = rotation;
[11] double overwritten_distance = y_values[oldest_distance_index];
[12] y_values[oldest_distance_index] = this_distance;
[13] oldest_distance_index++;
[14] if (oldest_distance_index >= NUM_SAMPLES) {
[15] oldest_distance_index = 0;
[16] }
[17] sum_xy = 0.0;
[18] int y_index = oldest_distance_index;
[19] for (int i = 0; i < NUM_SAMPLES; i++) {
[20] sum_xy += x_values[i] * y_values[y_index];
[21] y_index++;
[22] if (y_index >= NUM_SAMPLES) {
[23] y_index = 0;
[24] }
[25] }
[26] sum_y -= overwritten_distance;
[27] sum_y += this_distance;
[28] average_y = sum_y / NUM_SAMPLES;
[29] num_samples++;
[30] }
Listing 2: Implementation of WheelSensor.handleAsyncEvent()
When application code desires to know a current wheel speed or acceleration, it invokes the getSpeed
or getAcceleration
method, which are shown in Listing 3. As with the implementation of AcceleratorMonitor
, both of these methods and handleAsyncEvent
are all synchronized
. The private calculateLeastSquaresLine
method relies on the synchronization provided by the methods that invoke it.
The WheelSensor
object uses the instance variable num_samples
to count the number of times the handleAsyncEvent
method has been invoked. The calculateLeastSquaresLine
uses the least_squares_tag
instance variable to remember which sample data the most recent calculation was based on. The test at line 4 allows the method to avoid recomputing the slope and intercept if calculateLeastSquaresLine
is invoked again before new data has been fetched.
[1] private double m, b;
[2]
[3] private void calculateLeastSquaresLine() {
[4] if (num_samples != least_squares_tag) {
[5] m = (sum_xy - sum_x * sum_y / NUM_SAMPLES) / m_denominator;
[6] b = average_y - m * average_x;
[7] least_squares_tag = num_samples;
[8] }
[9] }
[10]
[11] // Current speed is returned in ft/sec
[12] public synchronized double getSpeed() {
[13] calculateLeastSquaresLine();
[14] double result = (b + m * polling_span) / polling_period;
[15] return result;
[16] }
[17]
[18] // Acceleration is the change in speed. Units of result are ft/sec/sec.
[19] public synchronized double getAcceleration() {
[20] calculateLeastSquaresLine();
[21] // The slope is ft/8 ms per second. Divide by
[22] // polling_period to convert to ft/sec/sec.
[23] return m / polling_period;
[24] }
Listing 3. Implementation of key WheelSensor
methods
For an application as simple as the traction monitoring system, it might seem unnecessarily complex to structure this as ten independent threads (main, dispatcher, periodic delay, four wheel sensors, brake sensor, acceleration sensor, and steering sensor). A simpler program structure might have been a single thread running in a tight loop, with a sleep request at the top of the loop to delay an appropriate amount of time between successive sensor readings. In fact, early real-time systems were almost always implemented using that alternative approach, which is characterized as static cyclic scheduling.
Most modern real-time systems are structured in the style of the sample traction monitoring application. The reasons for this more-dynamic task model are several fold:
While there still exist today many real-time systems that use static cyclic schedules, these are mostly the smaller and simpler systems. Since the real-time systems that are most likely to be implemented in Java SE are the ones that are larger, more complex, and more dynamic, the emphasis of this article is on priority-based scheduling.
As demonstrated by the quality of the fit to the trend line reported in Figure 5, this application is doing a very good job of meeting its real-time constraints. In a 30-second simulation, the deviance from intended real-time constraints was never so great as to result in a false indication of wheel slippage. In part, this is because we were very careful to engineer this application so that there would be no cause for inconsistent timing behavior (also known as jitter).
A careful reading of the complete application source code proves that the only memory allocation performed by this application is during startup, before we actually begin running the periodic real-time tasks. Furthermore, we have carefully avoided any dependencies on I/O services that might block execution while, for example, when a disk arm is seeking a new track. And we have been very careful to ensure that this is the only software running on the host platform while we are taking our real-time measurements.
This traction monitoring application has been implemented in a style that facilitates engineered compliance with real-time constraints. However, we have not yet explained how that engineering is performed. That is the topic of the next article in this series.
Particular topics that will be described, using this traction monitoring application to motivate the discussion, include the theory of rate-monotonic scheduling, priority inheritance, workload admission analysis, real-time garbage collection, and special virtual machine implementation approaches, all of which are designed to enable efficient deployment of real-time software written in Java SE.
Careful application of these principles of real-time software engineering allow us to build systems that are much more complex than the small traction monitoring application yet still comply reliably with real-time constraints.
As Chief Technology Officer over Java at Atego Systems—a mission- and safety-critical solutions provider—Dr. Kelvin Nilsen oversees the design and implementation of the Perc Ultra virtual machine and other Atego embedded and real-time oriented products. Prior to joining Atego, Dr. Nilsen served on the faculty of Iowa State University where he performed seminal research on real-time Java that led to the Perc family of virtual machine products. Dr. Nilsen participates in the Java Community Process as a member of the JSR 282 and JSR 302 Expert Groups. He holds a BS in physics and MS and PhD degrees in computer science. Atego Systems is a wholly owned subsidiary of PTC, Inc.
Join the Java community conversation on Facebook, Twitter, and the Oracle Java Blog!