Advertisement

Semaphores: introduction and basic services

April 17, 2018

Colin Walls-April 17, 2018

Semaphores were introduced in an earlier article. Their primary use is the control of access to resources.

Using Semaphores

In Nucleus SE, semaphores are configured at build time. There may be a maximum of 16 semaphores configured for an application. If no semaphores are configured, no data structures or service call code appertaining to semaphores are included in the application.

A semaphore is simply a counter of type U8, access to which is controlled so that it may be safely utilized by multiple tasks. A task can decrement (obtain) a semaphore and increment (release) it. Trying to obtain a semaphore that has the value zero may result in an error or task suspension, depending on options selected in the API call and the Nucleus SE configuration.

Configuring Semaphores

Number of Semaphores

As with most aspects of Nucleus SE, the configuration of semaphores is primarily controlled by #define statements in nuse_config.h. The key setting is NUSE_SEMAPHORE_NUMBER, which determines how many semaphores are configured for the application. The default setting is 0 (i.e. no semaphores are in use) and you can set it to any value up to 16. An erroneous value will result in a compile time error, which is generated by a test in nuse_config_check.h (this is included into nuse_config.c and hence compiled with this module) resulting in a #error statement being compiled.

Choosing a non-zero value is the “master enable” for semaphores. This results in some data structures being defined and sized accordingly, of which more later in this article. It also activates the API enabling settings.

API Enables

Every API function (service call) in Nucleus SE has an enabling #define symbol in nuse_config.h. For semaphores, these are:

NUSE_SEMAPHORE_OBTAIN
NUSE_SEMAPHORE_RELEASE
NUSE_SEMAPHORE_RESET
NUSE_SEMAPHORE_INFORMATION
NUSE_SEMAPHORE_COUNT

By default, all of these are set to FALSE, thus disabling each service call and inhibiting the inclusion of any implementation code. To configure semaphores for an application, you need to select the API calls that you want to use and set their enabling symbols to TRUE.

Here is an extract from the default nuse_config.h file.

#define NUSE_SEMAPHORE_NUMBER       0         /* Number of semaphores in
                                                 the system - 0-16 */
#define NUSE_SEMAPHORE_OBTAIN       FALSE     /* Service call enabler */
#define NUSE_SEMAPHORE_RELEASE      FALSE     /* Service call enabler */
#define NUSE_SEMAPHORE_RESET        FALSE     /* Service call enabler */
#define NUSE_SEMAPHORE_INFORMATION  FALSE     /* Service call enabler */
#define NUSE_SEMAPHORE_COUNT        FALSE     /* Service call enabler */


A compile time error will result if a semaphore API function is enabled and no semaphores are configured (except for NUSE_Semaphore_Count() which is always permitted). If your code uses an API call, which has not been enabled, a link time error will result, as no implementation code will have been included in the application.

Semaphore Service Calls

Nucleus RTOS supports eight service calls that appertain to semaphores, which provide the following functionality:

  • Obtain a semaphore. Implemented by NUSE_Semaphore_Obtain() in Nucleus SE.

  • Release a semaphore. Implemented by NUSE_Semaphore_Release() in Nucleus SE.

  • Restore a semaphore to the unused state, with no tasks suspended (reset). Implemented by NUSE_Semaphore_Reset() in Nucleus SE.

  • Provide information about a specified semaphore. Implemented by NUSE_Semaphore_Information() in Nucleus SE.

  • Return a count of how many semaphores are (currently) configured for the application. Implemented by NUSE_Semaphore_Count() in Nucleus SE.

  • Add a new semaphore to the application (create). Not implemented in Nucleus SE.

  • Remove a semaphore from the application (delete). Not implemented in Nucleus SE.

  • Return pointers to all the semaphores (currently) in the application. Not implemented in Nucleus SE.

The implementation of each of these service calls is examined in detail.

Semaphore Obtain and Release Services

The fundamental operations, which can be performed on a semaphore, are obtaining (decrementing) and releasing (incrementing) it. Nucleus RTOS and Nucleus SE each provide two basic API calls for these operations, which will be discussed here.

Obtaining a Semaphore

The Nucleus RTOS API call for obtaining a semaphore is very flexible, enabling you to suspend indefinitely, or with a timeout, if the operation cannot be completed immediately; i.e. you try to obtain a semaphore which currently has the value zero. Nucleus SE provides the same service, except task suspend is optional and timeout is not implemented.

Nucleus RTOS API Call for Obtaining a Semaphore

Service call prototype:

STATUS NU_Obtain_Semaphore(NU_SEMAPHORE *semaphore,
                           UNSIGNED suspend);

Parameters:

semaphore – pointer to the user-supplied semaphore control block

suspend – specification for task suspend; may be NU_NO_SUSPEND or NU_SUSPEND or a timeout value

Returns:

NU_SUCCESS – the call was completed successfully

NU_UNAVAILABLE – the semaphore had the value zero

NU_INVALID_SEMAPHORE – the semaphore pointer is invalid

NU_INVALID_SUSPEND – suspend was attempted from a non-task

NU_SEMAPHORE_WAS_RESET – the semaphore was reset while the task was suspended

Nucleus SE API Call for Obtaining a Semaphore

This API call supports the key functionality of the Nucleus RTOS API.

Service call prototype:

STATUS NUSE_Semaphore_Obtain(NUSE_SEMAPHORE semaphore,
                             U8 suspend);

Parameters:

semaphore – the index (ID) of the semaphore to be utilized

suspend – specification for task suspend; may be NUSE_NO_SUSPEND or NUSE_SUSPEND

Returns:

NUSE_SUCCESS – the call was completed successfully

NUSE_UNAVAILABLE – the semaphore had the value zero

