For cases in which multiple equivalent shared resources are used, a counting semaphore comes in handy, as shown in Figure 6.10.
Figure 6.10: Single shared-resource-access synchronization.
Note that this scenario does not work if the shared resources are not equivalent. The counting semaphore's count is initially set to the number of equivalent shared resources: in this example, 2. As a result, the first two tasks requesting a semaphore token are successful. However, the third task ends up blocking until one of the previous two tasks releases a semaphore token, as shown in Listing 6.6. Note that similar code is used for tAccessTask 1, 2, and 3.
Listing 6.6: Pseudo code for multiple tasks accessing equivalent shared resources.
tAccessTask () {
:
Acquire a counting semaphore token
Read or Write to shared resource
Release a counting semaphore token
:
}
As with the binary semaphores, this design can cause problems if a task releases a semaphore that it did not originally acquire. If the code is relatively simple, this issue might not be a problem. If the code is more elaborate, however, with many tasks accessing shared devices using multiple semaphores, mutexes can provide built-in protection in the application design.
As shown in Figure 6.9, a separate mutex can be assigned for each shared resource. When trying to lock a mutex, each task tries to acquire the first mutex in a non-blocking way. If unsuccessful, each task then tries to acquire the second mutex in a blocking way.
The code is similar to Listing 6.7. Note that similar code is used for tAccessTask 1, 2, and 3.
Listing 6.7: Pseudo code for multiple tasks accessing equivalent shared resources using mutexes.
tAccessTask () {
:
Acquire first mutex in non-blocking way
If not successful then acquire 2nd mutex in a blocking way
Read or Write to shared resource
Release the acquired mutex
:
}
Using this scenario, task 1 and 2 each is successful in locking a mutex and therefore having access to a shared resource. When task 3 runs, it tries to lock the first mutex in a non-blocking way (in case task 1 is done with the mutex). If this first mutex is unlocked, task 3 locks it and is granted access to the first shared resource. If the first mutex is still locked, however, task 3 tries to acquire the second mutex, except that this time, it would do so in a blocking way. If the second mutex is also locked, task 3 blocks and waits for the second mutex until it is unlocked.
6.5 Points to Remember
Some points to remember include the following:
· Using semaphores allows multiple tasks, or ISRs to tasks, to synchronize execution to synchronize execution or coordinate mutually exclusive access to a shared resource.
· Semaphores have an associated semaphore control block (SCB), a unique ID, a user-assigned value (binary or a count), and a task-waiting list.
· Three common types of semaphores are binary, counting, and mutual exclusion (mutex), each of which can be acquired or released.
· Binary semaphores are either available (1) or unavailable (0). Counting semaphores are also either available (count =1) or unavailable (0). Mutexes, however, are either unlocked (0) or locked (lock count =1).
· Acquiring a binary or counting semaphore results in decrementing its value or count, except when the semaphore’s value is already 0. In this case, the requesting task blocks if it chooses to wait for the semaphore.
· Releasing a binary or counting semaphore results in incrementing the value or count, unless it is a binary semaphore with a value of 1 or a bounded semaphore at its maximum count. In this case, the release of additional semaphores is typically ignored.
· Recursive mutexes can be locked and unlocked multiple times by the task that owns them. Acquiring an unlocked recursive mutex increments its lock count, while releasing it decrements the lock count.
· Typical semaphore operations that kernels provide for application development include creating and deleting semaphores, acquiring and releasing semaphores, flushing semaphore’s task-waiting list, and providing dynamic access to semaphore information.
Chapter 7: Message Queues
7.1 Introduction
Chapter 6 discusses activity synchronization of two or more threads of execution. Such synchronization helps tasks cooperate in order to produce an efficient real-time system. In many cases, however, task activity synchronization alone does not yield a sufficiently responsive application. Tasks must also be able to exchange messages. To facilitate inter-task data communication, kernels provide a message queue object and message queue management services.
This chapter discusses the following:
· defining message queues,
· message queue states,
· message queue content,
· typical message queue operations, and
· typical message queue use.
7.2 Defining Message Queues
A message queue is a buffer-like object through which tasks and ISRs send and receive messages to communicate and synchornize with data. A message queue is like a pipeline. It temporarily holds messages from a