µC/OS-III supports a special type of binary semaphore called a mutual exclusion semaphore (also known as a mutex) that eliminates unbounded priority inversions. The figure below shows how priority inversions are bounded using a Mutex.
Panel | ||
---|---|---|
| ||
Panel | ||
---|---|---|
| ||
(1) Task H and Task M are both waiting for an event to occur and Task L is executing. (2) At some point, Task L acquires a mutex, which it needs before it is able to access a shared resource. (3) Task L performs operations on the acquired resource. (4) The event that Task H waited for occurs and the kernel suspends Task L and begins executing Task H since Task H has a higher priority. (5) Task H performs computations based on the event it just received. (6) Task H now wants to access the resource that Task L currently owns (i.e., it attempts to get the mutex from Task L). Given that Task L owns the resource, µC/OS-III raises the priority of Task L to the same priority as Task H to allow Task L to finish with the resource and prevent Task L from being preempted by medium-priority tasks. (7) Task L continues accessing the resource, however it now does so while it is running at the same priority as Task H. Note that Task H is not actually running since it is waiting for Task L to release the mutex. In other words, Task H is in the mutex wait list. (8) Task L finishes working with the resource and releases the mutex. µC/OS-III notices that Task L was raised in priority and thus lowers Task L to its original priority. After doing so, µC/OS-III gives the mutex to Task H, which was waiting for the mutex to be released. (9) Task H now has the mutex and can access the shared resource. (10) Task H is finished accessing the shared resource, and frees up the mutex. (11) There are no higher-priority tasks to execute, therefore Task H continues execution. (12) Task H completes and decides to wait for an event to occur. At this point, µC/OS-III resumes Task M, which was made ready-to-run while Task H or Task L were executing. Task M was made ready-to-run because an interrupt (not shown in figure 13-5) occurred which Task M was waiting for. (13) Task M executes. |
Note that there is no priority inversion, only resource sharing. Of course, the faster Task L accesses the shared resource and frees up the mutex, the better.
µC/OS-III implements full-priority inheritance and therefore if a higher priority requests the resource, the priority of the owner task will be raised to the priority of the new requestor.
A mutex is a kernel object defined by the OS_MUTEX
data type, which is derived from the structure os_mutex
(see os.h
). An application may have an unlimited number of mutexes (limited only by the RAM available).
Only tasks are allowed to use mutual exclusion semaphores (ISRs are not allowed).
µC/OS-III enables the user to nest ownership of mutexes. If a task owns a mutex, it can own the same mutex up to 250 times. The owner must release the mutex an equivalent number of times. In several cases, an application may not be immediately aware that it called OSMutexPend()
multiple times, especially if the mutex is acquired again by calling a function as shown in Listing 13-12.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||
---|---|---|
| ||
OS_MUTEX MyMutex; SOME_STRUCT MySharedResource; void MyTask (void *p_arg) { OS_ERR err; CPU_TS ts; : while (DEF_ON) { OSMutexPend((OS_MUTEX *)&MyMutex, (1) (OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS *)&ts, (OS_ERR *)&err); /* Check "err" */ (2) /* Acquire shared resource if no error */ MyLibFunction(); (3) OSMutexPost((OS_MUTEX *)&MyMutex, (7) (OS_OPT )OS_OPT_POST_NONE, (OS_ERR *)&err); /* Check "err" */ } } void MyLibFunction (void) { OS_ERR err; CPU_TS ts; OSMutexPend((OS_MUTEX *)&MyMutex, (4) (OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS *)&ts, (OS_ERR *)&err); /* Check "err" */ /* Access shared resource if no error */ (5) OSMutexPost((OS_MUTEX *)&MyMutex, (6) (OS_OPT )OS_OPT_POST_NONE, (OS_ERR *)&err); /* Check "err" */ } |
Panel | ||
---|---|---|
| ||
(1) A task starts by pending on a mutex to access shared resources. (2) You should check the error return value. If no errors exist, (3) A function is called that will perform additional work. (4) The designer of (5) (6) The mutex is released and the nesting counter is decremented back to 1. Since this indicates that the mutex is still owned by the same task, nothing further needs to be done, and (7) The mutex is released again and, this time, the nesting counter is decremented back to 0 indicating that other tasks can now acquire the mutex. |
You should always check the return value of OSMutexPend()
(and any kernel call) to ensure that the function returned because you properly obtained the mutex, and not because the return from OSMutexPend()
was caused by the mutex being deleted, or because another task called OSMutexPendAbort()
on this mutex.
As a general rule, do not make function calls in critical sections. All mutual exclusion semaphore calls should be in the leaf nodes of the source code (e.g., in the low level drivers that actually touches real hardware or in other reentrant function libraries).
There are a number of operations that can be performed on a mutex, as summarized in the table below. However, in this chapter, we will only discuss the three functions that are most often used: OSMutexCreate()
, OSMutexPend()
, and OSMutexPost()
. Other functions are described in µC-OS-III API Reference.
Panel | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||||
|