Semaphores: utility services and data structures

This article continues the look at semaphores.

Semaphore Utility Services

Nucleus RTOS has four API calls which provide utility functions associated with semaphores: reset semaphore, return information about a semaphore, return number of semaphores in the application and return pointers to all semaphores in the application. The first three of these are implemented in Nucleus SE.

Resetting a Semaphore

This API call restores the semaphore to its initial, unused state. This API function is unusual, compared with that available for other kernel objects, as, although it is a reset, it does not simply initialize its counter to the start-up value; a new initial count is provided in the call. Any tasks which were suspended on the semaphore are resumed and receive a return code of NUSE_SEMAPHORE_WAS_RESET (in Nucleus SE, or NU_SEMAPHORE_RESET with Nucleus RTOS).

Nucleus RTOS API Call for Resetting a Semaphore

Service call prototype:

STATUS NU_Reset_Semaphore(NU_SEMAPHORE  *semaphore,
UNSIGNED initial_count);

Parameters:

semaphore – pointer to user-supplied semaphore control block

initial_count – the value to which the semaphore’s counter is to be set

Returns:

NU_SUCCESS – the call was completed successfully

NU_INVALID_SEMAPHORE – the semaphore pointer is not valid

Nucleus SE API Call for Resetting a Semaphore

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

Service call prototype:

STATUS NUSE_Semaphore_Reset(NUSE_SEMAPHORE semaphore,
U8 initial_count);

Parameters:

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

initial_count – the value to which the semaphore’s counter is to be set

Returns:

NUSE_SUCCESS – the call was completed successfully

NUSE_INVALID_SEMAPHORE – the semaphore index is not valid

Nucleus SE Implementation of Semaphore Reset

The main job of the NUSE_Semaphore_Reset() API function – after parameter checking – is to simply set the appropriate entry in NUSE_Semaphore_Counter[] to the provided initial value.

When blocking is enabled, further code is required to unblock tasks:

