Using RTOS semaphores - Part 2: Event semaphores
As noted in Part 1 of this series, the semaphore is often disparaged because it cannot prevent unbounded priority inversions like its big brother, the mutex. To compensate for this disability, semaphores have many more capabilities and uses than mutexes. Following on my discussion earlier of binary and multiple resource semaphores in embedded systems, this part discusses using binary and multiple event semaphores.
Event semaphores provide a method to synchronize tasks with internal or external events and with other tasks. For example, one task can signal another:
where port1_ack is an event semaphore. An event semaphore is created as follows:
Mode == M for a multiple event semaphore and == B for a binary event semaphore. In both cases, the semaphore's internal count starts at 0, i.e., there are no events to report, yet.
Multiple event semaphore
We start with the multiple event semaphore because it is the more common of the two. When a signal occurs for it, the first waiting task is resumed with TRUE. If no task is waiting, the internal count is incremented by 1. A multiple event semaphore keeps an exact count of signals received, whether or not any task is waiting. Hence, it is good for situations where every event counts and missing events could have bad consequences.
Of course, a simple counter could keep an accurate count of events, but a task cannot wait at a counter. An important property of the multiple event semaphore is that it handles both the case where events get ahead of the processing task and the case where the processing task gets ahead of events. In the first case, the semaphore's internal count is incremented; in the second case, the task waits. The following is an example showing this:
This is a simple example in which the occurrence of external event A causes an interrupt, which causes ISR_EventA() to run. ISR_EventA() invokes LSR_EventA(), which signals semaphore sme. Note that task t2a tests sme for every loop, including the first. If the internal count in sme is greater than 0, the count will be decremented, and t2a will process event A. Looping continues until the internal count of sme is 0, then t2a waits. Hence, no events will be lost if t2a gets behind, and when all events have been processed, t2a will wait for the next event. This synchronization between external events and an internal task produces a solid design.
Note that t2a will only wait for TMO ticks, after which the ‘while’ loop is exited and other code runs to deal with the timeout or error. It is always a good idea to specify reasonable timeouts on task waits in order to prevent tasks from being permanently suspended due to unexpected combinations of events. This improves reliability.
Note: LSRs are a means of performing deferred interrupt processing. Most other RTOSes use callback functions to accomplish this.
Binary event semaphore
The binary event semaphore is the converse of the multiple event semaphore in that events after the first are discarded. There is a good reason for this, as we shall see. A binary event semaphore is created as follows:
A binary event semaphore is useful when it is necessary to notify a task only of the first event in a series of events. In this case, subsequent events will be processed with the first event, and thus it is not necessary to record their occurrences. Binary semaphores are useful in producer/consumer applications where the producer signals more often than the consumer wants to hear about it (sort of like unsolicited advertising).
When the consumer runs, it loops to get all items the producer has made available rather than testing the semaphore for each item that was produced. A binary semaphore is appropriate for this situation because the task does not care how many times it was signaled; it only cares that it was signaled at least once. Using a binary semaphore in this situation saves overhead by not calling the RTOS SemTest() service needlessly.
A good example where a binary semaphore is preferred is the case where characters are being received by an ISR and processed by a task, as follows:
This example is similar to the previous example, but here characters are coming in. When a character is received, an interrupt causes ISR_Port() to run. It gets the input character, puts it into inpipe, and invokes LSR_Port(), which signals sbe. If task t2a is busy with other work, many characters may be received and sbe will be signaled many times. However its internal counter will not go above 1.
When task t2a completes its other work and tests sbe, it will be allowed to continue and the sbe internal counter will be cleared to 0. What makes this example different from the preceding example is the inner ‘do’ loop. This loop takes characters from inpipe and puts them into dbuf. When there are no more characters in inpipe, PipeGet8() returns 0. Then, the do loop is exited and t2a processes the characters in dbuf, does other work, and tests sbe, again. Note that only one SemTest() call is needed to get and process all of the characters in inpipe.
When t2a tests sbe again, if no new characters have come in, sbe's counter will be 0, causing t2a to wait at sbe. Otherwise, t2a gets and processes the new characters. A special case occurs if a character comes in while t2a is emptying inpipe. This results in sbe's counter being set to 1, yet the character will have been removed before t2a tests sbe again. So, t2a it will be allowed to continue, but inpipe will be empty. The t2a code must be designed to deal with this case, as is done in the above example. A little wasted action occurs here, but no characters are lost, thus producing a solid design.
Read Part 1: Resource semaphores
Next in Part 3: Threshold, gate and reader/writer semaphores.
1. SMX User’s Guide and Reference Manual
Ralph Moore, President of SMX, graduated with a degree in physics from Caltech. He spent his early career in computer research. Then he moved into mainframe design in the 60's and became a consultant in the early 70's. He taught himself programming and became a microprocessor programmer. He founded Micro Digital in 1975, and many years of successful consulting led into architecting and developing the SMX kernel in 1987. For many years he managed the company’s business and sales, but in recent years he has been focused almost solely on v4 development of the SMX multitasking kernel.