For the remainder of the RTOS Revealed series, we will be looking in detail at how an RTOS is implemented and deployed. To do that, we will work with a specific RTOS: Nucleus SE. Even if you have no intention of using this kernel, or others related to it, understanding how it works will give you a good basis for coming to grips with any RTOS.
To understand why Nucleus SE was designed the way it is, it is useful to outline the design goals of the kernel and the objectives that I had in embarking upon the project:
Simplicity – The code of the kernel should be simple, clear, well commented and documented, and thus easy to understand. Nucleus SE is primarily intended to be useful in an educational context.
Size – It should be a small, very scalable kernel (as memory, particularly RAM, might be in short supply).
Functionality – It should have a useful level of functionality, supporting standard RTOS facilities.
8/16-bit Support – It should be 8/16-bit “friendly”: byte-size data should be used wherever possible; data structures should not require exotic memory addressing modes; constant data should not be copied to RAM unnecessarily.
Future – There should be a growth path from Nucleus SE to Nucleusâ RTOS. Users should be able to easily port code between the kernels. More importantly, their expertise should be portable. The Nucleus SE API effectively implements a subset of the Nucleus RTOS application programmer interface (API).
Cost – The business model needed to be attractive to all potential users: 8/16-bit device developers, first time RTOS users and those learning about RTOS technology. It was decided to make Nucleus SE freely available and royalty free for both commercial and educational applications; you may use and modify the code in any way that you wish.
Nucleus SE Target Users
The result of this approach was a kernel that may be useful by three kinds of developers:
Programmers of 8/16-bit devices, who have a need for a simple kernel or task scheduler. It is particularly attractive if such developers are keen to acquire some RTOS usage skills or if the development is of a system where other 32-bit devices are in use, where Nucleus RTOS may be a good choice.
Developers of embedded applications employing 32-bit devices where the software complexity does not merit the cost of a conventional, commercial RTOS. Utilizing Nucleus SE may provide some useful facilities, while offering a growth path (to Nucleus RTOS) if the application complexity increases.
Students in education and training contexts may find Nucleus SE a useful basis for the study of RTOSes. The skills acquired are useful later, when they commence employment in the “real world”.
Design Decisions and Trade-offs
To achieve the aforementioned goals, several carefully considered design decisions were necessary. Details of these will be included later, when the specific features are covered, but this is a summary of the key issues.
Nucleus SE is a statically configured RTOS – i.e. all the decisions about configuration are made at build time, not dynamically at runtime. This has multiple benefits including the simplification of data structures and a reduction on code size, as use of simplified data has this side-effect and there is no need for “create” and “delete” API calls. For most applications, dynamic object creation is really not required.
The number of objects of each type is limited in a Nucleus SE application. There may be between one and sixteen tasks and between zero and sixteen of each other type of kernel object. This simplifies object addressing (see below). This limitation is not a constraint to the small applications for which the kernel is intended.
Objects are addressed by means of an “index”, which can have values from zero to fifteen. Compared with the conventional use of pointers, this can be more efficient on small processors and uses less storage – an index requires only 4 bits of storage; an address is 16-32 bits.
An area of the kernel’s design, that was subjected to careful simplification, was the scheduler. Instead of providing a flexible mechanism with priority scheduling, round robin and time slicing options, four separate scheduler types are available; the specific scheduler for an application is selected at configuration time.
Some functionality, which is available in Nucleus RTOS, has not been implemented in Nucleus SE. In some cases, this may just be for simplicity. In other cases, a small loss of functionality in one area renders something else much simpler to implement. These incompatibilities are highlighted in relevant articles in the series.
Since limited memory applications needed to be supported by Nucleus SE, some care was given to its memory utilization. A “classical” ROM and RAM memory architecture was assumed – ROM being used for code and constant data; RAM containing variables, stack etc. Although a specific target may have a different scheme, the Nucleus SE code is quite flexible; #defines (ROM and RAM ) are used to prefix all variables and data structures to specify their location. How this is achieved is a toolkit issue.
A key requirement was to avoid unnecessary copying of data from ROM to RAM, as RAM is likely to be in short supply. The mechanism by which this is achieved is outlined in Data Structures in the next article in this series.
The API for Nucleus SE is implemented in a conventional way: a C function implements each API call. These calls are grouped logically.
Although Nucleus SE API calls are not precisely the same as Nucleus RTOS, the broad functionality is emulated and mapping between the APIs is very simple. Details of the Nucleus RTOS API will be included.
The code for many API function calls includes sequences of instructions that manipulate kernel data. Commonly, the data may be in an invalid state during the course of these instructions, so care must be taken to avoid an interrupt occurring. Or, specifically, no code from another task or an interrupt service routine may be allowed to run, if it might conceivably access this (currently invalid) data. Such sequences of instructions are termed critical sections.
A pair of macros are defined called NUSE_CS_Enter() and NUSE_CS_Exit() . All Nucleus SE API function code uses these to enclose a critical section, thus:
Typically, the definition of these macros will effect a disable interrupts instruction and an enable interrupts instruction respectively. This will need to be reviewed if Nucleus SE is implemented on a different CPU architecture. Further detail on porting Nucleus SE will be included later in the series.
Like all modern RTOSes, Nucleus SE is scalable. The usual technique employed, to ensure that only the used RTOS components are included, is to provide all the API functions as a library. Thus, at link time, only the referenced functions are extracted and included in the memory footprint. Nucleus RTOS uses this approach for both the kernel and all the other OS components. Nucleus SE uses a different technique.
Instead of relying on a library facility in the chosen toolkit, the Nucleus SE distribution source files all contain conditional compilation directives. To configure Nucleus SE for an application, the user needs to set a number of #define symbols (more on this in the next article). This determines which API functions are compiled and, hence, included into the application.
Nucleus SE takes this approach one step further, offering a facility that I call “extreme scalability”. Several aspects of the kernel’s functionality can be turned on and off or tuned in other ways using similar #define symbols. The user thus has a very fine-grained control over the memory usage.
Nucleus SE has its own “native” API, which will be described in detail in future articles. For many users, just incorporating these API function calls into their code will be quite satisfactory.
Some users may prefer to use another API – either a standard or simply one with which they are familiar. The Nucleus SE API is sufficiently flexible that constructing a wrapper to map another API is likely to be quite straightforward.
One of the design goals of Nucleus SE was a high degree of compatibility – at the user level – with Nucleus RTOS. Although its API is different, it is designed so that the mapping is quite straightforward. A wrapper facilitating the use of the Nucleus RTOS API on Nucleus SE will be available.
A subsequent article continues this look at Nucleus SE with a focus on some of its internal structure and deployment of the RTOS.
Colin Walls has over thirty years experience in the electronics industry, largely dedicated to embedded software. A frequent presenter at conferences and seminars and author of numerous technical articles and two books on embedded software, Colin is an embedded software technologist with Mentor Embedded [the Mentor Graphics Embedded Software Division], and is based in the UK. His regular blog is located at: http://blogs.mentor.com/colinwalls. He may be reached by email at firstname.lastname@example.org