FreeRTOS semaphore (4) ------ mutual exclusion semaphore


1. Introduction to mutual exclusion semaphore

Mutual exclusion semaphore is actually a binary semaphore with priority inheritance. In synchronous applications (synchronization between tasks and tasks or between interrupts and tasks), binary semaphores are most suitable. Mutex semaphores are suitable for applications that require mutually exclusive access. In mutual exclusion access, the mutex semaphore is equivalent to a key. When the task wants to use the resource, it must first obtain the key. When the resource is used up, the key must be returned, so that other tasks can hold the key. to use resources.

The mutex semaphore uses the same API operation function as the binary semaphore, so the mutex semaphore can also set the blocking time. What is different from the binary semaphore is that the mutex semaphore has the characteristic of priority inheritance. When a mutex semaphore is being used by a low-priority task, and a high-priority task also tries to acquire the mutex semaphore, it will be blocked. However, this high-priority task will raise the priority of the low-priority task to the same priority as itself. This process is priority inheritance. Priority inheritance minimizes the time that high-priority tasks are in the blocking state, and minimizes the impact of "priority inversion" that has occurred.

Priority inheritance cannot completely eliminate priority inversion, it just reduces the impact of priority inversion as much as possible. Hard real-time applications should avoid priority inversion from the beginning of the design. Mutex semaphores cannot be used in interrupt service functions for the following reasons:
● Mutex semaphores have a priority inheritance mechanism, so they can only be used in tasks and cannot be used in interrupt service functions.
● In the interrupt service function, the blocking time cannot be set to enter the blocking state because it needs to wait for the mutex semaphore.

Second, create a mutex semaphore

FreeRTOS provides two mutex semaphore creation functions, as shown in the following table:
insert image description here

1. xSemaphoreCreateMutex()

This function is used to create a mutex semaphore, and the required memory is allocated through the dynamic memory management method. This function is essentially a macro. The function xQueueCreateMutex() actually completes the creation of the semaphore. The prototype of this function is as follows:

SemaphoreHandle_t xSemaphoreCreateMutex( void )

Parameters:
None.

Return value:
NULL: Mutex semaphore creation failed.
Other values: The handle to the successfully created mutex semaphore.

2. xSemaphoreCreateMutexStatic()

This function also creates a mutex semaphore, but the RAM required by the semaphore needs to be allocated by the user if this function is used to create a mutex semaphore. This function is a macro, and the specific creation process is completed through the function xQueueCreateMutexStatic (). , the function prototype is as follows:

SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer )

Parameters:
pxMutexBuffer: This parameter points to a variable of type StaticSemaphore_t, which is used to save the semaphore structure.

Return value:
NULL: Mutex semaphore creation failed.
Other values: The handle to the successfully created mutex semaphore.

3. Analysis of Mutex Semaphore Creation Process

Here we only analyze the dynamic creation of mutex semaphore function xSemaphoreCreateMutex (), this function is a macro, defined as follows:

#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )

It can be seen that the real function is the function xQueueCreateMutex(), which is defined in the file queue.c as follows,

QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{
    
    
	Queue_t *pxNewQueue;
	const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
	pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize,\ (1)
	 ucQueueType );
	prvInitialiseMutex( pxNewQueue ); (2)
	return pxNewQueue;
}

(1) Call the function xQueueGenericCreate() to create a queue, the queue length is 1, the queue item length is 0, and the queue type is the parameter ucQueueType. Since this function creates a mutex semaphore, the parameter ucQueueType is queueQUEUE_TYPE_MUTEX.

(2) Call the function prvInitialiseMutex() to initialize the mutex semaphore.
The function prvInitialiseMutex() code is as follows:

static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
    
    
	if( pxNewQueue != NULL )
	{
    
    
		//虽然创建队列的时候会初始化队列结构体的成员变量,但是此时创建的是互斥
		//信号量,因此有些成员变量需要重新赋值,尤其是那些用于优先级继承的。
		pxNewQueue->pxMutexHolder = NULL; (1)
		pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX; (2)
		//如果是递归互斥信号量的话。
		pxNewQueue->u.uxRecursiveCallCount = 0; (3)
		traceCREATE_MUTEX( pxNewQueue );
		//释放互斥信号量
		( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U,\
		 queueSEND_TO_BACK );
	}
	else
	{
    
    
		traceCREATE_MUTEX_FAILED();
	}
}

(1) and (2), the two member variables pxMutexHolder and uxQueueType are macros, specially prepared for mutual exclusion semaphores, and are defined in the file queue.c as follows:

#define pxMutexHolder pcTail
#define uxQueueType pcHead
#define queueQUEUE_IS_MUTEX NULL

