Intertask and Interprocess Communication
7 Intertask and Interprocess Communication .4 Semaphores
7.4.4 Mutual-Exclusion Semaphores
. /* critical region, only accessible by a single task at a time */
.
semGive (semMutex);
Synchronization
When used for task synchronization, a semaphore can represent a condition or event that a task is waiting for. Initially, the semaphore is unavailable (empty). A task or ISR signals the occurrence of the event by giving the semaphore. Another task waits for the semaphore by calling semTake( ). The waiting task blocks until the event occurs and the semaphore is given.
Note the difference in sequence between semaphores used for mutual exclusion and those used for synchronization. For mutual exclusion, the semaphore is initially full, and each task first takes, then gives back the semaphore. For synchronization, the semaphore is initially empty, and one task waits to take the semaphore given by another task.
Broadcast synchronization allows all processes that are blocked on the same semaphore to be unblocked atomically. Correct application behavior often requires a set of tasks to process an event before any task of the set has the opportunity to process further events. The routine semFlush( ) addresses this class of synchronization problem by unblocking all tasks pended on a semaphore.
7.4.4 Mutual-Exclusion Semaphores
The mutual-exclusion semaphore is a specialized binary semaphore designed to address issues inherent in mutual exclusion, including priority inversion, deletion safety, and recursive access to resources.
The fundamental behavior of the mutual-exclusion semaphore is identical to the binary semaphore, with the following exceptions:
■ It can be used only for mutual exclusion.
■ It can be given only by the task that took it.
■ The semFlush( ) operation is illegal.
7 Intertask and Interprocess Communication 7.4 Semaphores
User-Level Mutex Semaphores
Note that mutex semaphores can be created as user-level (user-mode) objects. They are faster than kernel-level semaphores as long as they are uncontested, which means the following:
■ The mutex semaphore is available during a semTake( ) operation.
■ There is no task waiting for the semaphore during a semGive( ) operation.
The uncontested case should be the most common, given the intended use of a mutex semaphore.
By default, using the semMCreate() routine in a process creates a user-level mutex semaphore. However, a kernel-level semaphore can be created when
semMCreate( ) is used with the SEM_KERNEL option. The semOpen() routine can only be used to create kernel-level semaphores in a process. Note that user-level semaphores can only be created as private objects, and not public ones.
Priority Inversion and Priority Inheritance
Figure 7-4 illustrates a situation called priority inversion.
Priority inversion arises when a higher-priority task is forced to wait an indefinite period of time for a lower-priority task to complete.
Consider the scenario in Figure 7-4: t1, t2, and t3 are tasks of high, medium, and low priority, respectively. t3 has acquired some resource by taking its associated binary guard semaphore. When t1 preempts t3 and contends for the resource by taking the same semaphore, it becomes blocked. If we could be assured that t1 would be blocked no longer than the time it normally takes t3 to finish with the NOTE: User-level semaphores are not supported for the symmetric
multiprocessing (SMP) configuration of VxWorks. With the SMP configuration, a semMCreate( ) call in a process creates a kernel-level mutex semaphore. For information about VxWorks SMP and about migration, see VxWorks Kernel Programmer’s Guide: VxWorks SMP.
Figure 7-4 Priority Inversion
t3
t1
t3
t2
HIGH
LOW
KEY: = take semaphore = preemption
= give semaphore
= own semaphore
priority
= priority inheritance/release
= block time
t1
t3
resource, there would be no problem because the resource cannot be preempted.
However, the low-priority task is vulnerable to preemption by medium-priority tasks (like t2), which could inhibit t3 from relinquishing the resource. This condition could persist, blocking t1 for an indefinite period of time.
Priority Inheritance Policy
The mutual-exclusion semaphore has the option SEM_INVERSION_SAFE, which enables a priority-inheritance policy. The priority-inheritance policy assures that a task that holds a resource executes at the priority of the highest-priority task that is blocked on that resource.
Once the task’s priority has been elevated, it remains at the higher level until all mutual-exclusion semaphores that have contributed to the tasks elevated priority are released. Hence, the inheriting task is protected from preemption by any intermediate-priority tasks. This option must be used in conjunction with a priority queue (SEM_Q_PRIORITY).
Note that after the inheriting task has finished executing at the elevated priority level, it returns to the end of the ready queue priority list for its original priority.
(For more information about the ready queue, see Scheduling and the Ready Queue, p.106.)
In Figure 7-5, priority inheritance solves the problem of priority inversion by elevating the priority of t3 to the priority of t1 during the time t1 is blocked on the semaphore. This protects t3, and indirectly t1, from preemption by t2.
The following example creates a mutual-exclusion semaphore that uses the priority inheritance policy:
semId = semMCreate (SEM_Q_PRIORITY | SEM_INVERSION_SAFE);
Figure 7-5 Priority Inheritance
t3
t1 t3 t1
t2
HIGH
LOW
priority
time
KEY: = take semaphore = preemption
= give semaphore
= own semaphore
= priority inheritance/release
= block
7 Intertask and Interprocess Communication 7.4 Semaphores
Deletion Safety
Another problem of mutual exclusion involves task deletion. Within a critical region guarded by semaphores, it is often desirable to protect the executing task from unexpected deletion. Deleting a task executing in a critical region can be catastrophic. The resource might be left in a corrupted state and the semaphore guarding the resource left unavailable, effectively preventing all access to the resource.
The primitives taskSafe( ) and taskUnsafe( ) provide one solution to task deletion.
However, the mutual-exclusion semaphore offers the option SEM_DELETE_SAFE, which enables an implicit taskSafe( ) with each semTake( ), and a taskUnsafe( ) with each semGive( ). In this way, a task can be protected from deletion while it has the semaphore. This option is more efficient than the primitives taskSafe( ) and taskUnsafe( ), as the resulting code requires fewer entrances to the kernel.
semId = semMCreate (SEM_Q_FIFO | SEM_DELETE_SAFE);
Recursive Resource Access
Mutual-exclusion semaphores can be taken recursively. This means that the semaphore can be taken more than once by the task that holds it before finally being released. Recursion is useful for a set of routines that must call each other but that also require mutually exclusive access to a resource. This is possible because the system keeps track of which task currently holds the mutual-exclusion semaphore.
Before being released, a mutual-exclusion semaphore taken recursively must be given the same number of times it is taken. This is tracked by a count that increments with each semTake( ) and decrements with each semGive( ).
Example 7-1 Recursive Use of a Mutual-Exclusion Semaphore
/* Function A requires access to a resource which it acquires by taking
* mySem;
* Function A may also need to call function B, which also requires mySem:
*/
/* includes */
#include <vxWorks.h>
#include <semLib.h>
SEM_ID mySem;
/* Create a mutual-exclusion semaphore. */
init ()
printf ("funcA: Got mutual-exclusion semaphore\n");
...
funcB ();
...
semGive (mySem);
printf ("funcA: Released mutual-exclusion semaphore\n");
}
funcB () {
semTake (mySem, WAIT_FOREVER);
printf ("funcB: Got mutual-exclusion semaphore\n");
...
semGive (mySem);
printf ("funcB: Releases mutual-exclusion semaphore\n");
}