while (NUSE_Semaphore_Blocking_Count[semaphore] != 0){    U8 index;           /* check whether any tasks are blocked */                        /* on this 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_SEMAPHORE_WAS_RESET;            NUSE_Task_Status[index] = NUSE_READY;            break;        }    }    NUSE_Semaphore_Blocking_Count[semaphore]--;} #if NUSE_SCHEDULER_TYPE == NUSE_PRIORITY_SCHEDULER    NUSE_Reschedule(NUSE_NO_TASK);#endif

Each task suspended on the semaphore is marked as “ready” with a suspend return code of NUSE_SEMAPHORE_WAS_RESET . After this process is complete, if the Priority scheduler is in use, a call is made to NUSE_Reschedule() , as one or more higher priority tasks may have been readied and needs to be allowed to run.

Semaphore Information

This service call obtains a selection of information about a semaphore. The Nucleus SE implementation differs from Nucleus RTOS in that it returns less information, as object naming and suspend ordering are not supported and task suspend may not be enabled.

Nucleus RTOS API Call for Semaphore Information

Service call prototype:

STATUS NU_Semaphore_Information(NU_SEMAPHORE *semaphore,
CHAR *name, UNSIGNED *current_count, OPTION *suspend_type,
UNSIGNED *tasks_waiting, NU_TASK **first_task);

Parameters:

semaphore – pointer top the control block of the semaphore about which information is being requested

name – pointer to an 8-character destination area for the semaphore’s name; this includes space for the null terminator

current_count – a pointer to a variable, which will receive the current value of the semaphore counter

suspend_type – pointer to a variable that holds the task’s suspend type; valid task suspend types are NU_FIFO and NU_PRIORITY

tasks_waiting – a pointer to a variable which will receive the number of tasks suspended on this semaphore

first_task – a pointer to a variable of type NU_TASK which will receive a pointer to the control block of the first suspended task

Returns:

NU_SUCCESS – the call was completed successfully

NU_INVALID_SEMAPHORE – the semaphore pointer is not valid

Nucleus SE API Call for Semaphore Information

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

Service call prototype:

STATUS NUSE_Semaphore_Information(NUSE_SEMAPHORE semaphore,
U8 *current_count, U8 *tasks_waiting, NUSE_TASK *first_task);

Parameters:

semaphore – the index of the semaphore about which information is being requested

current_count – a pointer to a variable, which will receive the current value of the semaphore counter

tasks_waiting – a pointer to a variable which will receive the number of tasks suspended on this semaphore (nothing returned if task suspend is disabled)

first_task – a pointer to a variable of type NUSE_TASK which will receive the index of the first suspended task (nothing returned if task suspend is disabled)

Returns:

NUSE_SUCCESS – the call was completed successfully

NUSE_INVALID_SEMAPHORE – the semaphore index is not valid

NUSE_INVALID_POINTER – one or more of the pointer parameters is invalid

Nucleus SE Implementation of Semaphore Information

The implementation of this API call is quite straightforward:

NUSE_CS_Enter(); *current_count = NUSE_Semaphore_Counter[semaphore];#if NUSE_BLOCKING_ENABLE     *tasks_waiting = NUSE_Semaphore_Blocking_Count[semaphore];    if (NUSE_Semaphore_Blocking_Count[semaphore] != 0)    {        U8 index;         for (index=0; index<NUSE_TASK_NUMBER; index++)        {            if ((LONIB(NUSE_Task_Status[index]) ==                  NUSE_SEMAPHORE_SUSPEND) &&                 (HINIB(NUSE_Task_Status[index]) == semaphore))            {                *first_task = index;                break;            }        }    }    else    {        *first_task = 0;    } #else     *tasks_waiting = 0;    *first_task = 0; #endif NUSE_CS_Exit(); return NUSE_SUCCESS;

The function returns the semaphore status. Then, if blocking API calls is enabled, the number of waiting tasks and the index of the first one are returned (otherwise these two parameters are set to 0).

Obtaining the Number of Semaphores

This service call returns the number of semaphores configured in the application. Whilst in Nucleus RTOS this will vary over time and the returned value will represent the current number of semaphores, in Nucleus SE the value returned is set at build time and cannot change.

Nucleus RTOS API Call for Semaphore Count

Service call prototype:

UNSIGNED NU_Established_Semaphores(VOID);

Parameters:

None

Returns:

The number of created semaphores in the application

Nucleus SE API Call for Semaphore Count

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

Service call prototype:

U8 NUSE_Semaphore_Count(void);

Parameters:

None

Returns:

The number of configured semaphores in the application

Nucleus SE Implementation of Semaphore Count

The implementation of this API call is almost trivially simple: the value of the #define symbol NUSE_SEMAPHORE_NUMBER is returned.

Continue to page two: “Data Structures” >>

Data Structures

Semaphores utilize two or three data structures – in RAM and ROM – which, like other Nucleus SE objects, are a series of tables, included and dimensioned according to the number of semaphores configured and options selected.

I strongly recommend that application code does not access these data structures directly but uses the provided API functions. This avoids incompatibility with future versions of Nucleus SE and unwanted side-effects and simplifies porting of an application to Nucleus RTOS. The details of data structures are included here to facilitate easier understanding of the working of the service call code and for debugging.

RAM Data

These data structures are:

NUSE_Semaphore_Counter[] – This is an array of type U8 , with one entry for each configured semaphore; this is where the count value is stored.

NUSE_Semaphore_Blocking_Count[] – This type U8 array contains the counts of how many tasks are blocked on each semaphore. This array only exists if blocking API call support is enabled.

NUSE_Semaphore_Counter[] is initialized to an initial value (see ROM Data below) and NUSE_Semaphore_Blocking_Count[] is set to zero by NUSE_Init_Semaphore() when Nucleus SE starts up. A future article will provide a full description of Nucleus SE start-up procedures.

Here are the definitions of these data structures in nuse_init.c file.

RAM U8 NUSE_Semaphore_Counter[NUSE_SEMAPHORE_NUMBER];#if NUSE_BLOCKING_ENABLE     RAM U8 NUSE_Semaphore_Blocking_Count[NUSE_SEMAPHORE_NUMBER]; #endif

ROM Data

This data structure is:

NUSE_Semaphore_Initial_Value[] – This is an array of type U8 , with one entry for each configured semaphore; this is the value to which the counters are initialized.

This data structure is declared and initialized (statically, of course) in nuse_config.c :

ROM U8 NUSE_Semaphore_Initial_Value[NUSE_SEMAPHORE_NUMBER] ={    /* semaphore initial count values */};

Semaphore Data Footprint

Like all kernel objects in Nucleus SE, the amount of data memory required for semaphores is readily predictable.

The ROM data footprint (in bytes) for all the semaphores in an application is just NUSE_SEMAPHORE_NUMBER .

