Mutual Exclusion Semaphore Internals

A mutex is a kernel object defined by the OS_MUTEX data type, which is derived from the structure os_mutex (see os.h) as shown in the listing below:

OS_MUTEX data type
typedef  struct  os_mutex  OS_MUTEX;          (1)
 
struct  os_mutex {
    OS_OBJ_TYPE          Type;                (2)
    CPU_CHAR            *NamePtr;             (3)
    OS_PEND_LIST         PendList;            (4)
    OS_MUTEX            *MutexGrpNextPtr;     (5)
    OS_TCB              *OwnerTCBPtr;         (6)
    OS_NESTING_CTR       OwnerNestingCtr;     (7)
    CPU_TS               TS;                  (8)
};

(1) In µC/OS-III, all structures are given a data type. All data types begin with “OS_” and are uppercase. When a mutex is declared, you simply use OS_MUTEX as the data type of the variable used to declare the mutex.

(2) The structure starts with a “Type” field, which allows it to be recognized by µC/OS-III as a mutex. Other kernel objects will also have a “.Type” as the first member of the structure. If a function is passed a kernel object, µC/OS-III will be able to confirm that it is being passed the proper data type (assuming OS_CFG_OBJ_TYPE_CHK_EN is set to DEF_ENABLED in os_cfg.h). For example, if passing a message queue (OS_Q) to a mutex service (for example OSMutexPend()), µC/OS-III will recognize that the application passed an invalid object and return an error code accordingly.

(3) Each kernel object can be given a name to make them easier to recognize by debuggers or µC/Probe. This member is simply a pointer to an ASCII string, which is assumed to be NUL terminated.

(4) Because it is possible for multiple tasks to wait (or pend) on a mutex, the mutex object contains a pend list as described in Pend Lists.

(5) To propagate the correct priority to the owning tasks and because a task can own more than one mutex, µC/OS-III has to track which mutex is owned by which tasks.

(6) If the mutex is owned by a task, it will point to the  OS_TCB  of that task.

(7) µC/OS-III allows a task to “acquire” the same mutex multiple times. In order for the mutex to be released, the owner must release the mutex the same number of times that it was acquired. Nesting can be performed up to usually 250-levels deep.

(8) A mutex contains a timestamp, used to indicate the last time it was released. µC/OS-III assumes the presence of a free-running counter that allows applications to make time measurements. When the mutex is released, the free-running counter is read and the value is placed in this field, which is returned when  OSMutexPend()  returns.

Application code should never access any of the fields in this data structure directly. Instead, you should always use the API provided with µC/OS-III.

A mutual exclusion semaphore (mutex) must be created before it can be used by an application. The listing below shows how to create a mutex.

Creating a mutex
OS_MUTEX  MyMutex;                           (1)
 
 
void  MyTask (void *p_arg)
{
    OS_ERR  err;
    :   
    :
    OSMutexCreate(&MyMutex,                  (2) 
                 "My Mutex",                 (3) 
                 &err);                      (4) 
    /* Check "err" */
    :
    :
}

(1) The application must declare a variable of type OS_MUTEX. This variable will be referenced by other mutex services.

(2) You create a mutex by calling OSMutexCreate() and pass the address to the mutex allocated in (1).

(3) You can assign an ASCII name to the mutex, which can be used by debuggers or µC/Probe to easily identify this mutex. There are no practical limits to the length of the name since µC/OS-III stores a pointer to the ASCII string, and not to the actual characters that makes up the string.

(4) OSMutexCreate() returns an error code based on the outcome of the call. If all the arguments are valid, err will contain OS_ERR_NONE.

Note that since a mutex is always a binary semaphore, there is no need to initialize a mutex counter.

A task waits on a mutual exclusion semaphore before accessing a shared resource by calling OSMutexPend() as shown in the listing below (see µC-OS-III API Reference for details regarding the arguments).