NUSE_INVALID_SEMAPHORE – the semaphore index is invalid

NUSE_INVALID_SUSPEND – suspend was attempted from a non-task thread or when blocking API calls were not enabled

NUSE_SEMAPHORE_WAS_RESET – the semaphore was reset while the task was suspended

Nucleus SE Implementation of Obtain Semaphore

The bulk of the code of the NUSE_Semaphore_Obtain() API function – after parameter checking – is selected by conditional compilation, dependent on whether support for blocking (task suspend) API calls is enabled. We will look at the two variants separately here.

If blocking is not enabled, the logic for this API call is quite simple:

if (NUSE_Semaphore_Counter[semaphore] != 0) /* semaphore available */
{
    NUSE_Semaphore_Counter[semaphore]--;
    return_value = NUSE_SUCCESS;
}
else                                        /* semaphore unavailable */
{
    return_value = NUSE_UNAVAILABLE;
}


The semaphore value is tested and, if non-zero, decremented.

When blocking is enabled, the logic becomes more complex:

do
{
    if (NUSE_Semaphore_Counter[semaphore] != 0) /* semaphore available */
    {
        NUSE_Semaphore_Counter[semaphore]--;
        return_value = NUSE_SUCCESS;
        suspend = NUSE_NO_SUSPEND;
    }
    else                                    /* semaphore unavailable */
    {
        if (suspend == NUSE_NO_SUSPEND)
        {
            return_value = NUSE_UNAVAILABLE;
        }
        else
        {                                   /* block task */
            NUSE_Semaphore_Blocking_Count[semaphore]++;
            NUSE_Suspend_Task(NUSE_Task_Active,
                              semaphore << 4) | NUSE_SEMAPHORE_SUSPEND);
            return_value = NUSE_Task_Blocking_Return[NUSE_Task_Active];
            if (return_value != NUSE_SUCCESS)
            {
                suspend = NUSE_NO_SUSPEND;
            }
        }
    }
} while (suspend == NUSE_SUSPEND);

Some explanation of the code may be useful:

The code is enclosed in a do...while loop, which continues while the parameter suspend has the value NUSE_SUSPEND.

If the semaphore is non-zero, it is decremented. The suspend variable is set to NUSE_NO_SUSPEND and the API call exits with NUSE_SUCCESS.

If the semaphore is zero and suspend is set to NUSE_NO_SUSPEND, the API call exits with NUSE_UNAVAILBLE. If suspend was set to NUSE_SUSPEND, the task is suspended. On return (i.e. when the task is woken up), if the return value is NUSE_SUCCESS, indicating that the task was woken because the semaphore has been released (as opposed to a reset of the semaphore) the code loops back to the top.

Releasing a Semaphore

The Nucleus RTOS API call for releasing a semaphore is quite simple; the semaphore is incremented and success reported. Nucleus SE provides the same service, except an overflow check is performed.

Nucleus RTOS API Call for Releasing a Semaphore

Service call prototype:

STATUS NU_Release_Semaphore(NU_SEMAPHORE *semaphore);

Parameters:

semaphore – pointer to the user-supplied semaphore control block

Returns:

NU_SUCCESS – the call was completed successfully

NU_INVALID_SEMAPHORE – the semaphore pointer is invalid

Nucleus SE API Call for Releasing a Semaphore

This API call supports the key functionality of the Nucleus RTOS API.

Service call prototype:

STATUS NUSE_Semaphore_Release(NUSE_SEMAPHORE semaphore);

Parameters:

semaphore – the index (ID) of the semaphore to be released

Returns:

NUSE_SUCCESS – the call was completed successfully

NUSE_INVALID_SEMAPHORE – the semaphore index is invalid

NUSE_UNAVAILABLE – the semaphore has the value 255 and cannot be incremented

Nucleus SE Implementation of Release Semaphore

The initial code of the NUSE_Semaphore_Release() API function – after parameter checking – is common, whether task blocking is enabled or not. The value of the semaphore is checked and, if it less than 255, decremented.

Further code is selected by conditional compilation, if support for blocking (task suspend) API calls is enabled:

NUSE_CS_Enter();
if (NUSE_Semaphore_Counter[semaphore] < 255)
{
    NUSE_Semaphore_Counter[semaphore]++;
    return_value = NUSE_SUCCESS;
    #if NUSE_BLOCKING_ENABLE
        if (NUSE_Semaphore_Blocking_Count[semaphore] != 0)
        {
            U8 index;       /* check whether a task is blocked */
                            /* on this semaphore */
            NUSE_Semaphore_Blocking_Count[semaphore]--;
            for (index=0; index<NUSE_TASK_NUMBER; index++)
            {
                if ((LONIB(NUSE_Task_Status[index]) ==
                     NUSE_SEMAPHORE_SUSPEND)
                    && (HINIB(NUSE_Task_Status[index]) ==
                        semaphore))
                {
                    NUSE_Task_Blocking_Return[index] =
                        NUSE_SUCCESS;
                    NUSE_Wake_Task(index);
                    break;
                }
            }
        }
    #endif
}
else
{
    return_value = NUSE_UNAVAILABLE;
}
NUSE_CS_Exit();
return return_value;

If any tasks are suspended on this semaphore, the first one is woken up.

The next article will cover some additional API calls associated with event flag groups, along with the relevant data structures.


Colin Walls has over thirty years experience in the electronics industry, largely dedicated to embedded software. A frequent presenter at conferences and seminars and author of numerous technical articles and two books on embedded software, Colin is an embedded software technologist with Mentor Embedded [the Mentor Graphics Embedded Software Division], and is based in the UK. His regular blog is located at: http://blogs.mentor.com/colinwalls. He may be reached by email at colin_walls@mentor.com

 

Loading comments...

Most Commented