Skip to end of metadata
Go to start of metadata

The kernel offers the following services that control the access to a shared resource among many tasks.

Simple

Critical Section (Deprecated)

See the section on Interrupts and Critical Sections to see how to set up a critical section.

The simplest way to protect a shared resource is by using a critical section created using CPU_CRITICAL_ENTER() and CPU_CRITICAL_EXIT().

Note: A critical section disables the interrupts and stops the kernel from scheduling another task. Given the side effects, you should only use a critical section if access to the shared resource is both short and infrequent.

Locking the Scheduler

A safer approach to resource arbitration is locking the scheduler. During that time, the kernel cannot schedule another task.

Be sure that the shared resource is not used in an ISR. Interrupts are not disabled while the scheduler is locked, so an ISR and a task could access the resource at the same time. The following is an example of a shared resource being accessed while the scheduler is locked with OSSchedLock() and unlocked with OSSchedUnlock().

CPU_INT32U  App_GlobalCounter;
              :
              :
void  App_SomeTask (void  *p_arg)
{
    RTOS_ERR  err;
              :
              : 
    OSSchedLock(&err);        /* Lock the scheduler.                               */
    if (err.Code == RTOS_ERR_NONE) {
        App_GlobalCounter++;
        OSSchedUnlock(&err);  /* Unlock the scheduler.                             */
    }
              :
              :
}
Listing - Example of call to OSSchedLock() and OSSchedUnlock()

Semaphores

A counting semaphore can be used to protect a resource that is shared by multiple tasks. Before you can use the semaphore, you must create it with OSSemCreate(). A semaphore can be deleted with OSSemDel(), as shown below.

OS_SEM  App_Semaphore;
              :
              :
void  App_SomeTask (void  *p_arg)
{
    RTOS_ERR    err;
    OS_OBJ_QTY  qty;
              :
              :
                                   /* Create the semaphore.                           */
    OSSemCreate(&App_Semaphore,    /*   Pointer to user-allocated semaphore.          */
                "App Semaphore",   /*   Name used for debugging.                      */
                 1,                /*   Initial count: available in this case.        */
                &err);
    if (err.Code != RTOS_ERR_NONE) {
        /* Handle error on semaphore create. */
    }
              :
              :
              :
                                   /* Delete the semaphore.                           */
    qty = OSSemDel(&App_Semaphore,       /*   Pointer to user-allocated semaphore.    */
                    OS_OPT_DEL_NO_PEND,  /*   Option: delete if 0 tasks are pending.  */
                   &err);
    if (err.Code != RTOS_ERR_NONE) {
        /* Handle error on semaphore delete. */
    }
              :
              :
}
Listing - Example of call to OSSemCreate() and OSSemDel()

A task can acquire the right to use the protected resource by calling OSSemPend(). Once the task is done, it can release the resource with OSSemPost().

OS_SEM  App_Semaphore;
              :
              :
void  App_SomeTask (void  *p_arg)
{
    RTOS_ERR    err;
    OS_SEM_CTR  ctr;
               :
               :
                                   /* Acquire resource protected by semaphore.        */
    ctr = OSSemPend(&App_Semaphore,        /* Pointer to user-allocated semaphore.    */
                     1000,                 /* Wait for a maximum of 1000 OS Ticks.    */
                     OS_OPT_PEND_BLOCKING, /* Task will block.                        */
                     DEF_NULL,             /* Timestamp is not used.                  */
                    &err);
    if (err.Code == RTOS_ERR_NONE) {
        /* Resource acquired. 'ctr' contains number of available resources. */
              :
              :
                                   /* Release resource protected by semaphore.        */
        ctr = OSSemPost(&App_Semaphore,    /* Pointer to user-allocated semaphore.    */
                         OS_OPT_POST_1,    /* Only wake up highest-priority task.     */
                        &err);
        if (err.Code != RTOS_ERR_NONE) {
            /* Handle error on semaphore post. */
        }
    } else {
        /* Handle error on semaphore pend. */
    }
              :
              :
}
Listing - Example of call to OSSemPend() and OSSemPost()

If the kernel configuration permits it, a task can force the pends on a semaphore to abort with OSSemPendAbort().

OS_SEM  App_Semaphore;
              :
              :
void  App_SomeTask (void  *p_arg)
{
    RTOS_ERR    err;
    OS_OBJ_QTY  qty;
              :
              :
                                       /* Abort the highest-priority task's pend.     */
    qty = OSSemPendAbort(&App_Semaphore,      /* Pointer to user-allocated semaphore. */
                          OS_OPT_PEND_ABORT_1,/* Only abort the HP task's pend.       */
                         &err);
    if (err.Code != RTOS_ERR_NONE) {
        /* Handle error on semaphore pend abort. */
    }
              :
              :
}
Listing - Example of call to OSSemPendAbort()

Finally, a task may explicitly set the number of available resources by calling OSSemSet().

OS_SEM  App_Semaphore;
              :
              :
void  App_SomeTask (void  *p_arg)
{
    RTOS_ERR  err;
              :
              :
                              /* Set the number of available resources to 10.         */
    OSSemSet(&App_Semaphore,  /*   Pointer to user-allocated semaphore.               */
              10,             /*   10 resources.                                      */
             &err);
    if (err.Code != RTOS_ERR_NONE) {
        /* Handle error on semaphore set count. */
    }
              :
              :
}
Listing - Example of call to OSSemSet()

