Inter-task communication and synchronization
Semaphores are independent kernel objects, which provide a flagging mechanism that is generally used to control access to a resource. There are broadly two types: binary semaphores (that just have two states) and counting semaphores (that have an arbitrary number of states). Some processors support (atomic) instructions that facilitate the easy implementation of binary semaphores. Binary semaphores may also be viewed as counting semaphores with a count limit of 1.
Any task may attempt to obtain a semaphore in order to gain access to a resource. If the current semaphore value is greater than 0, the obtain will be successful, which decrements the semaphore value. In many OSes, it is possible to make a blocking call to obtain a semaphore; this means that a task may be suspended until the semaphore is released by another task. Any task may release a semaphore, which increments its value.
There is more information about semaphores in a future article, which describes their implementation in Nucleus SE.
Mailboxes are independent kernel objects, which provide a means for tasks to transfer messages. The message size depends on the implementation, but will normally be fixed. One to four pointer-sized items are typical message sizes. Commonly, a pointer to some more complex data is sent via a mailbox. Some kernels implement mailboxes so that the data is just stored in a regular variable and the kernel manages access to it. Mailboxes may also be called “exchanges”, though this name is now uncommon.
Any task may send to a mailbox, which is then full. If a task then tries to send to send to a full mailbox, it will receive an error response. In many RTOSes, it is possible to make a blocking call to send to a mailbox; this means that a task may be suspended until the mailbox is read by another task. Any task may read from a mailbox, which renders it empty again. If a task tries read from an empty mailbox, it will receive an error response. In many RTOSes, it is possible to make a blocking call to read from a mailbox; this means that a task may be suspended until the mailbox is filled by another task.
Some RTOSes support a “broadcast” feature. This enables a message to be sent to all the tasks that are currently suspended on reading a specific mailbox.
Certain RTOSes do not support mailboxes at all. The recommendation is to use a single-entry queue (see below) instead. This is functionally equivalent, but carries additional memory and runtime overhead.
There is more information about mailboxes in a future article, which describes their implementation in Nucleus SE.
Queues are independent kernel objects, that provide a means for tasks to transfer messages. They are a little more flexible and complex than mailboxes. The message size depends on the implementation, but will normally be a fixed size and word/pointer oriented.
Any task may send to a queue and this may occur repeatedly until the queue is full, after which time any attempts to send will result in an error. The depth of the queue is generally user specified when it is created or the system is configured. In many RTOSes, it is possible to make a blocking call to send to a queue; this means that, if the queue is full, a task may be suspended until the queue is read by another task. Any task may read from a queue. Messages are read in the same order as they were sent – first in, first out (FIFO). If a task tries to read from an empty queue, it will receive an error response. In many RTOSes, it is possible to make a blocking call to read from a queue; this means that, if the queue is empty, a task may be suspended until a message is sent to the queue by another task.
An RTOS will probably support the facility to send a message to the front of the queue – this is also termed “jamming”. Some RTOSes also support a “broadcast” feature. This enables a message to be sent to all the tasks that are suspended on reading a queue. Additionally, an RTOS may support the sending and reading of messages of variable length; this gives greater flexibility, but carries some extra overhead.
Many RTOSes support another kernel object type called “pipes”. A pipe is essentially identical to a queue, but processes byte-oriented data.
The internal operation of queues is not of interest here, but it should be understood that they have more overheads in memory and runtime than mailboxes. This is primarily because two pointers – to the head and tail of the queue – need to be maintained.
There is more information about queues and pipes in future articles, which describe their implementation in Nucleus SE.
Mutual exclusion semaphores – mutexes – are independent kernel objects, which behave in a very similar way to normal binary semaphores. They are slightly more complex and incorporate the concept of temporary ownership (of the resource, access to which is being controlled). If a task obtains a mutex, only that same task can release it again – the mutex (and, hence, the resource) is temporarily owned by the task.
Mutexes are not provided by all RTOSes, but it is quite straightforward to adapt a regular binary semaphore. It would be necessary to write a “mutex obtain” function, which obtains the semaphore and notes the task identifier. Then a complementary “mutex release” function would check the calling task’s identifier and release the semaphore only if it matches the stored value, otherwise it would return an error.
Colin Walls has over thirty years experience in the electronics industry, largely dedicated to embedded software. A frequent presenter at conferences and seminars and author of numerous technical articles and two books on embedded software, Colin is an embedded software technologist with Mentor Embedded [the Mentor Graphics Embedded Software Division], and is based in the UK. His regular blog is located at: http://blogs.mentor.com/colinwalls. He may be reached by email at email@example.com