When Queue_t is used to represent a queue, pcHead and pcTail point to the storage area of ​​the queue. When Queue_t is used to represent a mutual exclusion semaphore, pcHead and pcTail are not needed. When used for a mutex semaphore, set pcHead to NULL to indicate that pcTail holds the owner of the mutex queue, and pxMutexHolder points to the task control block of the task that owns the mutex semaphore. The reason for renaming pcTail and pcHead is to improve the readability of the code.

(3) If the created mutex semaphore is a mutex semaphore, the member variable u.uxRecursiveCallCount in the queue structure needs to be initialized.

After the mutex semaphore is successfully created, the function xQueueGenericSend() will be called to release the semaphore once, indicating that the mutex semaphore is valid by default! After the mutex semaphore is created, it is shown in the following figure:
insert image description here

Fourth, release the mutex semaphore

When releasing the mutex semaphore, it is the same as the binary semaphore and the counting semaphore, using the function xSemaphoreGive() (in fact, the function xQueueGenericSend() is used to complete the release of the semaphore). However, since the mutex semaphore involves the issue of priority inheritance, the specific processing process will be a little different. The most important step to use the function xSemaphoreGive() to release the semaphore is to add one to uxMessagesWaiting, and this step is done through the function prvCopyDataToQueue(). The function xQueueGenericSend() that releases the semaphore will call prvCopyDataToQueue(). The priority inheritance of the mutex semaphore is also completed in the function prvCopyDataToQueue(), which contains the following piece of code:

static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, 
									  const void * pvItemToQueue, 
									  const BaseType_t xPosition )
{
    
    
	BaseType_t xReturn = pdFALSE;
	UBaseType_t uxMessagesWaiting;
	uxMessagesWaiting = pxQueue->uxMessagesWaiting;
	if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
	{
    
    
		#if ( configUSE_MUTEXES == 1 ) //互斥信号量
		{
    
    
			if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) (1)
			{
    
    
				xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );(2)
				pxQueue->pxMutexHolder = NULL; (3)
			}
			else
			{
    
    
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif /* configUSE_MUTEXES */
	}
	/*********************************************************************/
	/*************************省略掉其他处理代码**************************/
	/*********************************************************************/
	pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;
	return xReturn;
}

(1) The current operation is a mutex semaphore.

(2) Call the function xTaskPriorityDisinherit() to deal with the priority inheritance of the mutex semaphore.

(3) After the mutex semaphore is released, the mutex semaphore does not belong to any task, so pxMutexHolder should point to NULL.

Let's take a look at how the function xTaskPriorityDisinherit() specifically handles priority inheritance. The code of the function xTaskPriorityDisinherit() is as follows:

BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
    
    
	TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
	BaseType_t xReturn = pdFALSE;
	if( pxMutexHolder != NULL ) (1)
	{
    
    
		//当一个任务获取到互斥信号量以后就会涉及到优先级继承的问题,正在释放互斥
		//信号量的任务肯定是当前正在运行的任务 pxCurrentTCB。
		configASSERT( pxTCB == pxCurrentTCB );
		configASSERT( pxTCB->uxMutexesHeld );
		( pxTCB->uxMutexesHeld )--; (2)
		//是否存在优先级继承?如果存在的话任务当前优先级肯定和任务基优先级不同。
		if( pxTCB->uxPriority != pxTCB->uxBasePriority ) (3)
		{
    
    
			//当前任务只获取到了一个互斥信号量
			if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 ) (4)
			{
    
    
				if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) (5)
				{
    
    
					taskRESET_READY_PRIORITY( pxTCB->uxPriority ); (6)
				}
				else
				{
    
    
					mtCOVERAGE_TEST_MARKER();
				}
				//使用新的优先级将任务重新添加到就绪列表中
				traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
				pxTCB->uxPriority = pxTCB->uxBasePriority; (7)
				/* Reset the event list item value. It cannot be in use for
				any other purpose if this task is running, and it must be
				running to give back the mutex. */
				listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), \ (8)
				( TickType_t ) configMAX_PRIORITIES - \
				( TickType_t ) pxTCB->uxPriority );
				prvAddTaskToReadyList( pxTCB ); (9)
				xReturn = pdTRUE; (10)
			}
			else
			{
    
    
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
    
    
			mtCOVERAGE_TEST_MARKER();
		}
	}
	else
	{
    
    
	mtCOVERAGE_TEST_MARKER();
	}
	return xReturn;
}

(1) The parameter pxMutexHolder of the function indicates that the task control block owns the mutex semaphore, so it is necessary to judge whether the mutex semaphore has been acquired by other tasks.

(2) Some tasks may acquire multiple mutex semaphores, so it is necessary to mark the number of mutex semaphores currently acquired by the task, and the member variable uxMutexesHeld of the task control block structure is used to save the mutex semaphores acquired by the current task The number of mutex semaphores. Every time a task releases the mutex semaphore, the variable uxMutexesHeld must be reduced by one.

