Task Identifiers
You must be able to identify each task in the system. This requirement is important for other kernel objects, but there are some nuances in the tasks that correspond to the topic of this article.

RTOS developers use different approaches to identifying tasks, but four general strategies can be distinguished:
- A task is identified with a pointer to its “control block”. Pointers are always unique and easy to use, since access to the control block is required for many API calls. This implies that all task data is stored in random access memory (RAM), which may be inefficient. The pointer usually takes about 32 bits of memory.
- A task can be defined using an arbitrary “ordinal number” (index number). This value can be useful when granting access to records in certain tables. Such an identifier can occupy eight or fewer bits of memory, depending on the restrictions on the number of tasks that are supported by the RTOS.
- Some RTOS allow only one task at each priority level and, therefore, use priority to uniquely identify a task. This means that the priority of the task cannot be changed. This approach is a variation of the previous approach.
- Tasks can have names that are character strings. This may be useful for debugging, but is unlikely to be an effective means of uniquely identifying a task. RTOSs that support task naming usually have an additional identifier (such as a pointer) that is used by API calls, etc. For most embedded systems, text names are overhead; A good debugger allows you to call them locally on a host.
Previous articles in the series:
Article # 3. Tasks and planningArticle # 2. RTOS: Structure and Real Time
Article # 1. RTOS: introduction.
Context switch
Context switching is a procedure for transferring control from one task to another. This topic is worth exploring more closely, since the way context switching works is a fundamental principle of how a RTOS works.
What is the challenge?
We know that a task is a quasi-independent program that shares processor time with a number of other tasks under the control of an RTOS. But you need to think about what really characterizes the task.
Register set
A task is, ultimately, a unique set of processor register values. They are either loaded into the registers of the processor (that is, the task is current), or stored somewhere until the scheduled execution time. In an ideal world, a CPU would have several sets of registers, and each could be assigned to a separate task. This was implemented for special cases. Many years ago, Texas Instruments' TI 9900 series had many sets of registers for each task, but they were implemented in main memory, which limited performance. The SPARC architecture (formerly used in Unix desktop systems) supports many sets of registers in the “ring structure”, but the number of sets is still limited.
Internal data
The task will probably have its own stack, the size of which can be set separately for each task or it can be a global parameter for all tasks in the system. This, along with registers, provides data storage for specific tasks. There may be other areas of memory for storing data intended for a specific task.
Common Resources
Virtually any resources can be shared between tasks. The code can be general: either certain functions, or the entire task code. It is necessary to make sure that the code is reentrant, in the first place, static variables (indicated as static or just outside the function) should not be used. Be careful with standard library modules that are not intended for embedded use; they usually have many non-reentrant functions.
It is also possible to share data, but you need to carefully control access to them. Ideally, only one task is the “owner” of the data at any given time.
How to keep context
When a task is rescheduled (that is, it ceases to be current), its set of registers must be saved somewhere. There are at least two possibilities:
- Registers can be stored in a special table for tasks. Can be part of a task control block (Task Control Block, TCB). Size is a predictable and constant value (for a particular CPU architecture).
- Registers can be pushed onto the task stack. This requires allocating sufficient additional stack space and ensuring that the pointer is stored (possibly in the TCB).
The choice of mechanism depends on the characteristics of the particular RTOS and on the target processor. Some (usually 32-bit) devices can effectively access the stack; the rest (for example, 8-bit) can be more optimal when working with tables.
Dynamic task creation
The main aspect of the RTOS architecture is that the RTOS is either “static” or “dynamic”.
When using static RTOS, everything is determined at the time of building the application, in particular, the number of tasks in the system. This is a logical solution for embedded applications that usually have limited functionality.
Dynamic RTOS triggers one task (which can be a specialized “primary” task), and also creates and deletes other tasks as needed. This allows the system to adapt to changing requirements and is a closer analogue of the desktop system, which behaves in this way. The static / dynamic view also applies to other kernel objects.
Requirement for dynamic task creation
This feature is included in most commercial RTOS. However, only a small part of the application really needs dynamic operation. Very often, the system starts up, creates all the necessary tasks (and other objects), and then never creates anything else or destroys the application code during operation. The ability to create dynamic tasks has become a formality. One supplier introduced it; all others followed suit.
It is noteworthy that the OSEK / VDX standard requires a static architecture, even though it can be applied to fairly complex applications. The result of these requirements is the inability to implement OSEK / VDX using a wrapper, an intermediate layer on top of the usual (dynamic) RTOS.
The pitfalls of dynamic task creation
There are several problems associated with the dynamic mode of operation, which may cause concern.
First, the system becomes more complex, which means that additional information is needed for data structures describing tasks (TCBs). As a rule, they are implemented in the form of bidirectional lists, which leads to costs associated with the amount of memory.
All data describing the task should be stored in RAM. This is inefficient, since most of them may simply be permanent data elements copied from ROM. In addition, on lower-level processors (microcontrollers) may not get RAM.
Perhaps the most worrying is the possibility of an unpredictable shortage of resources, which may lead to the impossibility of creating new objects. Since the essence of the real-time system is its predictability, this is unacceptable. Therefore, care must be taken when using the creation of dynamic tasks (and other objects).
Interruptions
It is possible that an embedded real-time system can be implemented without using interrupts, but this is not typical.
Interrupts and core
When an RTOS is used, the interrupt handler (ISR) is made as easy as possible in order to “steal” the minimum amount of processor time from scheduled tasks. Often, the device can simply be serviced, and any desired task will be queued for processing. In addition, it is difficult to speak in general about interruptions and their interaction with the cores, simply because they vary greatly. On the one hand, the RTOS developer can make interrupts not relate to the kernel at all, and the programmer will have to make sure not to overload the task scheduler using a lot of CPU time in the ISR. On the other hand, the RTOS can fully control the entire interrupt subsystem. None of the approaches described is right or wrong, they are just different.
Saving context
The ISR always needs to maintain a “context” so that the code being interrupted is not affected by the ISR calculations. In a system implemented without RTOS, it is simply a matter of saving any registers used by the ISR (usually on the stack) and restoring them before returning. Some processors have a dedicated ISR stack, others simply use the same stack as the application code.
When using RTOS, the approach may be exactly the same. In the same way, the stack used by the ISR can be “borrowed” from the current task, or it can be another stack allocated for interrupts. Some cores implement this feature, even if the processor itself does not support the interrupt stack. The situation is complicated if the ISR makes an API call that affects the task scheduler. This may cause the interrupt to return to another task from the one that was started when the interrupt occurred.
Interrupts and Scheduler
There are several circumstances in which the ISR execution code can return to another task:
- The ISR can assign a higher priority to an already completed task, rather than the current one, if the priority scheduler is used.
- The ISR may suspend the current task.
- With the help of the time scheduler (English Time-Slice, TS), the system timer interrupt handler will manage the time intervals and can call the scheduler if necessary.
Clock timer (tick clock)
In embedded systems, the use of a periodic "clock timer" (time slice) is often encountered. In some RTOS, it is a required component. Typically, the presence of a clock timer is optional, and its absence simply excludes the availability of certain services. The timer interrupt handler typically provides four functionalities:
- If the time scheduler is used, the timer interrupt handler will manage the time counter and schedule a new task each time it expires.
- Provides support for system time. Basically, it is a 32-bit variable that is incremented with a timer and can be preset or requested by tasks.
- If the RTOS provides applications with work with timers, then it will be supported by a timer interrupt handler that will be responsible for expiration and rescheduling.
- If the RTOS maintains timeouts in blocking API calls or tasks can be in the “sleep” state, then these time intervals will be supported by the timer interrupt handler.
When we worked on our own real-time operating system, the MAXROS MAKS (previously published articles about it), our team “stumbled” on the blog of Colin Walls, an expert in the field of microelectronics and firmware of Mentor Graphics. The articles seemed interesting, they were translated for themselves, but in order not to “write to the table” they decided to publish. I hope they will also be useful to you. If so, then we plan to publish all the translated articles in the series.
About the author: Colin Walls has been working in the electronics industry for more than thirty years, spending a significant amount of time on embedded software. He is now an embedded software engineer in Mentor Embedded (a division of Mentor Graphics). Colin Walls often speaks at conferences and seminars, author of numerous technical articles and two books on embedded software. Lives in the UK. Colin's professional blog: blogs.mentor.com/colinwalls, e-mail: colin_walls@mentor.com
Read the
first, second and
third articles of the cycle, published earlier.