1, Semaphore introduction
Semaphores are an important part of the operating system. Semaphores are generally used for resource management and task synchronization. Semaphores in FreeRTOS are divided into binary semaphores, counting semaphores, mutually exclusive semaphores and recursive mutually exclusive semaphores. Different semaphores have different application scenarios, but some application scenarios can be used interchangeably.
2, Binary semaphore
- brief introduction
A binary semaphore is actually a queue with only one queue item. This special queue is either full or empty. Binary semaphores are usually used for mutually exclusive access or task synchronization. Binary semaphores are very similar to mutually exclusive semaphores, but there are still some subtle differences. Mutually exclusive semaphores have priority inheritance mechanism, and binary semaphores have no priority inheritance. Therefore, binary signals are more suitable for synchronization (synchronization between tasks or between tasks and interrupts), and mutually exclusive semaphores are suitable for simple mutually exclusive access. (what is mutually exclusive access)
The semaphore API function allows you to set a blocking time, which is the maximum number of clock beats that cause the task to enter the blocking state due to the invalid semaphore when the task obtains the semaphore. If multiple tasks are blocked on the same semaphore at the same time, which task with the highest priority gets the semaphore first, so that when the semaphore is valid, the high priority task will be unblocked.
Binary semaphore use case
In the past, when using the DTU module, we always created a task. In the while, we kept querying whether the 4G module sent messages, and then processed when there was data. This way wasted CPU resources. The most ideal method is that when there is no network data, the network task will enter the blocking state and give the CPU to other tasks. When there is data, the network task will be executed. The binary semaphore is used. The interrupt service function only needs to release the semaphore, while the binary semaphore is always requested in the network processing function. When the request arrives, it can be processed. If an interrupt only notifies one task, the task notification function can be used to replace the binary semaphore, and the task notification is faster and less code.
Operation example diagram:
- Related API functions
- Creation of binary semaphore
New dynamic binary semaphore creation
SemaphoreHandle_t xSemaphoreCreateBinary( void ) //Handle to the successfully created binary semaphore. Otherwise, NULL
- Release of binary semaphore
This function is used to release binary semaphores, count semaphores, or mutually exclusive semaphores. This function is a macro that really releases semaphores
The process of number quantity is completed by the function xQueueGenericSend()
BaseType_t xSemaphoreGive( xSemaphore )//Returns the pdPASS release semaphore. //Return errQUEUE_FULL: failed to release semaphore.
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, //Semaphore handle to get BaseType_t * pxHigherPriorityTaskWoken) /**Mark whether to switch tasks after exiting this function. The value of this variable is determined by The user does not need to set it. The user only needs to provide one Just create a variable to hold this value. When this value is pdTRUE, it is returned A task switch must be performed before the interrupt service function**/ //Function to switch tasks according to pxHigherPriorityTaskWoken portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
- Get semaphore
In task function
//pdTRUE: semaphore acquisition succeeded. //pdFALSE: timeout, failed to get semaphore. BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,//Semaphore handle to get TickType_t xBlockTime) //Blocking time
In interrupt function
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore, //Semaphore handle to get BaseType_t * pxHigherPriorityTaskWoken) /**Mark whether to switch tasks after exiting this function. The value of this variable is determined by The user does not need to set it. The user only needs to provide one Just create a variable to hold this value. When this value is pdTRUE, it is returned A task switch must be performed before the interrupt service function**/ //Function to switch tasks according to pxHigherPriorityTaskWoken portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
3, Counting semaphore
Counting semaphores are used for event counting and resource management.
- Event count
In this case, each time an event occurs, the semaphore is released in the event processing function (increase the count value of the semaphore), and other tasks will obtain the semaphore (the count value of the semaphore is minus one, and the semaphore value is the member variable uxmessageswaiting of the queue structure) to process the event. In this case, the initial count value of the count semaphore created is 0.
- resource management
In this case, the semaphore value represents the available quantity of current resources, such as the number of remaining parking spaces in the parking lot. If a task wants to obtain the right to use resources, it must first obtain the semaphore. After the semaphore is successfully obtained, the semaphore value will be reduced by one. When the semaphore value is 0, there are no resources. When a task uses up resources, it must release semaphores. After releasing semaphores, the semaphore value will be increased by one. In this case, the initial value of the count semaphore created should be the number of resources. For example, there are 100 parking spaces in the parking lot. When creating a semaphore, the semaphore value should be initialized to 100.
Dynamic method creation
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,//Maximum count value of count semaphore UBaseType_t uxInitialCount )//Count semaphore initial value
Static method creation
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, //Maximum count value of count semaphore UBaseType_t uxInitialCount,//Initial value StaticSemaphore_t * pxSemaphoreBuffer ) //Point to a staticsemaphore_ A variable of type T, which is used to save the semaphore structure.
The functions of transmitted semaphore and acquired semaphore and binary semaphore are the same
4, Mutually exclusive semaphore
1. Application scenarios of mutually exclusive semaphores --------- > > priority reversal
High priority tasks are blocked by low priority tasks, resulting in the delay of high priority tasks. But other medium priority tasks can grab CPU resources-- From a phenomenal point of view, it seems that medium priority tasks have higher priority than high priority tasks.
The operation process is shown in the figure below:
(1) Task H and task M are suspended, waiting for an event to occur, and task L is running.
(2) At a certain time, task L wants to access the shared resource. Before that, it must obtain the semaphore corresponding to the resource.
(3) Task L obtains the semaphore and starts using the shared resource.
(4) Because task H has high priority, it deprives task L of CPU usage after the event it waits for occurs.
(5) Task H starts running.
(6) During the operation of task H, the resource being used by task L should also be used, because the semaphore of the resource is also used by the task
When L is occupied, task H can only enter the suspended state and wait for task L to release the semaphore.
(7) Task L continues.
(8) Since the priority of task M is higher than task L, task M deprives the task after the event that task M waits for occurs
L's CPU usage rights.
(9) Task M handles what should be handled.
(10) After the execution of task M, return the CPU usage right to task L.
(11) Task L continues.
(12) The final task L completes all the work and releases the semaphore. So far, the real-time kernel knows that there is a high
Priority tasks are waiting for this semaphore, so the kernel does task switching.
(13) Task H obtains the semaphore and then runs.
FreeRTOS is a real-time operating system. The priority order is H > m > L, but the actual operation effect is m > L > H, which will cause a high priority task to wait for a low priority task, but the low priority task cannot execute a deadlock.
2. Priority inheritance
Priority inheritance is an optimization mechanism proposed to solve the priority inversion problem. The general principle is to temporarily raise the priority of low priority tasks when they obtain a mutex semaphore (if a high priority thread also needs to use the mutex semaphore). Previously, it could execute faster and release synchronization resources. Release the synchronization resource before restoring its original priority.
3. Use of mutually exclusive semaphores
Mutually exclusive semaphores have priority inheritance. Priority inheritance can not completely eliminate priority reversal, but only reduce the impact of priority reversal as much as possible.
Mutex signals cannot be used in interrupts for the following reasons:
- In the interrupt service function, you cannot set the blocking time to enter the blocking state because you want to wait for a mutually exclusive semaphore
- Calling mutex in interrupt will break the priority inheritance.
Create mutually exclusive semaphores
/** Return value ** NULL: Mutex semaphore creation failed. ** Other values: handle to create a successful mutex semaphore. **/ SemaphoreHandle_t xSemaphoreCreateMutex( void ) SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer ) //pxMutexBuffer: this parameter points to a staticsemaphore_ A variable of type T, which is used to save the semaphore structure
5, Recursive semaphore
In the mutex semaphore, the task that has obtained the mutex semaphore cannot obtain the mutex semaphore again. The recursive mutex semaphore can be regarded as a special mutex semaphore. The task that has obtained the recursive mutex semaphore can obtain the recursive mutex semaphore again, and the number of times is unlimited!
How many times a task successfully obtains a recursive mutex semaphore using the function xsephoretakerecursive() must be released using the function xsephoregiverecursive()! For example, if a task successfully obtains 5 recursive semaphores, the task must also release 5 recursive semaphores.
1. Create recursive semaphore
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void )//Dynamic method creation SemaphoreHandle_t xSemaphoreCreateRecursiveMutexStatic( StaticSemaphore_t *pxMutexBuffer )//Static method creation //pxMutexBuffer: this parameter points to a staticsemaphore_ A variable of type T, which is used to save the semaphore structure.
2. Get and get recursive semaphores
xSemaphoreGiveRecursive( xMutex )//Recursive mutex semaphore handle xSemaphoreTakeRecursive( xMutex, //Recursive mutex semaphore handle xBlockTime )//Blocking time