Design Article

Abstraction packs power for multi-platform development

Steven Stolpher, Software Systems Manager, Carrier Access Business Unit, Broadcom Corp., Irvine, Calif.

4/18/2003 8:57 AM EDT

Abstraction packs power for multi-platform development

More embedded developers are being asked to design systems that run on more than one hardware platform and sometimes under more than one operating system. The force behind that trend is the decreasing time between consecutive generations of a product. Not only has the duration of development cycles decreased, but the time between development cycles has decreased as well.

A highly competitive marketplace demands that the next generation of a product be designed even as the previous one heads out the door. Developers find they must leverage the software from the previous generation to accelerate the next generation's time-to-market, but advances in technology frustrate that goal. System designers select different devices (i.e., FPGAs), subsystems and CPUs to improve the next-generation product. As a result, developers often face a porting issue that they never expected.

When software is not designed to anticipate those possibilities, the result can be more costly than launching a new design. Major overhauls to the code, constant support calls to the original software developers, duplication of testing effort and major redesigns all combine to confound the original goals of cost saving and accelerated time-to-market.

One of the most creative ways to approach multiplatform development is through the strategic use of abstraction.

The elegance of abstraction is that it distills the application to its root concepts while isolating it from the details of the system implementation. The key is to compartmentalize the system such that the application's algorithms are decoupled from the implementation environment. A camera-pointing system, for example, should operate on velocities, coordinates and distances to target. It is irrelevant whether a radar device or an optical tracking subsystem generated the ranging information. Does the algorithm really need to know that some of the rate information was produced by a Hall-effect sensor?

The approach is effective over a wide variety of applications. Just as the mechanical components of a system can be abstracted, so can communications interfaces, CPU resources, RTOS facilities and storage devices. Deft use of abstraction simplifies the application into a compact, reusable code base. It is a powerful tool when applied against the problems of multi-platform development in relation to OS usage, logical interfaces, protocol implementation and systems services.

Never call OS functions directly; rather, divorce the application from the underlying RTOS by encapsulating operating system functions in an "OS abstraction" library. That permits developers to migrate the application to different operating systems simply by porting the OS abstraction layer. The application code remains intact.

Testing and quality assurance performed previously on the application are not wasted, because the application code remains unchanged. The "hardening" that the software acquired in the field will carry over to the new platform and accelerate the transition to the new environment. Also, when bugs are discovered, the OS abstraction library provides a ready starting point to focus the debugging effort.

Disregards OS

The OS abstraction library should export function prototypes and guarantee specific behaviors for them, regardless of the underlying operating system. For example, many operating systems have semaphores that are safe to take more than once from the same task context. Other operating systems will block the caller if a "recursive" semaphore take is attempted. If the OS abstraction library exports a function, OS_SemTake(), then it must proscribe a standard behavior for the function independent of the underlying operating system. In the case of a "recursive" semaphore take function, developers might have to implement the recursive behavior themselves.

The implementation for some operating systems might involve comparing the ID of the task that last acquired the semaphore to the ID of the task attempting to take it. If they are the same, the function should increment a counter for that semaphore and not call the OS semaphore take function. The corresponding "give" function would have to decrement the counter each time it was called, until the counter reached zero. At that time, the OS semaphore give function would be called to relinquish the semaphore.

You should write wrappers even for functions you would expect to behave similarly on all operating systems. That protects the application from idiosyncrasies and bugs in the underlying operating system implementations. "Standard" functions often aren't, and "well known" functions could make fortunes writing tell-all books about their secret lives.

The socket library function, select(), provides a good example of why encapsulation is so valuable. The types of devices that can be "select-ed" vary considerably from operating system to operating system. Some allow only sockets to be "select-ed," while others permit sockets, pipes and message queues. Abstracting the underlying implementations protects the application against an unwanted, and often messy, redesign for another operating system. In one case, an operating system failed to implement the "timeout" feature of select() properly. Fortunately, the work-around could be confined to the abstraction library; otherwise significant architectural changes to the application may have been required.

You should also convert physical interfaces to logical ones. Consider a system that has an engineering bus to which peripherals are attached. You could create a set of functions that access the bus, such as an EngBus_SendTo() and EngBus_ReadFrom(), so that the function set effectively hides the implementation of the bus — whether PCI, VME, 1553B or a serial port — from the application.

This approach works extremely well for networked applications. If an embedded client engages in a "session" with a networked server over Ethernet-TCP/IP, the concept of "session" could be abstracted and partitioned from the Internet Protocol connection. The application should invoke functions that reflect the logical session, such as:

Session_Open(), Session_

Close(), Session_Send(), Session_Receive(), and Session_

BlockForSessionData().

This allows the application to migrate to a platform where the physical interface may be a paltry serial port. The concept of "session" is unchanged; the only change is the medium over which the session is conducted. If abstracted judiciously, the application can be embedded in different types of equipment with a variety of physical interfaces.

Free to move

The same approach applies to applications that might be distributed among several processors in later generations of the product. Abstracting inter-process communications (IPC) mechanisms allows the software subsystems of the application to move freely from one processor to another. By partitioning the software in this manner, the application can scale from an "entry level" product to a "behemoth" model by adding more processors to the system board. The converse occurs when "bridging" between generations of products. Software required to translate between product generations can be removed in later generations of the product, once the older product technology itself has been completely removed from the platform.

System command is abstracted in the same manner. Whether the commands are generated by command line interface (CLI), infrared link or an embedded SNMP agent does not matter. The mechanism by which the commands enter the system should be divided from the set of functions invoked to execute the commands.

The same is true of event logging and general I/O. By distilling the logical connections from the physical, the application can be "rewired" into dozens of different platforms. The advantages of this approach are a reusable code base that can be leveraged between different platforms as well as a consistent interface across different product lines from the same manufacturer.

The idea is to separate the protocol implementation from both the transport medium and the system-specific details of the application. In our camera-pointing example, the pointing algorithm was isolated from the sources of its input data (the physical sensors). The protocol implementation can be isolated from the transport medium in exactly the same way.

For example, a packet-based protocol would be implemented as a code library that accepts packets as input and produces response packets as output. For connection-oriented protocols that maintain state, the library could also accept and modify data structures that preserve state information related to the connection.

This code organization provides the freedom to embed the protocol in any number of devices at a later date. It also permits the testing of the protocol to occur in isolation, which is an invaluable capability. The implementation can be debugged prior to the appearance of hardware and used later as a static testbed to simulate failures in the field.

It is important not to place application-specific knowledge into the protocol implementation. For example, an application-level protocol implementation that produces AAL5 packets cannot easily be placed into a device that uses Internet Protocol. Similarly, an implementation that accepts a TCP socket descriptor, to which it sends responses, cannot easily be employed across a proprietary backplane.

This article was excerpted from ESC paper 304, titled "Designing Multi-Platform Software."





Please sign in to post comment

Navigate to related information

Datasheets.com Parts Search

185 million searchable parts
(please enter a part number or hit search to begin)

Feedback Form