
In previous articles, we looked at the multitasking model, and found that each task is a quasi-independent program. Although tasks in embedded systems have a certain degree of independence, this does not mean that they do not “know” about each other. Some tasks will be truly isolated from others, but interaction and synchronization between them is a common requirement. This mechanism is one of the key functions of the RTOS. The range of functions may vary depending on the RTOS, so in this article we will look at publicly available options.
Previous articles in the series:
Article # 4. Tasks, context switching and interruptsArticle # 3. Tasks and planningArticle # 2. RTOS: Structure and Real Time
Article # 1. RTOS: introduction.
Function range
There are three models of inter-task interaction and synchronization:
- Services are tied to tasks: RTOS empowers tasks with attributes that provide interaction between them. As an example, consider the signals.
- Kernel objects are autonomous means of communication or synchronization. Examples: event flags, mailboxes, queues / channels, semaphores, and mutexes.
- Messaging is a streamlined scheme in which an RTOS allows you to create message objects and transfer them from one task to another or several. This is fundamental to the architecture of the kernel, and therefore such a system is called "messaging RTOS".
Mechanisms that are ideal for different processes will vary. Their capabilities may overlap, so you should think about the scalability of these models. For example, if an application requires multiple queues, but only one mailbox, you can implement a mailbox with a queue for one item. This object will not be entirely optimal, but the entire code of the mailbox will not be included in the application, and, therefore, scalability will reduce the amount of memory occupied by the RTOS.
Common variables or memory areas
A simplified approach to the interaction between tasks is the presence of variables or memory areas in the system that are available for all tasks. This approach may be applicable to several processes, despite its simplicity. It is necessary to control access. If a variable is simply a byte, then writing to or reading from it is likely to be an atomic operation (that is, continuous), but care must be taken if the processor allows other operations on memory bytes, since they may be interrupted and may occur synchronization problem. One way to implement lock / unlock is to disable interrupts for a short time.
If a memory area is used, a lock is still needed. The use of the first byte as a blocking flag is allowed, given that the memory architecture provides atomic access to this byte. One task loads data into a memory area, sets a flag and then waits for it to be reset. Another task waits for setting the flag, reads the data, and clears the flag. Using interrupt disable as a lock is less reasonable, since moving the entire data buffer may take some time.
Such use of shared memory is similar to the implementation of many interprocessor communications in multi-core systems. In some cases, a hardware lock and / or interrupt is built into the interprocessor interface of shared memory.
Signals
Signals are one of the simplest mechanisms for the interaction between tasks offered by traditional RTOS. They contain a set of bit flags (8, 16 or 32, depending on the specific application), which is associated with a specific task.
A signal flag (or several flags) can be set for any task using the logical OR operation. The flag (s) can only be read by the task that contains the signal. The reading process is usually destructive, that is, the flags are also reset.
In some systems, signals are implemented in a more complex way, so that a special function assigned by a task-owner of a signal is automatically executed when any signal flags are set. This eliminates the need for the task to control the flags itself. This is somewhat similar to an interrupt handler.
Event flag groups
Groups of event flags are similar to signals in that they are bit-oriented means for interacting between tasks. Similarly, they can contain 8, 16, or 32 bits. Unlike signals, they are independent kernel objects and do not “belong” to any particular task. Any task can set and reset event flags using the logical operations "OR" and "AND". Similarly, any task can check event flags using the same operations. In many RTOSs, you can make a blocking API call for a combination of event flags. That is, a task can be suspended until a specific combination of event flags is set. The “consume” option may also be available when checking event flags, which resets all flags.
Semaphores
Semaphores are independent kernel objects used for resource accounting. There are two types of semaphores: binary (can have only two values) and common (unlimited number of values). Some processors support (atomic) instructions that facilitate the fast implementation of binary semaphores. Binary semaphores can be implemented as generic semaphores with a value of 1.
Any task can attempt to assign a semaphore to access the resource. If the current value of the semaphore is greater than 0 (the semaphore is free), the value of the counter is reduced by 1, therefore, the assignment will be successful. In many operating systems, you can use a blocking mechanism to assign a semaphore. This means that a task can be in a wait state until the semaphore is released by another task. Any task can release the semaphore, and then the semaphore value will increase.
Mailboxes
Mailboxes are independent kernel objects that provide the means to transfer messages. The size of the message depends on the implementation, but usually it is fixed. Typical message sizes are from one to four elements the size of a pointer. As a rule, a pointer to more complex data is sent through the mailbox. Some cores implement mailboxes in such a way that data is simply stored in a regular variable and the kernel controls access to it. Mailboxes can also be called "exchange", although this name is now rarely found.
Any task can send messages to the mailbox, which is then filled. If the task tries to send a message to the filled in mailbox, it will receive an error response. In many RTOSs, you can use a blocking mechanism to send to the mailbox. This means that the task will be suspended until the message in the mailbox is read. Any task can read messages from the mailbox, after which it is emptied. If the task tries to read from an empty mailbox, it will receive an error response. In many RTOSs, you can use a blocking call to read from the mailbox. This means that the task will be suspended until a new message appears in the mailbox.
Some RTOSs support a “broadcast” function. This allows you to send messages to all tasks that are currently suspended while reading a specific mailbox.
Some RTOS do not support mailboxes at all. Instead, it is recommended to use a single item queue. This is functionally equivalent, but incurs additional overhead for memory and execution time.
Queues
Queues are independent kernel objects that provide a mechanism for sending messages. They are a bit more flexible and complex than mailboxes. The size of the message depends on the implementation, but usually it is fixed and word / pointer oriented.
Any task can send messages to the queue, and this can be repeated until the queue is full, after which any sending attempts will result in an error. The length of the queue is usually determined by the user when creating it or setting up the system. In many RTOSs, you can use a blocking mechanism to send to the queue. That is, if the queue is full, the task can be suspended until the message in the queue is read by another task. Any task can read messages from the queue. Messages are read in the same order in which they were sent (First in - First out, FIFO). If the task tries to read from an empty queue, it will receive an error response. In many RTOSs, you can use a blocking mechanism to read from an empty queue. That is, if the queue is empty, the task can be suspended until the message is sent to the queue by another task.
Most likely, the RTOS will have a mechanism for sending a message to the top of the queue, this is called “jamming”. Some RTOS also support the “broadcast” function. This allows you to send messages to all tasks suspended while reading a queue.
In addition, a RTOS can support the sending and reading of variable length messages. This gives more flexibility, but entails additional overhead.
Many RTOSs support a different type of core object, the “pipes”. In essence, the channel is similar to the queue, but it processes byte-oriented data.
The functionality of the queues is not of interest, but it should be understood that they have more overhead for memory and execution time than mailboxes, primarily because you need to save two pointers: the beginning and the end of the queue.
Mutexes
Mutexes (mutually exclusive semaphores) are independent kernel objects that behave in a very similar manner to regular binary semaphores. They are slightly more complex than semaphores and include the concept of temporary ownership (a resource, access to which is controlled). If a task assigns a mutex, only the same task can release it again: the mutex (and therefore the resource) temporarily belongs to the task.
Mutexes are not provided by all RTOSs, but a regular binary semaphore is fairly simple to adapt. You need to write the mutex obtain function, which assigns the semaphore and assigns the task identifier. Then the additional function “mutex release” checks the identifier of the calling task and releases the semaphore only if it matches the stored value, otherwise it returns an error.
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.comRead the
first, second, third, fourth articles published earlier.