
Semaphores have been mentioned in a previous article (# 5). Their main task is to control access to resources.
Previous articles in the series:
Article # 18. Event flag groups: helper services and data structuresArticle # 17. Event flag groups: introduction and basic servicesArticle # 16. SignalsArticle # 15. Memory sections: services and data structuresArticle # 14. Memory sections: introduction and basic servicesArticle # 13. Task data structures and unsupported API callsArticle # 12. Task ServicesArticle # 11. Tasks: configuration and introduction to the APIArticle # 10. Scheduler: additional features and context preservationArticle # 9. Scheduler: implementationArticle # 8. Nucleus SE: Inside and DeploymentArticle # 7. Nucleus SE: introductionArticle # 6. Other RTOS servicesArticle # 5. Interaction between tasks and synchronizationArticle # 4. Tasks, context switching and interruptsArticle # 3. Tasks and planningArticle # 2. RTOS: Structure and Real Time
Article # 1. RTOS: introduction.
Using semaphores
In Nucleus SE, semaphores are defined at the assembly stage. An application can have up to 16 semaphores. If the semaphores are not specified, the code for service calls and data structures is not included in the application.
A semaphore is a
U8 type counter, access to which is controlled in such a way that several tasks can use it. The task can reduce the semaphore count (capture) or increase it (release). Attempting to capture a semaphore with a null value may result in an error or a suspension of the task, depending on the selected API call parameters and the configuration of the Nucleus SE.
')
Setting up semaphores
Number of semaphores
As with most Nucleus SE objects, the semaphore setting is determined by the
#define directives in
nuse_config.h . The main parameter is
NUSE_SEMAPHORE_NUMBER , which determines the number of semaphores in the application. By default, the parameter is set to 0 (semaphores are not used in the application) and can take any value up to 16. An incorrect value will result in a compilation error, which will be generated by checking in
nuse_config_check.h (this file is included in
nuse_config.c , which means together with this module), as a result the
#error directive will work.
The choice of a non-zero value serves as the main activator for semaphores. This parameter is used when defining data structures and their size depends on its size (for more details, read later in this article). In addition, a non-zero value activates the API settings.
Activate API calls
Each API function (service call) in the Nucleus SE is activated by the
#define directive in
nuse_config.h . For semaphores, these include:
NUSE_SEMAPHORE_OBTAIN
NUSE_SEMAPHORE_RELEASE
NUSE_SEMAPHORE_RESET
NUSE_SEMAPHORE_INFORMATION
NUSE_SEMAPHORE_COUNT
By default, they are assigned the value
FALSE , thus disabling each service call and blocking the inclusion of the code implementing them. To set up semaphores, you need to select the necessary API calls and set the corresponding directives to
TRUE .
The following is an excerpt from the default
nuse_config.h file.
#define NUSE_SEMAPHORE_NUMBER 0 #define NUSE_SEMAPHORE_OBTAIN FALSE #define NUSE_SEMAPHORE_RELEASE FALSE #define NUSE_SEMAPHORE_RESET FALSE #define NUSE_SEMAPHORE_INFORMATION FALSE #define NUSE_SEMAPHORE_COUNT FALSE
An activated API function in the absence of semaphores in the application will result in a compilation error (except
NUSE_Semaphore_Count () , which is always allowed). If your code uses an API call that has not been activated, a build error will occur because the implementation code was not included in the application.
Service calls semaphores
Nucleus RTOS supports eight service calls that provide the following functionality:
- Capture semaphore. In Nucleus SE, it is implemented as a function in NUSE_Semaphore_Obtain () .
- Semaphore release. The Nucleus SE is implemented as a function in NUSE_Semaphore_Release () .
- Returning the semaphore to an unused state with the release of all suspended tasks (reboot). In Nucleus SE, NUSE_Semaphore_Reset () is implemented.
- Providing information about a specific semaphore. In Nucleus SE, NUSE_Semaphore_Information () is implemented.
- Returns the number of configured semaphores in the application. In Nucleus SE, NUSE_Semaphore_Count () is implemented.
- Adding a new semaphore to the application. Nucleus SE is not implemented.
- Remove semaphore from application. Nucleus SE is not implemented.
- Return pointers to all semaphores. Nucleus SE is not implemented.
The implementation of each service call is described in detail below.
Service calls capture and release of semaphores
The fundamental operations that can be performed on semaphores are capture and release (decrease and increase in value). Nucleus RTOS and Nucleus SE provide two basic API calls for these operations.
Semaphore capture
The Nucleus RTOS service call to capture a semaphore is very flexible and allows you to suspend tasks implicitly or with a specific timeout if the operation cannot be performed at the moment, for example, if you try to capture a semaphore with a zero value. Nucleus SE provides the same functions, only task suspension is optional, and timeout is not implemented.
Call to capture semaphore in Nucleus RTOSService Call Prototype:
STATUS NU_Obtain_Semaphore (NU_SEMAPHORE * semaphore, UNSIGNED suspend);Options:
semaphore - pointer to user-supplied semaphore control block;
suspend - the option to pause the task; it can be
NU_NO_SUSPEND or
NU_SUSPEND , as well as the timeout value.
Return value:
NU_SUCCESS - the call was successfully completed;
NU_UNAVAILABLE - the semaphore had a zero value;
NU_INVALID_SEMAPHORE - invalid pointer to semaphore;
NU_INVALID_SUSPEND - an attempt to suspend from a
thread that is not associated with a task;
NU_SEMAPHORE_WAS_RESET - The semaphore was dropped while the task was suspended.
Call to capture semaphore in Nucleus SEThis API call supports Nucleus RTOS API key functionality.
Service Call Prototype:
STATUS NUSE_Semaphore_Obtain (NUSE_SEMAPHORE semaphore, U8 suspend);Options:
semaphore - index (id) of the semaphore used;
suspend - the option to suspend the task; it can be
NUSE_NO_SUSPEND or
NUSE_SUSPEND .
Return value:
NUSE_SUCCESS - the call was successfully completed;
NUSE_UNAVAILABLE - the semaphore had a zero value;
NUSE_INVALID_SEMAPHORE - incorrect semaphore index;
NUSE_INVALID_SUSPEND - an attempt to suspend from a
thread that is not associated with the task or when the API blocking functionality is disabled;
NUSE_SEMAPHORE_WAS_RESET - the semaphore was dropped while the task was suspended;
Implementing Semaphore Capture in Nucleus SEThe option code for the
NUSE_Semaphore_Obtain () function (after checking the parameters) is selected using conditional compilation depending on whether support for blocking (suspending) tasks is activated or not. Consider both options.
If the lock is not activated, the logic of this API call is quite simple:
if (NUSE_Semaphore_Counter[semaphore] != 0) { NUSE_Semaphore_Counter[semaphore]--; return_value = NUSE_SUCCESS; } else { return_value = NUSE_UNAVAILABLE; }
The value of the semaphore counter is checked, and, if it is not zero, decreases.
If task lock is activated, the logic becomes more complex:
do { if (NUSE_Semaphore_Counter[semaphore] != 0) { NUSE_Semaphore_Counter[semaphore]--; return_value = NUSE_SUCCESS; suspend = NUSE_NO_SUSPEND; } else { if (suspend == NUSE_NO_SUSPEND) { return_value = NUSE_UNAVAILABLE; } else { 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 explanations may be helpful.
The code is placed in a
do ... while loop , which runs until the
suspend parameter is
NUSE_SUSPEND .
If the semaphore has a nonzero value, it is reduced. The variable
suspend is assigned the value
NUSE_NO_SUSPEND , and the API call ends and returns the value
NUSE_SUCESS .
If the semaphore is null and the
suspend variable is set to
NUSE_NO_SUSPEND , the API call returns
NUSE_UNAVAILABLE . If the suspension was assigned the value
NUSE_SUSPEND , the task is suspended. After the call is completed (for example, when the task is resumed), if the return value is
NUSE_SUCCESS (which indicates that the task was resumed after the semaphore was released, not after the reset), the cycle starts from the beginning.
Semaphore release
The Nucleus RTOS API service call to release a semaphore is quite simple: the value of the semaphore counter increases and a success message is returned. Nucleus SE provides the same features, only with an additional check for overflow.
Call to release semaphores in Nucleus RTOSService Call Prototype:
STATUS NU_Release_Semaphore (NU_SEMAPHORE * semaphore);Options:
semaphore is a pointer to a semaphore control block provided by the user.
Return value:
NU_SUCCESS - the call was successfully completed;
NU_INVALID_SEMAPHORE - invalid pointer to semaphore.
Call to release the semaphore in the Nucleus SEThis API call supports the core Nucleus RTOS API.
Service Call Prototype:
STATUS NUSE_Semaphore_Release (NUSE_SEMAPHORE semaphore);Options:
semaphore - the index (ID) of the semaphore being released.
Return value:
NUSE_SUCCESS - the call was successfully completed;
NUSE_INVALID_SEMAPHORE - incorrect semaphore index;
NUSE_UNAVAILABLE - the semaphore is 255 and cannot be incremented.
Implementing Semaphore Release in Nucleus SEThe function code
NUSE_Semaphore_Release () (after checking the parameters) is common, regardless of whether the task lock is activated or not. The value of the semaphore counter is checked, and if it is less than 255, it increases.
Further code is selected using conditional compilation, if support for blocking API calls (task suspension) is activated:
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; 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 resumes.
The next article will describe additional API calls related to semaphores and their data structures.
About the author: Colin Walls has been working in the electronics industry for more than thirty years, spending a significant amount of time on embedded software. He is now an embedded software engineer in Mentor Embedded (a division of Mentor Graphics). Colin Walls often speaks at conferences and seminars, author of numerous technical articles and two books on embedded software. Lives in the UK.
Colin's professional
blog , e-mail: colin_walls@mentor.com.