ImageCraft's upcoming RTOS (Real Time Operating System) product is based on a message-passing kernel. The working code name for the product is eMOS, short for Embedded Message-Passing OS, the eventual product name may differ. In this post, we will examine some of the advantages of using a message-passing kernel (we will explain WHY we are writing our own RTOS - and describe its architecture - in a later post.)

While most traditional RTOS and desktop/server OSes are "monolithic" in design (i.e. the kernel includes a large number of kernel functions), a message-passing kernel includes only a small set of functions, as most functions of a monolithic kernel may instead be written to run in non-kernel space as normal tasks. While not as widely used as monolithic kernels, there are significant design, peformance, and robustness advantages to a message-passing kernel. As an example, QNX, the kernel behind BlackBerry smartphone's BB OS, is one of the most well known message-passing kernels.

Let's look at how message passing can replace the requirement for certain functions in kernel space; for example: memory allocation using something similar to C's malloc and free library calls. In a monolithic kernel, if the low level memory management routines are outside of the kernel space, then certain code regions must be protected so that the internal data structures will not be corrupted by multiple tasks modifying the same data due to task switching.

Hence, monolithic kernels usually include low-level memory management routines as kernel functions. By running them in kernel space, the kernel provides automatic protection for the memory management data structure. However, this increases the size of the kernel, and limits the ability to replace memory management routines with others best suited for a given situation. For example, one may wish to use best-fit or first-fit or other algorithms to manage memory allocation. Having these fixed in the kernel code delivered by the RTOS vendor limits replacibility.

A message-passing kernel provides a better alternative. By using message-passing mechanisms, memory management routines run as normal tasks:

In eMOS, a message is an arbitrary-length buffer. The sender uses eMOS_MsgSend to send a message, and the receiver uses eMOS_MsgReceive to receive a message. A task sending a message using eMOS_MsgSend waits until the receiver both receives the message and sends a reply using eMOS_MsgReply. The message passing calls are synchronous, and the calls do not return until the the functions are done. Internally, the kernel schedules tasks as needed, and tasks waiting for message processing are blocked and will not use up CPU time.

In the above example, MemManager is a normal eMOS task acting as a memory management server. It loops forever while waiting for messages to come in. Each message is a memory management request. The command code 0 is used to denote an allocation request, and 1 is for deallocation (the codes can be anything, as long as the sender and receiver agree on their meanings).malloc and free send messages to MemManager to perform the actual memory allocation. If all memory requests go to this task, the internal data structures are automatically protected, because the server only processes one request at a time, and additional requests through eMOS_MsgSend are queued up by the kernel until the server task is able to process those requests.

Moreover, since it is not part of the kernel, MemManager can be replaced with other memory allocation algorithms, even if the source code for the kernel is not provided. This allows tailoring the most useful memory allocation scheme to a given scenario.

Using message passing has some overhead, but the tradeoffs are more robustness and better scalability. As more and more embedded systems are used in mission-critical situations, safety concerns must be high in the design goals of a system. For example, two potential nasty source of errors in an RTOS are race conditions and priority inversion (for example: priority inversion caused the Mars Pathfinder Rover to crash repeatedly, until an software patch was sent from Earth) and their chances of occurring are greatly reduced in a message-passing OS. (We will look at these issues, and more, in a future post.)

The wifi2go Internet of Things module combines a STM32F411 M4 MCU with the industry leading TI CC3100 Wi-Fi device. This Design Article I wrote for Embedded Computing describes some of the design decisions.

The wifi2go Internet of Things module combines a STM32F411 M4 MCU with the industry leading TI CC3100 Wi-Fi device. This Design Article I wrote for Embedded Computing describes some of the design decisions.

This is a sequel to a previous blog post in which we examined the basics of a multitasking executive for the Cortex-M. In this post, we look at the remaining issues, and how a multitasking executive we call nanoexec, which we have uploaded to Github, solves them.

NanoExec Functions

Nanoexec provides just two external functions: one to create a task, and one to start the multitasking executive. These functions (and a few define constants) are declared in the header file nanoexec.h

The first argument to nanoexec_NewTask, “p”, is the function code, while “arg” is the argument passed to the task function when it starts running. “stack_addr” is the address of the stack to be used by the task, and “stack_size” is the size of the stack. See usage example in the later part of this post.