Selective Sharing of Control with Traditional Java Components
The recommended approach for providing efficient and reliable integration of hard real-time components with traditional Java components makes use of a restrictive form of object sharing between the hard real-time and traditional Java domains. The shared objects always reside in the hard real-time domain and do not participate in garbage collection.
Because these are hard real-time objects, they will never be subject to relocation. This greatly simplifies the implementation and improves execution efficiency.
Sample Implementation of A Device Driver
In this section, we describe a simple interrupt-handling device driver software component, written entirely in Java. Figure 3 provides interface declarations that serve as a bridge between the soft and hard real-time domains. For any hard real-time Java object that implements these interfaces and happens to be shared with the traditional Java domain, the traditional Java environment can execute only the methods defined in these interfaces.
Figure 4 defines the DeviceBuffer class, and Figure 5 supplies the declarations of class constants and instance variables for the Interrupt class that represents the interrupt service routine. Figures 6 and 7 complete the implementation of the Interrupt class.
The ceilingPriority() method, shown on lines 24 through 26 of Figure 5, is required because this class implements the javax.realtime.util.sc.Atomic interface. For classes that implement such an interface, programmers are required to provide this method—it establishes a syntactic marker within the body of the class, which can be used to establish static properties regarding the object’s synchronization behavior.
The constructor, shown on lines 28 through 43 of Figure 6, instantiates the three I/O ports required for operation of the interrupt handler. For any given hardware configuration, certain ranges of memory and I/O address space will be eligible to be treated as I/O ports that are accessible from hard real-time Java components.
To ensure that this hard real-time Java component has permission to access the requested I/O ports, range checking is performed when these I/O ports are instantiated. Once instantiated, no additional checking is required when reading or writing the I/O ports. Besides instantiating the necessary I/O ports, the Interrupt constructor also allocates memory to represent a pair of input buffers.
Lines 45 through 58 of Figure 6 provide the actual interrupt-handling code. This method is declared as synchronized because, while running, all other interrupts of equal or lower priority are forbidden from running. Because this class is declared to implement the Atomic interface, the safety-critical Java byte-code verifier assures that the body of every synchronized method is execution-time bounded. This restricts the set of services that can be invoked from within an interrupt handler.
The byte-code verifier prohibits interrupt-handling code from invoking services that might block while holding the priority ceiling lock. The byte-code verifier also ensures that only objects implementing the Atomic interface can set their ceiling priority to ranges that correspond to hardware-dispatched interrupt handling.
In Figure 7, we present the method that enables efficient streaming of bytes from the hard real-time interrupt handler to the traditional Java domain. This method returns a reference to a DeviceBuffer object. Note that DeviceBuffer implements the TraditionalJavaBuffer interface. Thus, it’s possible to share a reference to the returned DeviceBuffer with the traditional Java domain. If shared, the traditional Java environment is able to invoke the validBytes() and byteAt() methods.
The Interrupt class doesn’t implement the TraditionalJavaDevice interface. Consequently, it’s not possible to share instances of Interrupt with the traditional Java environment. Figure 8 provides a definition of Interrupt subclass SharedDevice, which implements the TraditionalJavaDevice interface. Instances of SharedDevice can be shared with the traditional Java domain.
Note that the traditional Java getBuffer() method invokes this.getReadBuffer() indirectly, by way of NoHeapRealtimeThread.transfigure(). The transfigure() service has the effect of converting the currently running traditional Java thread into a hard real-time thread. Therefore, the thread could perform hard real-time synchronization operations.
If we allowed the traditional Java thread to perform hard real-time synchronization without first transfiguring the thread, we would introduce many scheduling complexities into the hard real-time environment, which would be very difficult to analyze. Though the transfigure operation is very efficient, thread transfiguration and synchronization is more expensive than simple method calls. This is why the implementation of this shared device driver processes the contents of each buffer without requiring any further synchronization.
Conclusion
With proper attention to resource management details, developers can implement hard real-time tasks using the Java language. The hard real-time Java technologies offer improved performance and determinism as well as a much smaller memory footprint than traditional Java technologies. Hard real-time Java components integrate cleanly and efficiently within large systems that typically comprise a combination of soft real-time and hard real-time capabilities. Development and maintenance of low-level software components using portable stylized Java rather than assembler, C, or C++ will yield significant productivity improvements and cost savings over the complete life cycle of typical critical software systems.