Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Embedded Systems

Smalltalk and Embedded Systems


OCT91: SMALLTALK AND EMBEDDED SYSTEMS

SMALLTALK AND EMBEDDED SYSTEMS

More than just a pretty face

John Duimovich and Mike Milinkovich

John is manager of embedded systems at Object Technology International (1785 Woodward Drive, Ottawa, Ontario K2C OP9 Canada). Mike is product manager for OTI's ENVY/Developer. They can be reached at [email protected] and [email protected], respectively.


It may come as a surprise to some people, but Smalltalk and embedded systems do mix. Before you throw down this magazine and say "this time Dr. Dobb's has gone too far," you should know that there are commercial products shipping today (and more to the point, making money) based on the technology described in this article. Developers are currently using Smalltalk to build and deliver systems in the following areas: protocol testing, instrumentation, automated hardware component testing, factory automation, process control, and command and control systems. Embedded Smalltalk systems exist for VME-based 680x0 platforms and run on top of real-time operating systems (RTOS) such as VRTX from Ready Systems and VxWorks from Wind River Systems. If you are surprised that Smalltalk runs in stock VME card cages with commercial cards and RTOSs, you may also be surprised to learn that Smalltalk executables are ROMable, and do not require a mouse, keyboard, display screen, or disk.

For most embedded systems developers, object-oriented programming is a relative unknown. When you begin thinking about using OOPS for your next product, you likely consider C++ as the only option. However, Smalltalk is not only a viable language alternative for many embedded applications, but it also provides better development tools. All implementations of Smalltalk come with a comprehensive toolset consisting of code and object browsers, symbolic debuggers, and extensive class libraries.

Embedded and Real-Time Use

Traditionally, embedded development involves coding on a host environment for a target system, then downloading and executing the code. This is the standard edit-compile-link-run cycle familiar to all developers using a compiled language, with the added burden of downloading to a target. Developers are happy to develop on a Sun workstation with their tools (editors, source code control), but in the end they have to deliver in an embedded target which can't or won't run Unix. In fact, targets are usually programmer hostile. The tools are either inferior to their workstation counterparts or not available at all. Developers struggle to deliver reliable, often mission-critical software, while dealing with emulators, downloaders, and cross-compilers -- each of which introduces another step or variable into the development process.

Embedded developers often delay the move to the target system until the last possible moment due to the lack of good (or at least familiar) development tools. The Smalltalk-based development system runs directly on the target. For example the entire development environment -- usually thought of as only available on high-powered workstations -- can be run in a VME card cage using 68000-family processors. Using this approach, the code is developed on the same machine it will be shipped on, reducing the requirement for ports and other cross-development headaches. In cases where the target hardware is not available or in short supply, the system can be coded on the host machine (in Smalltalk) and the identical code run on the target when it becomes available.

A typical Smalltalk development system contains a host workstation such as a Unix workstation or PC. For embedded systems, a target may also be added to the configuration, with a workstation acting as a host. Programmers may develop code on either the host or target. Code written on the host can be loaded directly onto the target platform and vice versa. Figure 1 illustrates a typical machine configuration for embedded Smalltalk development. To provide an in-target development environment, graphics and I/O services for target systems which do not have such devices must be provided by the host. A remote interface can be provided using either Ethernet transport or a shared bus interface such as the one provided by the BIT3 PC to VME bus interface card. Where Ethernet is used, a single target system can easily be used by multiple developers to test applications under development. A transport-independent stream protocol will allow easy extension to other communications media. Multiple connections can be served by a single Smalltalk remote server. This allows a single Smalltalk server to provide graphics for multiple targets, either on separate target systems or in multi-processor configurations.

Memory Management

Manual versus automated memory management is a subject that will always result in an interesting discussion (try it at your next dinner party) and should be of considerable interest to embedded systems programmers. Garbage collection skeptics often use hypothetical disasters such as an airplane coming in for a landing just when its avionics decides to garbage collect its memory. Proponents use the counter-example of a dangling pointer bug to cause the same catastrophe. The truth is that automated memory management can result in more predictable and reliable systems than those using handcrafted techniques.

