20th Anniversary: Mastering a real-time operating system
A real-time operating system relieves the embedded systems developer of the need to implement solutions to such problems as interprocess communication and memory management. Nothing could be simpler--especially for those accustomed to building and debugging such services anew for each application--than making a few well-defined operating system calls.
While the operating system may provide the services a developer needs, it doesn't necessarily do so in a manner that optimizes the developer's time over the life of the project. By hiding the handling of its services, the operating system also hides details that can be important to the developer, especially (but not exclusively) during debugging. Furthermore, the operating system services are of necessity framed in fairly general ways and as a result don't closely match the needs of any particular application.
Does this mean that we would advise against the use of real-time operating systems? Far from it! We've found, however, that the value of the operating system can be significantly enhanced when the developer encloses it in a shell, or membrane, that tailors its general-purpose services to the needs of the application.
In this article we'll discuss a number of related strategies that have been developed at our company to more effectively use real-time operating systems in a variety of embedded applications. Our products are communication systems encompassing a full range of services, from user interfaces through network protocols down to the level of controlling specific transmission media including wireline, HF radio, and satellite communications.Thus, the techniques we discuss have been proven in a number of different environments.
We'll focus on three main areas: interprocess communication through mailed data, memory management, and debugging. The techniques we present can simplify application development and debugging. They also tend to be robustly adaptable to the inevitable changes in requirements that occur during the life of the application. After discussing the techniques themselves, we'll discuss our experience with them. This experience includes porting application code between different processors and different operating systems and use of the same service membranes by programs written in different languages.
A tale of two operating systems
Our company has worked extensively with two operating systems for the 80x86 family of processors: Intel's iRMX and Ready Systems' VRTX. At the appropriate level of abstraction, these operating systems provide the same critical services. However, the methods of implementation are quite different.
For memory management, iRMX provides a single heap for all dynamically allocated memory segments. The application need only specify the size of the desired segment. VRTX, by contrast, provides pools of fixed-size segments, called partitions. The partition sizes, and the size of the segments that each contains, are set by the application. When a segment is requested, the application must specify the number of the partition to which it belongs.
For interprocess communication, both systems provide a number of services; we've standardized on the use of mail. In each system, a task may send mail (in the form of a segment) to a mailbox, which is a FIFO queue. (In VRTX, such a mailbox is called a queue; VRTX has a more restricted interprocess structure that it calls a mailbox.) A task may read from the mailbox or, if there's no mail to read, wait at the mailbox until something arrives. In VRTX, the sender provides the identity of the mailbox, a pointer to the segment being mailed, and the usual status return variable. iRMX has one additional parameter: the identity of the mailbox to which any response to the message should be sent.
We're glossing over a number of significant details, including those relating to how such objects as tasks, memory partitions, memory segments, and mailboxes are created, typed, and named; we have equivalent versions of our tools for those services as well. It's important to note that the tools we'll be describing weren't designed to provide a common interface to these two operating systems. In fact, we developed these tools solely to support our work with iRMX and PL/M at a time when we had no serious thought of using any other operating system or language in our work. That our approach served us well when the use of other operating systems and languages became mandatory was icing on the cake.
One other area in which iRMX and VRTX differ is in their provision of debugging services. The VRTX debugger, TRACER, is fairly typical, providing breakpointing at addresses and the ability to examine and modify memory; it does provide some reports on the status of tasks and other system resources. (Ready Systems' RTScope debugger has more features than TRACER but wasn't available when we began working with VRTX.)
By contrast, the iRMX Dynamic Debugger was specifically oriented toward system objects. For example, rather than use it to break at an address, the developer could cause a break on such system events as mail being sent to a mailbox. One of the great virtues of this debugger is that it knows which segments of dynamic memory are allocated and can list them upon demand. The iRMX system also lets the application assign character strings\ (which become known to the debugger) to identify such system objects as tasks and mailboxes. (We sadly note that this debugger, which we used extensively on our 8086 projects, has been eliminated from the 286 and 386 versions of iRMX.)