CMP EMBEDDED.COM

Login | Register     Welcome Guest  
HOME DESIGN PRODUCTS COLUMNS E-LEARNING CONFERENCES CODE FORUMS/BLOGS NEWSLETTERS CONTACT FEATURES RSS RSS

20th Anniversary: Mastering a real-time operating system
The value of an operating system can be significantly enhanced when the developer encloses it in a shell that tailors its general-purpose services to the needs of the application.



Embedded.com

Having established the principle of creating such an envelope when sending mail, it was natural to ask what else it should contain. We decided to add codes that identify the sending and receiving tasks. This turned out to be superfluous, since the mail code uniquely identifies both. Still, it has its advantages in documenting the code and, as we shall show, in debugging.

Note that with this scheme all mailed items have the same size and data type. Uniform typing simplifies the code needed by the receiver mail to determine what's been received.

A task doesn't call directly on operating system services to receive mail. Unpacking and deletion of the envelope aren't relevant to the application; instead, the task calls Get_Mail. This function has two input (by value) arguments: the identifier for the mailbox and the time the task is willing to wait (usually forever). Get_Mail returns the mail code, data segment pointer, return mailbox, and task codes. It then deallocates (deletes) the segment containing the envelope.

An unexpected advantage of using an envelope to convey information surfaced during the debugging of Send_ Mail and Get_Mail under iRMX. It turned out that some of the envelopes created by Send_Mail weren't being deleted. Examining the segments on iRMX's list of allocated segments, we immediately saw the task and mail codes in each, allowing us to quickly trace the source of the problem.

As a result, we decided to add task and mail codes to all segments created by the application. Every data type definition begins by reserving two words at the front of the data. These two words are filled in automatically by Send_Mail from the mail code and recipient task code if the segment happens to be mailed between tasks. Whenever there are undeleted segments, we can usually identify them quickly by examining their first two words. The only time this method fails is when a mailed segment contains a pointer to another segment; Send_Mail doesn't fill in the mail and task codes in the segment pointed to.

Listings 2 and 3 show data declarations in PL/M and C that explicate the use of standard headers. Note that although we never found it necessary to add the sender's task code to the standard header, it would have required just a few minutes' work.

Memory services
With a memory-management scheme such as the one provided by iRMX, where all segments are allocated from a single pool, there's no obvious reason to interpose a layer between the application and the operating system. We chose to do this with iRMX in part to adhere to a philosophical position and in part to provide a single place we could go for debugging in case of difficulties. This decision stood us in good stead when we decided to migrate to VRTX.

In VRTX, the application program defines a collection of pools of segments; in VRTX jargon, these pools are called partitions. All the segments allocated from a given partition are the same size. To request a segment from iRMX, the application must provide the size of the segment; to request a segment from VRTX, the application instead provides the identifier for the partition containing segments of the desired size.

The cleanest approach would be to have an interface routine, Create_Segment, whose single argument (other than the ubiquitous status return) is the size of the segment being requested. Create_ Segment could, for a system like VRTX, map the size into the appropriate partition number and place the requisite operating system call. However, for one system we were developing, the language didn't provide a statement that returned the size of a data item analogous to the sizeof operator in C. To accommodate this system, we were constrained to passing the partition size directly from the application program to Create_Segment.

For the system in question, the partition numbers had to be specified directly in the code, an example of the abominations that can be forced on us by the choice of improper tools. However, the approach we took turns out to have a positive aspect: it allows us to use special codes for special partitions. For example, if we wanted to do our own memory management out of dual-ported RAM, we could assign that partition an identifier not known to the operating system and let Create_Segment handle management for that partition. This capability requires that partition numbers be provided by the application.

For other languages, such as C and PL/M, we instead have a routine called Choose_Partition. It takes as its argument the size of a data type and returns the identifier for the partition whose blocks are just large enough to hold the data. Choose_Partition bases its choice on the same constants used by the application to define the partitions. The call to Choose_Partition is used directly as an argument to Create_Segment.

When deleting a segment, the situations are similar: a single-pool system needs only the segment itself, while a multiple-fixed-size-partition approach may require that the partition identifier be provided as well. Here we wrote a routine, Get_Partition, that took as its argument a segment pointer and returned the partition from which the segment was allocated. Get_Partition functions by using the partition address boundaries known to the application (since it must define them) and determining which pair of boundaries the given segment fits in.

1 | 2 | 3 | 4 | 5

Rate this article: Low High
Current rating
  • .
Embedded.com Career Center
Looking for a new job?
SEARCH JOBS

Browse all jobs

SPONSOR
RECENT JOB POSTINGS





 :