Pending (or waiting) on a Mutual Exclusion Semaphore
          OS_MUTEX  MyMutex;
           
           
          void MyTask (void *p_arg)
          {
              OS_ERR  err;
              CPU_TS  ts;
              :   
              while (DEF_ON) {
                  :
                  OSMutexPend(&MyMutex,             /* (1) Pointer to mutex                           */
                              10,                   /*     Wait up until this time for the mutex      */
                              OS_OPT_PEND_BLOCKING, /*     Option(s)                                  */
                              &ts,                  /*     Timestamp of when mutex was released       */
                              &err);                /*     Pointer to Error returned                  */
                  :
                  /* Check "err"                       (2)                                            */
                  :
                  OSMutexPost(&MyMutex,             /* (3) Pointer to mutex                           */
                              OS_OPT_POST_NONE,
                              &err);                /*     Pointer to Error returned                  */
                  /* Check "err"                                                                      */
                  :
                  :
              }
          }

(1) When called, OSMutexPend() starts by checking the arguments passed to this function to make sure they have valid values. This assumes that OS_CFG_ARG_CHK_EN is set to DEF_ENABLED in os_cfg.h.

If the mutex is available, OSMutexPend() assumes the calling task is now the owner of the mutex and stores a pointer to the task’s OS_TCB in p_mutex->OwnerTCPPtr. It also adds the mutex to the calling task list of owned mutex and sets the mutex nesting counter to 1. OSMutexPend() then returns to its caller with an error code of OS_ERR_NONE.

If the task that calls OSMutexPend() already owns the mutex, OSMutexPend() simply increments a nesting counter. In this case, the error returned will indicate  OS_ERR_MUTEX_OWNER . Applications can nest calls to OSMutexPend() up to usually 250- levels deep.

If the mutex is already owned by another task and OS_OPT_PEND_NON_BLOCKING is specified, OSMutexPend() returns since the task is not willing to wait for the mutex to be released by its owner.

If the mutex is owned by a task that currently has a lower priority, µC/OS-III will raise the priority of the owner to match the priority of the current task.

If you specify OS_OPT_PEND_BLOCKING as the option, the calling task will be inserted in the list of tasks waiting for the mutex to be available. The task is inserted in the list by priority order and thus, the highest priority task waiting on the mutex is at the beginning of the list.

If you further specify a non-zero timeout, the task will also be inserted in the tick list. A zero value for a timeout indicates a willingness to wait forever for the mutex to be released.

The scheduler is then called since the current task is no longer able to run (it is waiting for the mutex to be released). The scheduler will then run the next highest-priority task that is ready-to-run.

When the mutex is finally released and the task that called OSMutexPend() is again the highest-priority task, a task status is examined to determine the reason why OSMutexPend() is returning to its caller. The possibilities are:

1) The mutex was given to the waiting task. This is the desired outcome.

2) The pend was aborted by another task.

3) The mutex was not posted within the specified timeout.

4) The mutex was deleted.

When OSMutexPend() returns, the caller is notified of the outcome through an appropriate error code.

(2) If OSMutexPend() returns with err set to OS_ERR_NONE, assume that the calling task now owns the resource and can proceed with accessing it. If err contains anything else, then OSMutexPend() either timed out (if the timeout argument was non-zero), was aborted by another task, or the mutex was deleted by another task. It is always important to examine returned error codes and not assume everything went as planned.

If “err” is OS_ERR_MUTEX_NESTING, then the caller attempted to pend on the same mutex.

(3) When your task is finished accessing the resource, it must call OSMutexPost() and specify the same mutex. Again, OSMutexPost() starts by checking the arguments passed to this function to make sure they contain valid values (Assuming OS_CFG_ARG_CHK_EN is set to DEF_ENABLED in os_cfg.h).

OSMutexPost() now calls OS_TS_GET() to obtain the current timestamp and place that information in the mutex, which will be used by OSMutexPend().

OSMutexPost() decrements the nesting counter and, if still non-zero, OSMutexPost() returns to the caller. In this case, the current owner has not fully released the mutex. The error code will be OS_ERR_MUTEX_NESTING.

OSMutexPost() removes the mutex from the calling tasks mutex group.

If there are no tasks waiting for the mutex, OSMutexPost() sets p_mutex->OwnerTCBPtr to a NULL pointer and clears the mutex nesting counter.

If µC/OS-III had to raise the priority of the mutex owner, µC/OS-III sets the priority of the calling task to the highest priority in the calling task's owned mutex group or its base priority, whichever is higher.

The highest-priority task waiting on the mutex is then extracted from the pend list and given the mutex. This is a fast operation since the pend list is sorted by priority.

If the option to OSMutexPost() is not OS_OPT_POST_NO_SCHED then, the scheduler is called to execute the next highest priority task.