(3) Determine whether there is priority inheritance, if it exists, the current priority of the task must not be equal to the base priority of the task.

(4), judging whether the current release is the last mutex semaphore acquired by the task, because if the task also acquires other mutex semaphores, priority inheritance cannot be processed. Priority inheritance must be handled when the last mutex semaphore is released.

(5) To put it bluntly, the processing of priority inheritance is to reduce the current priority of the task to the base priority of the task, so the current task must be removed from the task ready table first. When the task priority returns to the original priority, it will be added to the ready list again.

(6) If there is no other task in the ready list corresponding to the priority inherited by the task, the ready state of this priority will be canceled.

(7) Reset the priority of the task to the base priority uxBasePriority of the task.

(8) The event list item of the reset task.

(9). Re-add the task whose priority has been restored to the task ready list.

(10) Return pdTRUE, indicating that task scheduling needs to be performed.

5. Get the mutex semaphore

The function to obtain a mutex semaphore is the same as the function to obtain a binary semaphore and a counting semaphore, both are xSemaphoreTake() (the function that actually executes the semaphore acquisition is xQueueGenericReceive()), and the process of obtaining a mutex semaphore also requires To deal with the problem of priority inheritance, the function xQueueGenericReceive() is defined in the file queue.c. This function was not analyzed in the previous queue. Here is a brief analysis of this function. The reduced function code is as follows:

BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t 
xTicksToWait, const BaseType_t xJustPeeking )
{
    
    
	BaseType_t xEntryTimeSet = pdFALSE;
	TimeOut_t xTimeOut;
	int8_t *pcOriginalReadPosition;
	Queue_t * const pxQueue = ( Queue_t * ) xQueue;
	for( ;; )
	{
    
    
		taskENTER_CRITICAL();
	{
    
    
	const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
	//判断队列是否有消息
	if( uxMessagesWaiting > ( UBaseType_t ) 0 ) (1)
	{
    
    
		pcOriginalReadPosition = pxQueue->u.pcReadFrom;
		prvCopyDataFromQueue( pxQueue, pvBuffer ); (2)
		if( xJustPeeking == pdFALSE ) (3)
		{
    
    
			traceQUEUE_RECEIVE( pxQueue );
			//移除消息
			pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1; (4)
			#if ( configUSE_MUTEXES == 1 ) (5)
			{
    
    
				if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
				{
    
    
					pxQueue->pxMutexHolder = (6)
					 ( int8_t * ) pvTaskIncrementMutexHeldCount(); 
				}
				else
				{
    
    
					mtCOVERAGE_TEST_MARKER();
				}
			}
			#endif /* configUSE_MUTEXES */
			//查看是否有任务因为入队而阻塞,如果有的话就需要解除阻塞态。
			if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == (7)
			pdFALSE )
			{
    
    
				if( xTaskRemoveFromEventList( &
				( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
				{
    
    
					//如果解除阻塞的任务优先级比当前任务优先级高的话就需要
					//进行一次任务切换
					queueYIELD_IF_USING_PREEMPTION();
				}
				else
				{
    
    
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
    
    
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else (8)
		{
    
    
			traceQUEUE_PEEK( pxQueue );
			//读取队列中的消息以后需要删除消息
			pxQueue->u.pcReadFrom = pcOriginalReadPosition;
			//如果有任务因为出队而阻塞的话就解除任务的阻塞态。
			if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == (9)
			pdFALSE )
			{
    
    
				if( xTaskRemoveFromEventList( &
				( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
				{
    
    
					//如果解除阻塞的任务优先级比当前任务优先级高的话就需要
					//进行一次任务切换
					queueYIELD_IF_USING_PREEMPTION();
				}
				else
				{
    
    
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
    
    
				mtCOVERAGE_TEST_MARKER();
			}
		}
		taskEXIT_CRITICAL();
		return pdPASS;
	}
	else //队列为空 (10)
	{
    
    
		if( xTicksToWait == ( TickType_t ) 0 )
		{
    
    
			//队列为空,如果阻塞时间为 0 的话就直接返回 errQUEUE_EMPTY
			taskEXIT_CRITICAL();
			traceQUEUE_RECEIVE_FAILED( pxQueue );
			return errQUEUE_EMPTY;
		}
		else if( xEntryTimeSet == pdFALSE )
		{
    
    
			//队列为空并且设置了阻塞时间,需要初始化时间状态结构体。
			vTaskSetTimeOutState( &xTimeOut );
			xEntryTimeSet = pdTRUE;
		}
		else
		{
    
    
			mtCOVERAGE_TEST_MARKER();
		}
	}
}
		taskEXIT_CRITICAL();
		vTaskSuspendAll();
		prvLockQueue( pxQueue );
		//更新时间状态结构体,并且检查超时是否发生
		if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) (11)
		{
    
    
			if( prvIsQueueEmpty( pxQueue ) != pdFALSE ) (12)
			{
    
    
				traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
				#if ( configUSE_MUTEXES == 1 )
				{
    
    
					if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) (13)
					{
    
    
						taskENTER_CRITICAL();
						{
    
    
							vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );(14)
						}
						taskEXIT_CRITICAL();
					}
					else
					{
    
    
						mtCOVERAGE_TEST_MARKER();
					}
				}
				#endif
				vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), (15)
				xTicksToWait );
				prvUnlockQueue( pxQueue );
				if( xTaskResumeAll() == pdFALSE )
				{
    
    
					portYIELD_WITHIN_API();
				}
				else
				{
    
    
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
    
    
				//重试一次
				prvUnlockQueue( pxQueue );
				( void ) xTaskResumeAll();
			}
		}
		else
		{
    
    
			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();
			if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
			{
    
    
				traceQUEUE_RECEIVE_FAILED( pxQueue );
				return errQUEUE_EMPTY;
			}
			else
			{
    
    
				mtCOVERAGE_TEST_MARKER();
			}
		}
	}
}

(1), the queue is not empty, and data can be extracted from the queue.

(2) Call the function prvCopyDataFromQueue() to extract data from the queue by means of data copy.

(3) After the data is read, the data needs to be deleted.

(4) The message quantity counter uxMessagesWaiting of the queue is decremented by one, and the data is deleted through this step.

(5), indicating that this function is used to obtain a mutex semaphore.

(6) To obtain the mutex semaphore successfully, it is necessary to mark the owner of the mutex semaphore, that is, to assign a value to pxMutexHolder, which should be the task control block of the current task. But here, the value is assigned by the function pvTaskIncrementMutexHeldCount(). This function is very simple. It just adds one to the member variable uxMutexesHeld in the task control block, indicating that the task has obtained a mutex semaphore. Finally, this function returns the task control block of the current task. .

(7) After the dequeue is successful, it is judged whether there are tasks blocked due to enqueueing. If so, the blocking state of the task needs to be unblocked. If the priority of the unblocked task is higher than the priority of the current task, a task needs to be performed switch.

(8) There is no need to delete the message when leaving the team.

(9) If there is no need to delete the message when leaving the team, it is equivalent to the message that was just left the team and then valid! Since there are still effective messages in the queue, it is judged whether there is a task blocked due to dequeue, and if so, the blocking state of the task is released. Similarly, if the priority of the unblocked task is higher than the priority of the current task, a task switch is required.

(10) The above analysis is all when the queue is not empty, so how to deal with it when the queue is empty? The processing process is similar to the task-level general enqueue function xQueueGenericSend() of the queue. If the blocking time is 0, it will directly return errQUEUE_EMPTY, indicating that the queue is empty. If the blocking time is set, it will perform related processing.

(11), check whether the timeout occurs, if not, the task needs to be added to the xTasksWaitingToReceive list of the queue.

(12) Check whether the queue continues to be empty? If it is not empty, it will retry to leave the team once.

(13), indicating that this function is used to obtain a mutex semaphore.

(14) Call the function vTaskPriorityInherit() to deal with the priority inheritance problem in the mutex semaphore. If the function xQueueGenericReceive() is used to obtain the mutex semaphore, this function executes here to indicate that the mutex semaphore is being used by other tasks occupy. This function is opposite to the function xTaskPriorityDisinherit() in Section 14.8.4. This function will judge whether the task priority of the current task is higher than the task priority of the task that is owning the mutex semaphore, and if so, it will adjust the priority of the low priority task that owns the mutex semaphore to the same Same priority as current task!

(15), after step (12), if the queue is still empty, add the task to the list xTasksWaitingToReceive.

In the above analysis, the red part is the processing process when the function xQueueGenericReceive() is used for the mutex semaphore, among which (13) and (14) analyze the priority inheritance process of the mutex semaphore in detail. Let's take an example to briefly demonstrate this process. Suppose there are two tasks, HighTask and LowTask, the task priority of HighTask is 4, and the task priority of LowTask is 2. These two tasks will operate the same mutual exclusion semaphore Mutex, and LowTask first obtains the mutual exclusion semaphore Mutex. At this time, the task HighTask also needs to obtain the mutex semaphore Mutex. The task HighTask calls the function xSemaphoreTake() to try to acquire the mutex semaphore Mutex, and finds that the mutex semaphore is being used by the task LowTask, and the task priority of LowTask is 2, which is higher than The priority of its own task is small, because the task HighTask will adjust the task priority of LowTask to the same priority as itself, that is, 4, and then the task HighTask enters the blocking state and waits for the mutex semaphore to be valid.

Guess you like

Origin blog.csdn.net/Dustinthewine/article/details/130416458