Nucleus SE – internals and deployment
This article continues our look at Nucleus SE.
Nucleus SE provides the range of facilities that might be expected in a RTOS.
Firstly, there is the scheduler, which is designed to be simple, but, by having four variants available, maintains flexibility. Support is available for run to completion, round robin, time slice and priority scheduling schemes.
The Nucleus SE API includes nearly 50 service calls, which provide programmer access and control of tasks, memory partitions, signals, event flag groups, semaphores, mailboxes, queues, pipes, system time, application timers and diagnostics.
In addition to simple task scheduling, Nucleus SE (optionally) supports task suspension. This may be “pure” (i.e. as a result of an explicit task suspend API call), it may be a “sleep” function (where the task is suspended for a specific time period) or it may be the result of another API call, where a task is blocked (i.e. conditionally suspended) pending the availability of a kernel resource. Unlike Nucleus RTOS, Nucleus SE does not support timeouts on blocking API calls.
The range of facilities enable a choice to be made from a hierarchy of inter-task synchronization and communication facilities: from semaphores, through signals, event flags, mailboxes and queues/pipes.
By setting the configuration option NUSE_API_PARAMETER_CHECKING, code is included in all API functions to verify parameters – check for null pointers, valid object indexes etc. Since this is extra code, consuming additional memory, it would normally be prudent to activate the option during debug, but turn it off for a production build.
Nucleus SE is designed to be very configurable. This has two facets. Firstly, the kernel can be configured in a very fine-grained way to meet the needs of a specific application – tuning the available functionality and controlling memory utilization are very straightforward. Secondly, the Nucleus SE code is intended to be very portable – between toolkits and between processors.
As clarity and ease of understanding of the Nucleus SE code was a goal, some thought was given to naming conventions. Every symbol in the code has the prefix NUSE_. What follows this string obeys some simple rules.
Every API call function name in Nucleus SE starts NUSE_, which is almost always followed by the type of object in question, then the operation to be performed, all in mixed case and separated by underscores; an example is NUSE_Queue_Send(), which places a message in a queue.
Other Functions and Variables
All other functions and (global) variables in the Nucleus SE code continue to use the NUSE_ prefix, but the remainder of the name does not necessarily have any “structure”. This is unimportant to the normal user of the kernel, as the API is sole recommended interface into the code.
Since Nucleus SE is configured by means of #define symbols, these too obey the naming rules. They are also solely in upper case. The API call enablers have exactly the same name as the functions themselves (except for the case); an example is NUSE_QUEUE_SEND.
Other #define Symbols
Any other #define symbols – API call parameter and status return values, for example – that may be used by application code, follow the same conventions; they start with NUSE_ and are in upper case. An example is NUSE_SUCCESS.
All RTOSes maintain a number of data structures that describe kernel objects. In most implementations, these are in the form of C structures, which are typically organized into linked lists – often doubly and maybe circularly linked. This makes sense, as all the relevant data is encapsulated conveniently and list members may be added or removed, as objects are created and deleted.
In Nucleus SE, objects are all static, so arranging the object data structures into a simple list was an obvious optimization. This saved the space and complexity of forward and backward pointers. However, I decided to take the optimization one step further and not use structures at all; in Nucleus SE, all the kernel object data is represented by several simple arrays (often referred to as tables) of various types – one or more for each type of object. There were several reasons for this strategy:
Nucleus SE was intended to be “8-bit friendly”. Most small CPUs do not have an optimal means for a compiler to implement C structures. Simple arrays are much more efficient.
Since a maximum of 16 of each object type is permitted, addressing the elements of each of these arrays requires four bits – often a byte is used. This is more efficient than an address, which are normally 16 or 32 bits.
It is required that object data, which is constant, is kept in ROM, and not copied into RAM. Since a structure cannot (in conventional, portable C) be split between ROM and RAM, each object type might require two structures, which is overly complex. In Nucleus SE, object description tables may each be in either ROM or RAM, as required.
Because of the great configurability of Nucleus SE (“extreme scalability”), some object description data may be optional, depending on the selected facilities. This results in wide use of conditional compilation. A structure definition with conditional compilation directives embedded within it tends to be very hard to understand. Controlling the instantiation of individual arrays this way is quite readable.
All the object data tables adhere to the hierarchical naming convention idea mentioned earlier in this article. So, understanding which tables logically belong together is quite straightforward.