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

eCos: An Operating System for Embedded Systems


Jan00: eCos: An Operating System for Embedded Systems

Gary is an applications and systems engineer at Cygnus Solutions. He can be reached at [email protected].


The Embedded Configurable Operating System (eCos) is a royalty-free, open-source, real-time kernel, targeted at high-performance small embedded systems. As such, eCos has been specifically designed and tuned to run on 32- and 64-bit microprocessors. To date, eCos has been ported to the ARM7, PowerPC, MIPS, SPARClite, Hitachi SH3, and Matushita MN10300 microprocessors, with ports to many other architectures and platforms under way.

eCos is distributed by Cygnus Solutions (the company I work for) under the Cygnus eCos Public License (CEPL), which is based on the Netscape Public License (NPL) and adheres to the open-source model. For more information (including sources), see http://sourceware.cygnus.com/ecos/.

eCos Overview

eCos is designed around a single-process, single-address space, multiple-thread model. Application code is linked directly with the eCos kernel to be deployed in the target environment. A complete set of kernel features is provided to support applications built using eCos. These include:

  • Thread primitives. Functions to create, destroy, suspend, and resume threads are provided.

  • Synchronization primitives. Multiple mechanisms for thread synchronization are provided, including mutex and condition variables, binary and counting semaphores, message/mail boxes, and general-purpose event flags.

  • Timers, counters, and alarms. A set of flexible counter and alarm functions is provided.

  • Standard libraries. A complete Standard C run-time library is provided. Also included is a complete math run-time library for high-level mathematics functions, including a complete IEEE-754 floating-point library for those platforms without hardware floating points.

  • Device drivers. A complete, extensible package supporting general-purpose I/O devices is provided. Most platform ports also come with a set of drivers for standard devices, such as serial I/O, and the like.

  • Itron compatibility. A library that provides complete Itron (Version 3) emulation is also standard. (Itron is a standard real-time environment popular in Japan; see http://tron.um.u-tokyo.ac.jp/ TRON/ITRON/.)

Most of eCos is written in C++, with some machine-dependent portions written in C and assembly language. eCos also includes a complete C-language kernel API. C++ is used with care taken to exploit the high-level abstractions available in that environment without introducing unnecessary overheads. To keep run-time overhead low, neither C++ exceptions nor RTTI are used within the kernel. Additionally, the kernel is completely static (for instance, there are no uses of new), and the only use of C++ templates is to provide type-checked macros. An example of the use of templates is in the memory-pool management classes, wherein a template is used to wrap generic thread synchronization, waiting, and atomicity around any of several simple memory-pool managers.

Configurability

One of the key design features of eCos is its configurability. The eCos kernel comprises a set of packages, with you controlling which packages are included in the target application. Only those packages that are required by the application need to be included in the final software. Furthermore, as much as possible, all individual components within the packages are also configurable.

An example of this level of configurability might be the description of a serial I/O device in the system. The top-most choice would indicate that serial I/O is required. Then for the target platform, any of the standard serial devices may be configured into the system (or left out, as dictated by the application). Each of these devices can be configured in turn, with parameters such as the default baud rate or the size of kernel buffers to use among the possible choices.

At its finest grain, configurability can control the inclusion of individual lines of source code; to support a priority inheritance protocol, for instance. Only the lines of code that the application needs are included in the final image. Of course, this is only possible with open source, which allows recompilation of the entire system by the user.

To mitigate the potential complexity of so many configuration parameters (currently more than 250), Cygnus provides ConfigTool (Figure 1), which lets you specify complete configuration information using a graphical interface. (ConfigTool is currently available only when eCos is purchased with commercial support, not via the sourceware mechanism. However, eCos remains easily configurable using the include command-line based tools.)

The CDL Language

All configuration options available within eCos are described in a high-level language called the Configuration Description Language (CDL). The use of CDL in eCos lets the ConfigTool support consistency and dependency checking, which guarantees that the chosen configuration works properly in the final product. Each configuration option has associated online documentation that can be referenced easily as the system is being configured.

The principle unit of configuration within eCos is the package -- a complete, standalone layer of functionality that may or may not be present. In many cases, one package will require the presence of another package, but care was taken to keep these units as independent as possible. For example, the Itron support is a totally independent package. However, if Itron support is included, the kernel package is also required because Itron uses the eCos kernel facilities.

Listing One is part of the CDL used to describe the options available for the kernel thread scheduler. As you can see, the CDL is just a set of specially formatted comment strings within a C/C++ include file that describes the package. In this case, the file would be <pkgconf/kernel.h>. You use CDL commands to describe packages (the basic level of configurability), components within the packages, and options for each component, in a hierarchical (tree-like) fashion.

ConfigTool processes these strings to understand the structure and dependency relationships of the various configuration options. Listing One describes the choice of schedulers currently available as standard in eCos. Whether or not any scheduler is included in the system at all is determined by use of the component CYGPKG_KERNEL SCHED, which can only be defined if the CYGPKG_KERNEL has been enabled. When CYGPKG_KERNEL_SCHED is selected in ConfigTool (see Figure 2), there are two mutually exclusive implementation choices -- CYGSEM_KERNEL_SCHED_MLQUEUE (for the multilevel queue-based scheduler) and CYGSEM_KERNEL_SCHED_BITMAP (for a simple one-thread-per-priority scheduler) -- which are depicted as radio buttons in the GUI.

Associated with each of these choices may be additional options, some of which may only make sense for a particular selection. The #define/#undef macros at the end of the section define the default configuration. When processed by ConfigTool, these macros are updated with the user's selected configuration information.

The use of CDL to describe the possible configuration options also lets you include configuration options with any packages or components added to the system. This flexibility means that the same tools can be used for generic system choices as well as customized elements. Third-party add-on packages can also use CDL, thus maintaining the same degree of control over all parts of the system.

Additionally, ConfigTool lets you specify the layout of physical memory sections in the target environment. These include RAM and ROM segments, as well as other variations. Different environments can be defined easily; such as RAM-based (downloaded) operations or ROM-based applications. These definitions are imported into the final eCos application, allowing for flexible control over these resources.

Cygnus has tested a range of configurations, employing random and permutation-based testing to exercise many possible configuration choices. These tests, and the tools that drive them, are provided as part of eCos, letting you verify the proper operation of the kernel after it has been configured.

Hardware Abstraction Layer (HAL)

The eCos system was designed to be portable to different architectures and platforms with minimal effort. To accomplish this, the system is separated into layers (also referred to as "packages"); see Figure 3.

The most basic layer is the Hardware Abstraction Layer (HAL). The HAL provides access to architecture and platform-specific details in a completely architecture- and platform-independent way. This means that if a higher layer of the system (the kernel, for instance) needs to use a potentially machine-dependent function, it uses the HAL version of that function. Higher levels are also allowed access to the HAL, but in general, this is discouraged.

The HAL functions and capabilities are defined as C/C++ macros in a set of files: <cyg/hal/hal_arch.h>, <cyg/hal/hal_intr.h>, <cyg/hal/hal_cache.h>, and <cyg/hal/ hal_io.h>. Some of the macros defined in these files may be constant or even empty, depending on the platform details. The point of using the HAL in this way is to make the rest of the system totally independent of the machine details. This layering works so well that absolutely no code changes are required for a port to a new architecture outside of the HAL layer.

Some of the items described by the HAL are:

  • Architecture details, such as word size endianness.
  • Access to common I/O devices, such as the system clock.

  • Thread context details. Initialization of new thread contexts, switching from one thread context to another, and so on.

  • Platform initialization. eCos expects the HAL to initialize the hardware platform.

  • GDB support. Details and management of interacting with the GNU Debugger. In general, eCos doesn't care about this, but the HAL provides it.

  • Cache support. The HAL can provide standardized support for instruction and data caches, the details of which depend on the platform.

  • MMU support. eCos does not require the use of an MMU, nor is any particular support expected. However, on some architectures the MMU must be used, and it is up to the HAL to provide this support. For example, some architectures such as the ARM require the MMU to be enabled for the caches to operate.

  • Interrupt and trap/exception handling, including setting up interrupt handlers, functions to enable and disable interrupts, and the like.

Listing Two shows the kernel function that handles the real-time clock (heartbeat) interrupt. This function contains examples of most of the types of functionality that exist in the HAL layer.

The first item to notice is the block of conditional code (enclosed within the #ifdef/#endif brackets). This code sequence is used to measure interrupt latencies within the eCos system. Because there is additional overhead incurred to measure these values, this code will only be included if selected by the configuration. The #ifdef/#endif sequence shows how most eCos configuration options are used within the code base.

Given that the user wants to measure the interrupt latency, a simple way to estimate it is to compute the value for a typical interrupt source. Most eCos systems have a real-time clock (source of constant ticks or heartbeats). Interrupt latency can be estimated by measuring how much time accrues from when a clock interrupt occurs until it is handled by the clock interrupt routine. This is measured during the interrupt handling routine itself, using the optional HAL function HAL_CLOCK_ LATENCY(). If this macro is defined, it corresponds to a function that returns the number of system clock ticks since the last clock/timer interrupt occurred. If this feature is not defined by the HAL, the system simply does not attempt to measure this event.

The clock interrupt handling function then resets the system clock so it can generate the next heartbeat interrupt. The HAL_CLOCK_RESET() function is used for this purpose. (HAL_CLOCK_RESET() must be provided by all HALs, since this symbol is used unconditionally.) It is defined as a function that resets the system clock/timer so as to generate an interrupt (CYGNUM_HAL_INTERRUPT_RTC) after the specified time interval. In many cases, the HAL_CLOCK_RESET() macro will be empty if the hardware automatically resets after generating an interrupt. This function only needs to be assigned a nonempty definition if the hardware requires some intervention to generate the next interrupt.

Configuring eCos's Scheduler

One area within eCos that makes considerable use of configuration options is the thread scheduler. The standard system includes two completely different scheduler designs. The bitmap scheduler is a simple scheduler that supports a finite number of threads, each with a unique priority. A bitmap is kept of threads that are ready to execute, with one bit per thread (thus the name).

The other standard scheduler is a more traditional, multiple-priority, queue-based scheduler. Using this scheduler, the system supports any number of threads, each with an execution priority within some defined range. More than one thread can have the same priority and will execute on a first-come, first-served basis.

The different schedulers are provided for different environments, with the requirements of the final system dictating the choice. The bitmap scheduler is a good choice if the system has a small number of threads that may be running at any point. It also is the most efficient (in terms of system execution overhead) of the available schedulers. The mlqueue scheduler is appropriate if the number of threads is dynamic or additional features such as time slicing are required.

For both of these schedulers, there are additional configuration options, such as the number of priority levels and whether preemptive time-slicing is supported.

In a traditional system, this level of configuration can be tedious to specify, if even possible. In eCos, ConfigTool manages the configuration information, placing appropriate C/C++ macro definitions into a standard file <pkgconf/kernel.h>, short for "definitions pertaining to the configuration of the kernel package." Implementation modules include this file to apply the actual configuration.

As mentioned, eCos currently provides two different scheduling methodologies, using the configuration system to select the desired one. C++ class inheritance is used to separate the implementation details from the actual APIs being used. The class Cyg_Scheduler exposes the basic scheduler functionality. This is a superclass, based on Cyg_Scheduler_Implementation which is defined by the actual implementation (either bitmap or mlqueue). Figure 4 shows the class hierarchies for these three classes. The *_Implementation classes are supplied by the selected scheduler.

The public APIs are generally supplied by the final derived classes in the hierarchy, which make use of functions provided by the base classes to do this. As much of each class as possible is kept private or protected to prevent leakage. Classes at each abstraction level tend to be friends, so they can use each other's private interfaces.

Listing Three shows the structure of the public scheduler interface. There are separate files, bitmap.cxx and mlqueue.cxx, which provide the actual choice of schedulers. Each of these files provides an implementation of the thread scheduler classes. However, because eCos currently supports only a single scheduler implementation, only one of these files will compile into code. This is achieved by having the entire file controlled by the configuration information, as in Listing Four. In this case, the macro CYGSEM_KERNEL_SCHED_ MLQUEUE is only defined (by ConfigTool) if the user has selected the multilevel queue scheduler.

The implementation file, in this case mlqueue.cxx, contains complete implementations of the scheduler classes. By separating the scheduler functionality into three classes, the implementation can deal with the details more effectively. The Cyg_ThreadQueue_Implementation class provides all of the functionality necessary for the given choice of thread queueing (such as FIFO queueing and so on). Other areas of the scheduler then manipulate thread queues via published interfaces only, allowing for multiple possible implementations. This class structuring also allows for future reuse of various implementations. For example, a scheduler other than mlqueue might use the same thread queue mechanisms. By structuring the code with these distinct classes, such code reuse is more easily achieved.

Conclusion

The eCos configuration system provides control at many levels, from the basic level -- choosing among a set of scheduler implementations, to the most advanced level -- specifying the actual details of the selected scheduling policy. Additionally, the use of C++ classes with inheritance provides for very clean code, separating implementation details from API abstractions.

DDJ

Listing One

/* ---------------------------------------------------------------------
 * {{CFG_DATA
 cdl_package CYGPKG_KERNEL {
     display  "eCos kernel"
     type     boolean
     requires CYGFUN_HAL_COMMON_KERNEL_SUPPORT
     description "
         This package contains the core functionality of the eCos
         kernel. It relies on functionality provided by various HAL
         packages and by the eCos infrastructure. In turn the eCos
         kernel provides support for other packages such as the device
         drivers and the uITRON compatibility layer."
     doc ref/ecos-ref/ecos-kernel-overview.html
 }
 cdl_component CYGPKG_KERNEL_SCHED {
     display "Kernel schedulers"
     type    dummy
     parent  CYGPKG_KERNEL
     description "
         The eCos kernel provides a choice of schedulers. In addition
         there are a number of configuration options to control the
         detailed behaviour of these schedulers.
     "
     doc ref/ecos-ref/ecos-kernel-overview.html#THE-SCHEDULER
 }
 cdl_option CYGSEM_KERNEL_SCHED_MLQUEUE {
     display    "Multi-level queue scheduler"
     type       radio
     parent     CYGPKG_KERNEL_SCHED
     description "
         The multi-level queue scheduler supports multiple priority
         levels and multiple threads at each priority level.
         Preemption between priority levels is automatic. Timeslicing
         within a given priority level is controlled by a separate
         configuration option."
     doc ref/ecos-ref/ecos-kernel-overview.html#THE-SCHEDULER
 }
 cdl_option CYGSEM_KERNEL_SCHED_BITMAP {
     display    "Bitmap scheduler"
     type       radio
     parent     CYGPKG_KERNEL_SCHED
     description "
         The bitmap scheduler supports multiple priority levels, but
         only one thread can exist at each priority level. This means
         that scheduling decisions are very simple to ensure the
         schedulers efficiency. Preemption between priority levels is
         automatic. Timeslicing within a given priority level is
         irrelevant since there can be only one thread at each
         priority level."
     doc ref/ecos-ref/ecos-kernel-overview.html#THE-SCHEDULER
 }
 # NOTE: this option makes sense only if the current scheduler
 # supports multiple priority levels.
 cdl_option CYGNUM_KERNEL_SCHED_PRIORITIES {
     display            "Number of priority levels"
     type               count
     legal_values       1 to 32
     parent             CYGPKG_KERNEL_SCHED
     description "
         This option controls the number of priority levels that are
         available. For some types of schedulers, including the bitmap
         scheduler, this may impose an upper bound on the number of
         threads in the system. For other schedulers, such as the
         mlqueue scheduler, the number of threads is independent from
         the number of priority levels. Note that the lowest priority
         level is normally used only by the idle thread, although
         application threads can run at this priority if necessary."
     doc ref/ecos-ref/ecos-kernel-overview.html#THE-SCHEDULER
 }
  # NOTE: this option makes sense only for some of the schedulers.
  # Timeslicing is irrelevant for bitmap schedulers.
  cdl_option CYGSEM_KERNEL_SCHED_TIMESLICE {
      display           "Scheduler timeslicing"
      parent            CYGPKG_KERNEL_SCHED
      requires          !CYGSEM_KERNEL_SCHED_BITMAP
      requires          CYGVAR_KERNEL_COUNTERS_CLOCK
      description "
          Some schedulers, including the mlqueue scheduler, support
          timeslicing. This means that the kernel will check regularly
          whether or not there is another runnable thread with the
          same priority, and if there is such a thread will do
          an automatic context switch. Not all applications require
          timeslicing, for example, because every thread performs a
          blocking operation regularly. For these applications, it is
          possible to disable timeslicing, which reduces the overhead
          associated with timer interrupts."
  }
  cdl_option CYGNUM_KERNEL_SCHED_TIMESLICE_TICKS {
      display           "Number of clock ticks between timeslices"
      parent            CYGPKG_KERNEL_SCHED
      type              count
      legal_values      1 to 65535
      active_if CYGSEM_KERNEL_SCHED_TIMESLICE
      description "
          Assuming timeslicing is enabled, how frequently should it
          take place? The value of this option corresponds to the
          number of clock ticks that should occur before a timeslice
          takes place, so increasing the value reduces the frequency
          of timeslices."
  }
 }}CFG_DATA */
#define CYGSEM_KERNEL_SCHED_MLQUEUE
#undef  CYGSEM_KERNEL_SCHED_BITMAP
#define CYGNUM_KERNEL_SCHED_PRIORITIES          32
#define CYGSEM_KERNEL_SCHED_TIMESLICE
#define CYGNUM_KERNEL_SCHED_TIMESLICE_TICKS      5

Back to Article

Listing Two

// -------------------------------------------------------------------------
cyg_uint32 Cyg_RealTimeClock::isr(cyg_vector vector, CYG_ADDRWORD data)
{
#ifdef HAL_CLOCK_LATENCY
    if (measure_clock_latency) {
        cyg_int32 delta;
        HAL_CLOCK_LATENCY(&delta);
        // Note: Ignore a latency of 0 when finding min_clock_latency.
        if (delta > 0) {
            // Valid delta measured
            total_clock_latency += delta;
            total_clock_interrupts++;
            if (min_clock_latency > delta) min_clock_latency = delta;
            if (max_clock_latency < delta) max_clock_latency = delta;
        }
    }
#endif
  CYG_INSTRUMENT_CLOCK( ISR, 0, 0);
  HAL_CLOCK_RESET( CYGNUM_HAL_INTERRUPT_RTC, CYGNUM_KERNEL_COUNTERS_RTC_PERIOD
 );
    Cyg_Interrupt::acknowledge_interrupt(CYGNUM_HAL_INTERRUPT_RTC);
    return Cyg_Interrupt::CALL_DSR|Cyg_Interrupt::HANDLED;
}
// -------------------------------------------------------------------------

Back to Article

Listing Three

// Scheduler class. This is the public scheduler interface seen by the
// rest of the kernel.
class Cyg_Scheduler
    : public Cyg_Scheduler_Implementation
{
    friend class Cyg_Thread;
    // This function is the actual implementation of the unlock function. 
    // The unlock() later is an inline shell that deals with the common case.
    static void             unlock_inner();
public:
    // The following API functions are common to all scheduler implementations.
    // claim the preemption lock
    static void             lock();         
    // release the preemption lock and possibly reschedule
    static void             unlock();
    // release the preemption lock without rescheduling
    static void             unlock_simple();
    // return a pointer to the current thread
    static Cyg_Thread       *get_current_thread();
    // Return current value of lock
    static cyg_ucount32 get_sched_lock();
    // Return current number of thread switches
    static cyg_ucount32 get_thread_switches();
    // Start execution of the scheduler
    static void start() __attribute__ ((noreturn));
    // The only  scheduler instance should be this one...
    static Cyg_Scheduler scheduler;
};

Back to Article

Listing Four

//==========================================================================
//      sched/mlqueue.cxx
//      Multi-level queue scheduler class implementation
//==========================================================================
// Description:  This file contains the implementations of
//                  Cyg_Scheduler_Implementation
//                  Cyg_SchedThread_Implementation
//                  Cyg_ThreadQueue_Implementation
//==========================================================================
#include <pkgconf/kernel.h>
#include <cyg/kernel/ktypes.h>         // base kernel types
#include <cyg/infra/cyg_trac.h>        // tracing macros
#include <cyg/infra/cyg_ass.h>         // assertion macros
#include <cyg/kernel/sched.hxx>        // our header
#include <cyg/hal/hal_arch.h>          // Architecture specific definitions
#include <cyg/kernel/thread.inl>       // thread inlines
#include <cyg/kernel/sched.inl>        // scheduler inlines

#ifdef CYGSEM_KERNEL_SCHED_MLQUEUE
  ... actual implementations of scheduler classes go here
#endif // CYGSEM_KERNEL_SCHED_MLQUEUE

Back to Article


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.