Most embedded systems programmers are used to doing their own memory management. Efficiency and vague mutterings about "full control" of memory are usually given as reasons for managing memory manually. The efficiency argument does not hold up under scrutiny, because modern garbage collectors use only about three percent of the CPU time in a typical interactive application. This is actually better than what the average developer can do on his or her own. Better yet, garbage collection substantially reduces memory management errors, often the source of catastrophic and hard-to-find bugs. Just as importantly, garbage collection frees developers from writing complicated memory management routines and allows them to concentrate on the real task at hand -- shipping a working product. For a great number of embedded applications, there is little doubt that automated memory management would be a major improvement over the current state of the art.

For those developers who have real-time requirements as well, garbage collection also raises the issue of deterministic runtime behavior. Currently, there are no commercially available garbage collectors which meet the needs of developers writing "hard" real-time systems. However, the technology exists, and as Smalltalk finds it way into more mission-critical applications, they will become available. In reality, though, the issue is really no different in Smalltalk than in, say, C. A truly time-critical function will typically be implemented using statically allocated memory (because malloc isn't deterministic either) and will often be written using handcrafted assembler code. Such functions can be called from Smalltalk as easily as from C, and the garbage collector is guaranteed not to run during their execution.

Interfacing to Other Languages

Real-world applications require that Smalltalk provides external interfaces to languages such as C and assembler. Most Smalltalk systems include a mechanism for calling user primitives written in C (or other languages). User primitives can be used to improve the performance of execution bottlenecks, to guarantee the deterministic behavior of critical regions, or to provide a Smalltalk interface to existing routines written in other languages.

Example 1(a) (taken from OTI's ENVY/Smalltalk) illustrates the use of a user primitive to compute the checksum of a byte object. The corresponding C code for this checksum primitive is in Example 1(b).

Example 1: (a) A user primitive that computes the checksum of a byte object; (b) the corresponding C code for the checksum primitive.

  (a)

  checkSum
     "Answer a twos complement checksum of the receiver."

     <primitive: checkSum>
     ^ self primitiveFailed

  (b)

  #include "userprim.h"
  DEFINE_USER_PRIMITIVE( checkSum )
  {
     char *buffer;
     unsigned int size;
     unsigned short chkSum;
     char *V_objectByteAddress();

     buffer = V_objectByteAddress(PARAM(1));
     size = V_objectSize (PARAM (1));
     for (i=0;i<size;i++) {
          chkSum += *buffer++;
     }
     SUCCEED (TO_SmallInteger (chkSum));
     }

Most Smalltalks support an API which allows user primitives full access to the objects passed as arguments as well as access to well-known Smalltalk objects such as true, false, and nil. Conversion routines allow the primitive to easily convert Smalltalk objects into C data and vice versa. An important feature in any user primitive facility is the ability to create Smalltalk objects in primitives. This allows the application developer full flexibility in coding primitives.

Processes and Interrupts

Most Smalltalk virtual machines support interrupts in some form. Some systems allow signaling of Smalltalk semaphores from primitives, allowing interrupt routines to start processes; others support numbered virtual machine interrupts directly. Example 2 (again from OTI's ENVY/Smalltalk) supports the latter model. Interrupts can be passed directly to interrupt handlers written in Smalltalk using the VM interrupt facility. The code fragment in Example 2(a) illustrates a process which waits for data and when enough has arrived, interrupts the Smalltalk system to process the data. Interrupt values are arbitrary integer values from 1 to 32,767. The Smalltalk code which would "hook" this interrupt is shown in Example 2(b). Every time the example code posts interrupt 20, the enoughDataHasArrived message will be sent to the dataHandler object. The interrupt executes in the context of the current process.

Example 2: (a) Direct support of numbered virtual machine interrupts; (b)Smalltalk code that "hooks" interrupts; (c) dataHandler as an object which has an instance variable, dataFullSemaphore, which refers to an instance of the class Semaphore; (d) the corresponding process could be created with this code.

  (a)

  while {1) {
      wait_for_data();
      if (enough_data) v_interruptVM (20);
  }

  (b)

  Process
      configureInterrupt: 20
      withMessage:
          (DirectedMessage
              selector: #enoughDataHasArrived
              arguments: # ()
              receiver: dataHandler).

  (c)

  enoughDataHasArrived
      "Inform the process waiting for the data that the
           data has arrived."

       dataFullSemaphore signal

  (d)

  [[true] whileTrue: [
      dataFullSemaphore wait.
      self processDataBuffer]] forkAt: 2

The Smalltalk process model is very flexible. The basic functionality is encapsulated in class Process. Processes provide a mechanism for executing multiple threads of control. Process management uses a priority-based scheduler with the highest priority processes executing sequentially in a round-robin fashion. Processes run until they block (typically on a semaphore) and usually do not timeslice. Full source to the process scheduler mechanism is provided and may be subclassed or modified to implement your favorite scheduling algorithm.

Continuing the example, a process-based implementation of enough DataHasArrived follows. An alternative approach could be to process the data directly from the Smalltalk interrupt handler. Example 2(c) uses a process responsible for handling the incoming data. This is a typical approach used in embedded applications. The code in Example 2(c) assumes that dataHandler is an object which has an instance variable, dataFullSemaphore, which refers to an instance of the class Semaphore. The corresponding process could be created with the code in Example 2(d) . The example given has the process running at priority 2, which typifies a low-priority background process. This code creates a new process running at priority 2. The process waits on the dataFullSemaphore until it is signalled by the interrupt handler enough DataHasArrived. If the process executing when the interrupt is serviced is lower than 2, then the signal code will result in an immediate process switch, and the data buffer will be processed. Otherwise, the current process will continue to run and the example process will handle the data when resumed by the scheduler.

Additional Embedded Features

Access to real memory is important when developing embedded system applications. Smalltalk is a high-level language, so developers are usually insulated from the low-level details of the underlying memory system and devices. For Smalltalk to be used in embedded applications, classes must be provided to model the underlying hardware. An example of how this may be done is a VMEMemory class which provides long, word, and byte access to real memory addresses. This allows Smalltalk code to be written which accesses low-level-data structures without requiring any user primitives.

When interfacing to an RTOS, Smalltalk can have full access to the underlying OS via user primitives. For example, the user primitive in Example 3 demonstrates how to signal an RTOS semaphore. (This example is from a Vx-Works implementation.)

Example 3: Signaling a RTOS semaphore

  DEFINE_USER_PRIMITIVE(semaphoreSignal)
  {
      int semId;
      IF ISNOT_SmallInteger (PARAM (1)) THEN FAIL; ENDIF
      semId = TO_long (PARAM (1));
      semGive (semId);
      SUCCEED (SELF);
  }

Delivery Tools

Once software has been developed, it must be performance tuned, packaged, and released. Sophisticated packaging tools exist which allow the delivery of stand-alone executables written in Smalltalk. In addition, for MC680x0 embedded environments, these tools support the ability to ROM portions of the image. Performance tools allow developers to profile arbitrary pieces of code or the whole system.

Performance Tuning

A number of profiling tools exist for Smalltalk language implementations. There are both sampling and full-trace profilers which provide an accurate picture of runtime processor and memory utilization. Sampling profilers periodically trace the stack during execution to provide a statistical analysis of where time is spent. To ensure a high degree of accuracy, the code must be run long enough to provide a statistically significant number of samples. The advantage to this type of profiler is that programs execute at nearly full speed while being observed. A tracing profiler follows the execution of every instruction and message send and provides an exact picture of execution history. This type of profiler is much slower but more accurate and can also provide code coverage information.

The better tools also provide a graphical user interface to allow developers to quickly interpret the results of the trace. Data on garbage collection activity during the trace may also be provided. The profiler can monitor single processes or groups of processes; the latter are useful when performance tuning multiple cooperating processes.

Once a bottleneck has been identified, there are several techniques which may be used to improve performance. The first is the same used in all languages -- analyze the algorithms, data structures, and coding style being used to ensure that the correct approach is being followed. One of the other options available for performance tuning is to recode critical sections in C or assembler. In typical systems the 90/1O rule applies; that is, 90 percent of the time is spent in 10 percent of the code. A primitive added in the appropriate place will often dramatically improve system performance.

A timing facility should be provided which can interface to high-resolution timers on some VME cards and allow the timing of code down to microsecond precision. The interactive nature of Smalltalk allows an application programmer to selectively execute and time pieces of the application individually, as performance bottlenecks are tracked down. Additionally, when prototyping, a potential piece of code can be tested for both correctness and performance, before it is integrated with the full system.

Memory usage and garbage collection can be monitored either by using a memory usage monitor or directly by the application. Memory utilization can be displayed graphically on the screen during program execution. Memory hot spots can be discovered by monitoring this visual "memory heartbeat."

Packaging

An important part of object-oriented embedded development is packaging. Smalltalk programmers typically develop code in a large, rich development environment, of which the stand-alone application is just a small part. Packaging is the process of extracting the stand-alone application from the development environment in order to deliver a minimal executable. Packaging is not a step required with traditional languages such as C, where the executable is generated by the compiler.

With Smalltalk, the developer is given a highly productive incremental development environment. The normal edit-compile-link phases, which allow most developers to spend a lot of time hanging around the coffee machine, do not exist. Once the application has been completed, however, it must be extracted from this environment to be shipped. Smalltalk's approach results in greatly improved programmer productivity throughout the development cycle, but does require this additional step at the end.

For embedded OO development, the packager must be more sophisticated than the standard link-editor tool used for languages such as C. The reason is the complex structure of objects. In addition to the simple code and initialized data found in C environments, OO environments include objects which must be handled during the packaging process. The packager tool should allow any object to be placed in ROM or RAM, at an absolute address or link time relocatable address. This includes the placement of classes, methods, and global objects. C header files are created during the packaging step which allow C code to directly reference objects.

When packaging a Smalltalk application, you specify which classes, methods, and variables are to be placed into the packaged application. Analysis tools aid the packaging engineer in identifying software components which are not required in the delivered executable. This is more difficult in Smalltalk than in other languages because of the dynamic binding inherent in polymorphic languages. For example, if the application sends the message "draw," all implementations of that method might be required in the packaged application. Packaging tools compute the potential messages required at runtime by following message sends. You can refine this by providing additional information such as which methods and classes may be removed. A good packager outputs a small executable with minimal input from the developer. Increasing the information provided by you, however, typically decreases the size of the delivered application. Consequently, packaging is an iterative process.

Additional tools provided by packagers allow fine grain control of the packaging process. This allows the creation of smaller images -- improving runtime memory usage. Typically, these tools include the removal of unused globals, object initialization, and method replacement.

More Details.

Packaging tools for embedded systems allow the user to specify whether a class, method, or object is ROMmable. Typically, in an embedded environment, the major part of a Smalltalk application (80 percent or more) can reside in ROM. An interesting benefit of ROMing is that system performance usually improves, because the garbage collector is not required to manage ROM. In principle, there is no difference between ROMing Smalltalk programs versus traditional assembler or C programs. In addition to ROMing requirements, embedded systems developers must be able to place specific structures (classes, instances, methods) at specific memory locations. Packagers should give the application engineer fine grained control over the placement of such data and code.

The final executable can be output as a binary image and/or as assembler source code. The assembler output code can be assembled and linked using conventional tools. This is important because it allows the development of Smalltalk applications to fit into any existing firmware generation process.

Conclusions

Embedded systems development using Smalltalk is no longer a research curiosity. Real systems are being shipped today which have used the technology described here. Some examples include:

  • A troubleshooting tool for Ethernet and Token Ring local area networks. This is a portable device which a technician connects to a network. A RISC-based subsystem snoops on network traffic, capturing and buffering data as the network runs. A rule-based expert system analyzes the captured data and provides troubleshooting advice to the technician.
  • A fast sampling oscilloscope implemented in Smalltalk and C. The Smalltalk portion consists of approximately 20,000 lines of code in 450 classes. The system integrates a DSP chip which is used to perform high-speed signal processing on sampled data. As microprocessors continue to improve and memory becomes even cheaper, the complexity of embedded applications will undoubtedly increase. As this happens, the ability to meet customers' expectations and management's deadlines with traditional tools and methods will decline. Using Smalltalk to develop embedded systems is not a panacea. Developing complex systems will never be easy. But developers who use object-oriented programming systems such as Smalltalk will be engineering high-quality solutions faster and cheaper than their competitors.

References

Goldberg A. and Robson D. Smalltalk: The Language and its Implementation. Addison-Wesley, 1983.

Smalltalk/V User Manual. Digitalk Inc., 1990.

Barry, B. Using Objects to Design and Build Radar ESM Systems. ACM OOPSLA Conference Proceedings, 1987.

Williams, T. "Object-Oriented Tools Expand the Repertoire of Real-Time Developers." Computer Design (May 1991).

Smalltalk Tools for Embedded Systems

Most Smalltalks are not suitable for embedded systems development straight out of the box -- but then, neither are most C or C++ compilers. To enhance the existing Smalltalk environment for use in embedded systems, a number of extensions are required. Three components of Object Technology International's ENVY tools are of interest to embedded systems developers.

  • ENVY/Smalltalk is an implementation of Digitalk's Smalltalk/V for MC680x0 platforms which has been tailored to meet the requirements of embedded systems. A number of features have been added to the runtime environment. These include: a virtual machine and image which are ROMable; a garbage collector which is aware of ROM; a high-resolution timer interface; ENVY/Remote, which provides graphics and input/output services from targets to host workstations; the ability to link (using standard link tools) the virtual machine and image into a single executable; and ROM emulation mode, which allows memory stores to be optionally checked to catch stores into ROM, even when running a RAM-based development environment. This catches classic "it works in RAM, but not in ROM" bugs.
  • ENVY/Developer is a development environment that bundles together ENVY/Manager (a tool that provides team programming, version control, and configuration management), ENVY/Stats (a runtime performance analysis tool), and ENVY/Packager (a tool for creating small, stand-alone executables).
  • ENVY/Actra is a multiprocessor implementation of Smalltalk in which the Smalltalk process model has been integrated with the process model of a message-passing, real-time operating system. ENVY/Actra's extended process model supports synchronous message send and receive and asynchronous reply between tasks. Task switches occur transparently when a Smalltalk message is sent between processes (Tasks). Processes may reside on different processors; if so, they execute concurrently. Tasks may also send to and receive messages from RTOS process which may be implemented in other languages such as C.
--J.D. and M.M.


_SMALLTALK AND EMBEDDED SYSTEMS_
by John Duimovich and Mike Milinkovich




Example 1

(a)


    checkSum
        "Answer a twos complement checksum of the receiver."

        <primitive: checkSum>
        ^ self primitiveFailed


(b)


    #include "userprim.h"
    DEFINE_USER_PRIMITIVE( checkSum )
    {
        char *buffer;
        unsigned int size;
        unsigned short chkSum;
        char *V_objectByteAddress();

        buffer = V_objectByteAddress(PARAM(1));
        size = V_objectSize (PARAM (1));
        for (i=0;i<size;i++) {
                chkSum += *buffer++;
        }
        SUCCEED (TO_SmallInteger(chkSum));
     }




Example 2

(a)


        while {1) {
            wait_for_data();
            if (enough_data) V_interruptVM (20);
        }


(b)

      Process
          configureInterrupt: 20
          withMessage:
              (DirectedMessage
                  selector: #enoughDataHasArrived
                  arguments: #()
                  receiver: dataHandler).


(c)

    enoughDataHasArrived
        "Inform the process waiting for the data that the
             data has arrived."

         dataFullSemaphore signal


(d)

    [[true] whileTrue: [
        dataFullSemaphore wait.
        self processDataBuffer]] forkAt: 2




Example 3

    DEFINE_USER_PRIMITIVE(semaphoreSignal)
    {
        int semId;
        IF ISNOT_SmallInteger (PARAM (1)) THEN FAIL; ENDIF
        semId = TO_long (PARAM (1));
        semGive (semId);
        SUCCEED (SELF);
     }


Copyright © 1991, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.