Task identifiers in embedded operating systems
As I have been working on the RTOS Revealed series of articles in recent weeks, I have been thinking about the operation and functionality of operating systems. It is a very broad subject – hence the plans for a long series of articles – but I thought that some specific areas might be usefully discussed in this, more compact, context. A topic, that is not often considered, is the question of task identifiers …
All RTOS objects need to be identified in some way. There are various approaches that are used in different operating systems. In some ways, tasks are “special” RTOS objects, if only because they are the only type of entity that is non-optional. Tasks also present a number of possibilities for identification:
- A pointer to the “task control block” [TCB].
- An arbitrary “index number”.
- Task priority level is also the ID [in an OS which allows a single task with each priority level].
- A character string.
Each of these options has been employed in at least one RTOS and they all have pros and cons:
The most common and apparently logical option is #1. Such a pointer would always be unique and is likely to be required for various API calls, which need access to the TCB. There are some efficiency downsides. This approach probably makes the assumption that the TCB is a contiguous block of memory [RAM] and this may not be the most efficient option. Also, the pointer is likely to use 32 bits of storage itself, which is over-kill.
Option #2 may be more efficient, as it can be used to index into multiple tables, which may be located in the optimum memory locations. Depending on the number of tasks supported by the RTOS, the index number may only require 8 bits of storage. Including error checking is simpler with an index, which can be range-checked more readily than a pointer can be verified.
Although it is, in effect, a variant of #2, option #3 is quite widely used, as it turns out to simplify the design of a scheduler if only one task is allowed to occupy each priority level. This also implies, of course, that a task’s priority is fixed.
The use of textual names – option #4 – is not widely used, as, by itself, it is not efficient. Although readable identifiers are useful for debugging, the overhead of keeping this text on the target is quite unnecessary. The host-based debugger should handle such matters.
An obvious question, at this point, is what do real RTOS products do? Naturally, the example, to which I would turn, is Mentor Embedded’s own Nucleus RTOS. Nucleus tends to handle all RTOS objects in a consistent way. All objects have control blocks and pointers to these are used to identify specific objects. The types of applications, for which Nucleus is normally employed, would normally keep all data in RAM, so contiguous control blocks are not an issue. Also, the number of each type of object that may be included in a given system is essentially indefinite, so the size of a pointer is not intrinsically an overhead.
The prototype for the API call for creating tasks in Nucleus looks like this:
As you can see, the call appears quite complex, but it is actually quite straightforward, after a little study. We will only concern ourselves with the first two parameters. The first one, task, is a pointer to a variable of type NU_TASK and this is the TCB for the task being created. The second parameter is a pointer to a null-terminated text string, which is also used to identify the task in a readable way. This string is not widely used in modern implementations of Nucleus-based systems.
This is my last posting of 2016, so I would like to wish everyone a great holiday and may we all have a peaceful and prosperous 2017.