📜 ⬆️ ⬇️

The whole truth about the RTOS. Article # 19. Semaphores: introduction and basic services



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 structures
Article # 17. Event flag groups: introduction and basic services
Article # 16. Signals
Article # 15. Memory sections: services and data structures
Article # 14. Memory sections: introduction and basic services
Article # 13. Task data structures and unsupported API calls
Article # 12. Task Services
Article # 11. Tasks: configuration and introduction to the API
Article # 10. Scheduler: additional features and context preservation
Article # 9. Scheduler: implementation
Article # 8. Nucleus SE: Inside and Deployment
Article # 7. Nucleus SE: introduction
Article # 6. Other RTOS services
Article # 5. Interaction between tasks and synchronization
Article # 4. Tasks, context switching and interrupts
Article # 3. Tasks and planning
Article # 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 /* 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 */ 

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:


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 RTOS
Service 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 SE
This 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 SE
The 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) /* semaphore available */ { NUSE_Semaphore_Counter[semaphore]--; return_value = NUSE_SUCCESS; } else /* semaphore unavailable */ { 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) /* 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 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 RTOS
Service 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 SE
This 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 SE
The 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; /* 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 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.

Source: https://habr.com/ru/post/429156/


All Articles