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.
RTOS; Nucleus; semaphores
Semaphores were introduced in Chapter 3, RTOS Services and Facilities. Their primary use is the control of access to resources.
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.
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.
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.
Nucleus RTOS supports eight service calls that appertain to semaphores, which provide the following functionality:
The implementation of each of these service calls will be examined in detail.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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).
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.
Service call prototype:
UNSIGNED NU_Established_Semaphores(VOID);
Parameters:
None
Returns:
The number of created semaphores in the application
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
The implementation of this API call is almost trivially simple: the value of the #define symbol NUSE_SEMAPHORE_NUMBER is returned.
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.
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
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 */
};
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.
Three semaphore API calls found in Nucleus RTOS are not implemented in Nucleus SE:
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
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.
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
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.
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.
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.
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.
3.15.25.32