The RAM data footprint (in bytes) for all the semaphores in an application, when blocking API calls is enabled, may be computed thus:

NUSE_SEMAPHORE_NUMBER * 2

Otherwise it is just NUSE_SEMAPHORE_NUMBER .

Unimplemented API Calls

Three semaphore API calls found in Nucleus RTOS are not implemented in Nucleus SE:

Create Semaphore

This API call creates a semaphore. It is not needed with Nucleus SE, as semaphores are created statically.

Service call prototype:

STATUS NU_Create_Semaphore(NU_SEMAPHORE *semaphore, CHAR *name,
UNSIGNED initial_count, OPTION suspend_type);

Parameters:

semaphore – pointer to a user-supplied semaphore control block; this will be used as a “handle” for the semaphore in other API calls

name – pointers to a 7-character, null-terminated name for the semaphore

initial_count – the initial value of the semaphore

suspend_type – specifies how tasks suspend on the semaphore. Valid options for this parameter are NU_FIFO and NU_PRIORITY , which represent First-In-First-Out (FIFO) and priority-order task suspension, respectively

Returns:

NU_SUCCESS – indicates successful completion of the service

NU_INVALID_SEMAPHORE – indicates the semaphore control block pointer is NULL or already in use

NU_INVALID_SUSPEND – indicates that the suspend_type parameter is invalid

Delete Semaphore

This API call deletes a previously created semaphore. It is not needed with Nucleus SE, as semaphores are created statically and cannot be deleted.

Service call prototype:

STATUS NU_Delete_Semaphore(NU_SEMAPHORE *semaphore);  

Parameters:

semaphore – pointer to semaphore control block

Returns:

NU_SUCCESS – indicates successful completion of the service

NU_INVALID_SEMAPHORE – indicates the semaphore pointer is invalid

Semaphore Pointers

This API call builds a sequential list of pointers to all semaphores in the system. It is not needed with Nucleus SE, as semaphores are identified by a simple index, not a pointer, and it would be redundant.

Service call prototype:

UNSIGNED NU_Semaphore_Pointers(NU_SEMAPHORE **pointer_list,
UNSIGNED maximum_pointers);

Parameters:

pointer_list – pointer to an array of NU_SEMAPHORE pointers; this array will be filled with pointers to established semaphores in the system

maximum_pointers – the maximum number of pointers to place in the array

Returns:

The number of NU_SEMAPHORE pointers placed into the array

Compatibility with Nucleus RTOS

With all aspects of Nucleus SE, it was my goal to maintain as high a level of applications-code compatibility with Nucleus RTOS as possible. Semaphores are no exception, and from a user’s perspective, they are implemented in much the same way as in Nucleus RTOS. There are areas of incompatibility, which have come about where I determined that such an incompatibility would be acceptable, given that the resulting code is easier to understand, or more likely, could be made more memory efficient. Otherwise, Nucleus RTOS API calls may be almost directly mapped onto Nucleus SE calls. A future article will include further information on using Nucleus SE for users of Nucleus RTOS.

Object Identifiers

In Nucleus RTOS, all objects are described by a data structure – a control block – which has a specific data type. A pointer to this control block serves as an identifier for the semaphore. In Nucleus SE, I decided that a different approach was needed for memory efficiency, and all kernel objects are described by a number of tables in RAM and/or ROM. The size of these tables is determined by the number of each object type that is configured. The identifier for a specific object is simply an index into those tables. So, I have defined NUSE_SEMAPHORE as being equivalent to U8 ; a variable – not a pointer – of this type then serves as the semaphore identifier. This is a small incompatibility, which is easily handled if code is ported to or from Nucleus RTOS. Object identifiers are normally just stored and passed around and not operated upon in any way.

Nucleus RTOS also supports naming of semaphores. These names are only used for target-based debug facilities. I omitted them from Nucleus SE to save memory.

Counter Size

In Nucleus RTOS, a semaphore counter is an unsigned , which is generally a 32-bit variable. In Nucleus SE, the counter is 8 bits; this could be easily changed. Normally, there is no check on overflow for semaphore releases in Nucleus RTOS. The Nucleus SE API call will not allow the counter to be incremented beyond 255.

Unimplemented API Calls

Nucleus RTOS supports eight service calls to work with semaphores. Of these, three are not implemented in Nucleus SE. Details of these and of the decision to omit them may be found in Unimplemented API Calls earlier in this article.

The next RTOS Revealed article will look at mailboxes.


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

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.