Use cases

In the example below, the application uses a global semaphore object to properly share a resource, a Printer, among two tasks, Task 1 and Task 2.

Figure - Protecting a Shared Hardware Resource

A disadvantage arises in that particular use-case. Indeed, the semaphore object itself needs to be shared and known by all tasks that need it. Furthermore, it needs to be created before any of the tasks tries to use it. It would be beneficial in terms of maintainability, and usability, if the semaphore object was encapsulated within a module that would export the services needed by the tasks. The illustration below shows a serial communications module, called COMM Module, that encapsulates the required locking. The tasks simply use the provided services to send commands and messages.

Figure - Encapsulating the Protection of a Shared Resource

A semaphore can also be used to protect a shared resource that is composed of more than one element. The semaphore is first initialized with the total number of available elements. When a task needs an element, it uses the semaphore to take an element. When it no longer needs the element, it gives it back through the semaphore. If a task tries to acquire an element and there is none left, the task will block until an element becomes available. An example of a counting semaphore can be found below.

Figure - Counting Semaphore Usage

In this example, the application defines a module called Buffer Manager that manages 10 buffers. When a task needs a buffer, it calls BufReq(). When the task no longer needs the buffer, it releases the buffer with BufRel(). Internally, the BufReq() and BufRel() methods use the Kernel's OSSemPend() service to allocate a buffer to the calling task, and the OSSemPost() service to put the buffer back in the pool.

Mutual Exclusion Semaphore (Mutexes)

If a shared resource can be used by only one task at a time, use a mutex. Because the semaphores are vulnerable to the priority-inversion problem, the mutex implementation in the kernel prevents priority inversions by using priority inheritance. You can use OSMutexCreate() to create a mutex and delete it with OSMutexDel(), as shown below.

OS_MUTEX  App_Mutex;
              :
              :
void  App_SomeTask (void  *p_arg)
{
    RTOS_ERR    err;
    OS_OBJ_QTY  qty;
              :
              :
                                /* Create the mutex.                                  */
    OSMutexCreate(&App_Mutex,   /*   Pointer to user-allocated mutex.                 */
                  "App Mutex",  /*   Name used for debugging.                         */
                  &err);
    if (err.Code != RTOS_ERR_NONE) {
        /* Handle error on mutex create. */
    }
              :
              :
              :
                                /* Delete the mutex.                                  */
    qty = OSMutexDel(&App_Mutex,           /*   Pointer to user-allocated mutex.      */
                      OS_OPT_DEL_NO_PEND,  /*   Option: delete if 0 tasks are pending.*/
                     &err);
    if (err.Code != RTOS_ERR_NONE) {
        /* Handle error on mutex delete. */
    }
              :
              :
}
Listing - Example of call to OSMutexCreate() and OSMutexDel()

A task can use the protected resource by calling OSMutexPend(). Once the task is done, it can release the resource with OSMutexPost().

A mutex can be nested, which means that you can perform severals consecutive calls of OSMutexPend() on a given mutex. This will increment an internal nesting counter and set the error to RTOS_ERR_IS_OWNER. This error could actualy be used as a nesting indicator. The resource will be released after the same number of OSMutexPost() calls.

OS_MUTEX  App_Mutex;
              :
              :
void  App_SomeTask (void  *p_arg)
{
    RTOS_ERR  err;
              :
              :
                                        /* Acquire resource protected by mutex.       */
    OSMutexPend(&App_Mutex,             /*   Pointer to user-allocated mutex.         */
                 1000,                  /*   Wait for a maximum of 1000 OS Ticks.     */
                 OS_OPT_PEND_BLOCKING,  /*   Task will block.                         */
                 DEF_NULL,              /*   Timestamp is not used.                   */
                &err);
    if (err.Code == RTOS_ERR_NONE) {
        /* Resource acquired. */
              :
              :
                                        /* Release resource protected by mutex.       */
        OSMutexPost(&App_Mutex,         /*   Pointer to user-allocated mutex.         */
                     OS_OPT_POST_1,     /*   Only wake up highest-priority task.      */
                    &err);
        if (err.Code != RTOS_ERR_NONE) {
            /* Handle error on mutex post. */
        }
    } else {
        /* Handle error on mutex pend. */
    }
              :
              :
}
Listing - Example of call to OSMutexPend() and OSMutexPost()

If the kernel configuration permits it, a task can force the pends on a mutex to abort with OSMutexPendAbort().

OS_MUTEX  App_Mutex;
              :
              :
void  App_SomeTask14 (void  *p_arg)
{
    RTOS_ERR    err;
    OS_OBJ_QTY  qty;
              :
              :
                                           /* Abort the highest-priority task's pend. */
    qty = OSMutexPendAbort(&App_Mutex,            /* Pointer to user-allocated mutex. */
                            OS_OPT_PEND_ABORT_1,  /* Only abort the HP task's pend.   */
                           &err);
    if (err.Code != RTOS_ERR_NONE) {
        /* Handle error on mutex pend abort. */
    }
              :
              :
}
Listing - Example of call to OSMutexPendAbort()

  • No labels