10

Semaphores

Abstract

Semaphores are a very simple method of intertask synchronization supported by Nucleus SE. They provide a low-cost means of controlling the use of resources between tasks. In this chapter, the operation of semaphores is outlined, with details of all the relevant application program interfaces. The implementation of semaphores in Nucleus SE is also detailed.

Keywords

RTOS; Nucleus; semaphores

Semaphores were introduced in Chapter 3, RTOS Services and Facilities. 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 is 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 application program interface (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 nonzero value is the “master enable” for semaphores. This results in some data structures being defined and sized accordingly, of which more later in this chapter. 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 will be 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; that is, you try to obtain a semaphore that 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 nontask thread.

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 nontask 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 nonzero, 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 nonzero, 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_UNAVAILABLE. 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 is less than 255, incremented.

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.

Semaphore utility services

Nucleus RTOS has four API calls that 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 that 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.

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” section) and NUSE_Semaphore_Blocking_Count[] is set to zero by NUSE_Init_Semaphore() when Nucleus SE starts up. Chapter 17, Nucleus SE Initialization and Start-up, provides 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.

NU_INVALID_SUSPEND—indicates that the suspend_type parameter is invalid.t

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. Chapter 20, Using Nucleus SE, includes 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 on 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 were outlined earlier in this chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.25.32