Other RTOS services
So far in the RTOS Revealed series, we have largely discussed the functionality of a kernel in terms of running tasks and communication between them. In this article, we will expand on that and consider what else a kernel can do, which is largely manifest in a number of other API calls that may be available. We will also take a look at what lies beyond a kernel: What makes a kernel become an operating system?
Beyond task scheduling and communication, an RTOS will undoubtedly include functionality – API calls – to control tasks in a selection of ways. We can look at some of the possibilities.
Task Creation and Deletion – In a “dynamic” RTOS, there will be calls to enable the creation of tasks (and other RTOS objects), as and when they are required. Such calls include a wide range of parameters which define the task; for example: code entry point, stack size, and priority. A corresponding task deletion API call enables resources to be release after the task is no longer required.
In a “static” RTOS, the defining parameters of a task are set up in some kind of configuration file at build time.
Task Suspend and Resume – As we have seen, most RTOSes have the concept of a “suspended” state for tasks. This may be entered in a variety of ways. One is for an explicit call to a Task Suspend API function. This may be called by the task itself or by another task. A corresponding Resume Task call enables the task to be a candidate for scheduling again.
Task Sleep – In a real-time system, the control of time is obviously a requirement and can take many forms. A simple one is the ability for a task to “sleep” – i.e. the task is suspended for a specific period of time. When that time has elapsed, the task is “woken” and is a candidate for scheduling again. An API call will generally be available for this purpose. Of course, this functionality is dependent upon the availability of a real-time clock.
Relinquish – When using a Round Robin scheduler, a task may wish to give up control of the processor to the next task in the chain. A Task Relinquish API will be available for this purpose. The task is not suspended – it is available for scheduling when its turn comes around again. With a time-sliced scheduler, it may equally be desirable for a task to relinquish the remainder of its time slot, if it has no further useful work to do immediately. A task relinquish has no logical meaning with a Run to Completion or Priority scheduler.
Task Termination – As we saw in a previous article, in addition to being Ready or Suspended, an RTOS may support other task states. A task may be “Finished”, which means its main function has simply exited – no special API call is required. A task may be “Terminated”, which means that it is not available for scheduling and must be reset in order to be available to run again – see Task Reset below. This requires a specific API call. The availability of these additional task states, the terminology used, and their precise definitions will vary widely from one RTOS to another.
Task Reset – Many RTOSes offer a Task Reset API call, which enables a task to be returned to its start-up condition. It may be in a Suspended state and require a Task Resume in order to become a scheduling candidate.
Task Priority etc. – In a dynamic RTOS, API calls are likely to be available to adjust several task characteristics at runtime. Examples include priority and time-slice duration.
There will more information about task control in a future article, when we look at its implementation.
An RTOS will provide a range of API calls to provide tasks with information about the system, including:
Task information – how many tasks are in the system and details of their configuration and current status.
Information about other kernel objects – how many of each type of object is in the system and object-specific configuration and status information. For example:
- What is the current capacity of a queue to accept more messages?
- How many tasks are suspended on a specific mailbox?
RTOS version information. It is common for an API call to be available that returns such data.
In many applications, it is useful for a program to be able to dynamically grab some memory when it is required, and release it again when it is no longer needed. Embedded software is no exception. However, the conventional approaches are prone to problems, which in desktop applications are unlikely or just inconvenient, but may be disastrous in an embedded system. There are ways to provide this kind of facility, even in a static RTOS.
Problems with malloc() and free()
In a desktop C program, a function can call malloc(), specifying how much memory is required and receive back a pointer to the storage area. Having made use of the memory, it may be relinquished with a call to free(). The memory is allocated from an area called the “heap”. The problem with this approach is that with an uncoordinated sequence of calls to these functions, the heap may easily become fragmented and memory allocations will then fail, even if enough memory is available, because contiguous areas are not large enough. In some systems (like Java and Visual Basic), elaborate “garbage collection” schemes are employed to effect defragmentation. The problems with these schemes are that it can result in very significant, unpredictable runtime delays and the need to use indirect pointers (which is not the way that C works). If malloc() and free() were implemented in a reentrant way (they usually are not) and used by RTOS tasks, the fragmentation would occur very rapidly and system failure would be almost inevitable.
In C++, there are operators, new and delete, which perform broadly the same functions as malloc() and free(). They also suffer from the same limitations and problems.