20th Anniversary: Mastering a real-time operating system
Whenever embedded systems are discussed, the hobgoblin of efficiency is raised. The systems we work with are indeed real-time systems. We've never discovered any negative impact on application efficiency resulting from the use of the tools presented here. We are, however, fully confident that they've trimmed weeks off the length of the projects on which we've used them. It's our philosophy to get an application working and then, using instrumentation tools, discover any efficiency bottlenecks and correct them. Of course, some applications can, through design and analysis, be proven to be so close to the wire that any additional overhead might compromise the success of the project. Such applications, we suspect, have no business using a real-time operating system in the first place.
Another area that must be considered in embedded systems work is that of the interfaces to other processors. The tools we discuss here were visualized in the context of processing on a single board, although we've since migrated them so that all the processors within an enclosure use them. Still, our processors talk with other devices ranging from PCs to special-purpose equipment provided by other vendors, and we've had no difficulties in making the necessary adaptations. To simplify the presentation, though, we'll ignore those special (and generally trivial) problems.
Mail services
In the abstract, a mail service need only provide the ability to send a memory segment to a mailbox. Our mail-service tools provide a more flexible collection of capabilities based on (and certainly affecting) the common approach to application design.
In some systems, a task may exist only to receive and process a single type of data. We have such tasks, of course, but most of our tasks need to handle a variety of messages. In some cases, these contain data or control information provided to the task; in others, they're requests for information from the task.
Most of our tasks are implemented as large case statements, as shown in Listing 1. After the necessary initializations, each task enters a loop in which it waits for mail at a mailbox. When the mail is received, the task examines it to determine what kind it is and takes the necessary actions. Modifications to this approach, such as those where a task may have to monitor more than one mailbox, have been fit easily into the basic scheme.

It's natural to include a code with each item of mail to define the nature of the mail. In cases where the mail contains control information, this code is the only data in the mail. To aid in creating distinct mail codes and in debugging, we assign mail codes in a way that makes it easy to identify both the sender and the recipient. For example, where the number of tasks permits, we use one hexadecimal digit to identify the sender, one to identify the recipient, and two to distinguish various mail codes between this particular pair of correspondents.
When sending mail, it's necessary to specify the mailbox to which the mail is being sent. As Intel observed in creating iRMX, it's often convenient to specify a return mailbox as well. In most systems--and certainly ours--this can be viewed as redundant information. Since we use different mail codes for different sender-recipient pairs, a task receiving mail knows from the nature of the mail whether it requires a response and, if so, to whom. Nevertheless, we've adopted the standard of providing a return mailbox to aid in readability and modifiability of the code.
To send mail from one task to another, we must create (request allocation of) a segment. From the point of view of the operating system, this is reasonable and necessary--how else is the information to be transmitted? From the point of view of the application, however, it's largely irrelevant. Suppose task A wishes to send a single item of control information to task B. Neither task cares how the information is transmitted, so long as transmission occurs. A fundamental principle of structured programming is that the structure of the code must match the structure of the problem; in this case, segment creation to transfer information isn't part of the problem and shouldn't clutter up the code.
Instead, we created an interface routine called Send_Mail to hide the mechanism of information transmission. This routine has a number of arguments, including the mail code, the mailbox to which the mail is being sent, the return mailbox (or a NULL if no return is required), and the address of the segment containing the actual data being transmitted, if any (other arguments will be introduced later). Send_Mail creates a short segment that contains its arguments, suitably packaged, and mails that segment to the designated mailbox. Figure 1 shows how this operates in two eases, one in which only a mail code is being sent and a second in which other data and a return address are being sent along with it.



Loading comments... Write a comment