FreeRTOS series | mutually exclusive semaphores

Posted by slimboy007 on Fri, 04 Mar 2022 15:49:33 +0100

Mutually exclusive semaphore

1. Priority reversal

Priority reversal is a common problem when using binary semaphores. It is very common in the deprivable kernel, but it is not allowed in the real-time system, because it will destroy the expected sequence of tasks and may lead to serious consequences.

An example of priority reversal is shown in the following figure:

  • Low priority task L needs to access shared resources. In the process of obtaining the semaphore to use CPU, if high priority task h arrives at this time, it will deprive L of CPU usage and run task H
  • When H wants to access a shared resource, because the semaphore of the resource is still occupied by L, H can only hang up and wait for L to release the semaphore
  • L continues to run. At this time, the medium priority task m arrives and deprives l of CPU usage again to run task M
  • After M executes, return the CPU usage right to task L, and l continues to run
  • After L runs and releases the semaphore, the high priority task H can obtain the semaphore, access the shared resources and run


As can be seen from the above figure, the priority of task H is actually reduced to the priority level of task L, because I have to wait for L to release the shared resources it occupies. In the process, the medium priority task m, which does not need to use shared resources, runs smoothly after preempting the CPU use right. In this way, it is equivalent to that the priority of M is higher than that of J, resulting in priority reversal

2. Mutually exclusive semaphore

Mutually exclusive semaphore is actually a binary semaphore with priority inheritance. Binary signals are suitable for synchronous applications, and mutually exclusive signals are suitable for applications requiring mutually exclusive access. In mutually exclusive access, the mutually exclusive semaphore is equivalent to a key. When the task wants to use the resource, it must obtain the key first, and return the key after using the resource

When a mutex semaphore is being used by a low priority task, it will be blocked if a high priority task also tries to obtain the mutex semaphore. However, at this time, the high priority task will raise the priority of the low priority task to the same level as itself (i.e. priority inheritance). Priority inheritance only reduces the impact of priority reversal as much as possible, but can not completely eliminate priority reversal

3. API function of mutually exclusive semaphore

3.1 creating mutually exclusive semaphores
/********************Dynamically create mutually exclusive semaphores**********************************************/
SemaphoreHandle_t xSemaphoreCreateMutex(void)
/********************Create mutex semaphores statically**********************************************/
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t * pxSemaphoreBuffer)
//Parameter: pxSemaphoreBuffer points to a staticsemaphore_ A variable of type T, which is used to save the semaphore structure
/***********************************************************************************/
Return value: the mutex semaphore handle is returned after successful creation; Failure Return NULL

The create mutex() function is a dynamic mutex function, which is completed through the following source code:

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateMutex()  xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)		
#endif
/**************xQueueCreateMutex Source code analysis*********************/
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType ){
  Queue_t *pxNewQueue;
  const UBaseType_t uxMutexLength = (UBaseType_t) 1,uxMutexSize = (UBaseType_t) 0;
  /* Create a queue with a queue length of 1, a queue item length of 0, and a queue type of queueQUEUE_TYPE_MUTEX queue */
  pxNewQueue = (Queue_t *)xQueueGenericCreate(uxMutexLength,uxMutexSize,ucQueueType);
  /* Initializing mutex semaphores is actually initializing the control block of the message queue */
  prvInitialiseMutex( pxNewQueue );
  return pxNewQueue;
}
/**************prvInitialiseMutex Source code analysis*********************/
static void prvInitialiseMutex( Queue_t *pxNewQueue ){
  if( pxNewQueue != NULL ){
	/* Reassign the member variables specific to some mutually exclusive semaphores of the created queue structure */
	pxNewQueue->pxMutexHolder = NULL;//Mutex semaphore specific macros
	pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;//Mutex semaphore specific macros
	/* For recursive mutex semaphores, assign the following member variables to 0 */
	pxNewQueue->u.uxRecursiveCallCount = 0;
	traceCREATE_MUTEX( pxNewQueue );
	/* Release mutex semaphore */
	(void) xQueueGenericSend(pxNewQueue,NULL,(TickType_t) 0U,queueSEND_TO_BACK);
  }
  else{
	traceCREATE_MUTEX_FAILED();
  }
}
3.1 releasing mutex semaphores

The function of releasing mutually exclusive semaphores is the same as that of binary semaphores and count semaphores. However, since mutex signals involve priority inheritance, there will be some differences in the processing process.

The semaphore release function xsemaphotoregive() actually calls the xQueueGenericSend() function, and analyzes the source code of the function( Message queue As described in), the visible function will call the prvCopyDataToQueue() function. The priority inheritance of mutually exclusive semaphores is completed in the prvCopyDataToQueue() function. Its source code is as follows:

 /**************prvCopyDataToQueue Source code analysis*********************/                       
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 (configure_mutexes = = 1) / / if it is a mutually exclusive semaphore
	{
	  if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){
		/* Call the following function to handle priority inheritance */
		xReturn = xTaskPriorityDisinherit((void *) pxQueue->pxMutexHolder);
		pxQueue->pxMutexHolder = NULL;//After the mutex semaphore is released, it does not belong to any task
	  }
	  else{
		mtCOVERAGE_TEST_MARKER();
	  } 
	}
	#endif /* configUSE_MUTEXES */
  }
  /*********************************************************/
  /********************Omit other processing codes*********************/
  /*********************************************************/
  pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;
  return xReturn
}

The source code analysis of the priority processing function xtaskprioritydisiinherit() is as follows:

BaseType_t xTaskPriorityDisinherit(TaskHandle_t const pxMutexHolder){
  TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
  BaseType_t xReturn = pdFALSE;
  if( pxMutexHolder != NULL ){
	/* After the task obtains the mutex semaphore, priority inheritance will be involved */
	configASSERT( pxTCB == pxCurrentTCB );
	configASSERT( pxTCB->uxMutexesHeld );
	( pxTCB->uxMutexesHeld )--;//Used to mark the number of mutually exclusive semaphores currently obtained by the task
	/* If the current priority of the task is different from the base priority of the task, priority inheritance exists */
	if(pxTCB->uxPriority != pxTCB->uxBasePriority){
	  /* The current task only gets one mutex semaphore */
	  if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 ){
		/* Reduce the current priority of the task to the base priority */
		if(uxListRemove(&(pxTCB->xStateListItem)) == (UBaseType_t ) 0){
		  taskRESET_READY_PRIORITY( pxTCB->uxPriority );
		}
		else{
		  mtCOVERAGE_TEST_MARKER();
		}
		/* Add the task to the ready list with the new priority */
		traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
		pxTCB->uxPriority = pxTCB->uxBasePriority;
		/* Reset the event list item of the task */
		listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), \
		(TickType_t)configMAX_PRIORITIES - (TickType_t)pxTCB->uxPriority); 
		prvAddTaskToReadyList( pxTCB );//Add the task after recovery priority back to the ready table
		xReturn = pdTRUE;//pdTRUE is returned, indicating that task scheduling is required
	  }
	  else{
		mtCOVERAGE_TEST_MARKER();
	  }
	}
	else{
	  mtCOVERAGE_TEST_MARKER();
	}
  }
  else{
	mtCOVERAGE_TEST_MARKER();
  }
  return xReturn;
}
3.2 obtaining mutually exclusive semaphores

The mutually exclusive semaphore acquisition function is the same as the binary semaphore and count semaphore acquisition functions. They are both xsemaphotoretake() functions, and finally call xQueueGenericReceive(); The process of obtaining mutually exclusive semaphores also needs to deal with the problem of priority inheritance. The source code analysis 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;
      /* Determine whether there are messages in the queue */
	  if(uxMessagesWaiting > (UBaseType_t ) 0){
		/* If there is data, call the following functions to extract data from the queue by copying data */
		pcOriginalReadPosition = pxQueue->u.pcReadFrom;
		prvCopyDataFromQueue( pxQueue, pvBuffer );
		if( xJustPeeking == pdFALSE ){//The data needs to be deleted after reading
		  traceQUEUE_RECEIVE( pxQueue );
          /* Remove message */
		  pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;
    	  #if ( configUSE_MUTEXES == 1 )
		  {
			if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){
			/* If the semaphore is obtained successfully, mark the owner of the mutually exclusive semaphore */
			  pxQueue->pxMutexHolder = (int8_t *) pvTaskIncrementMutexHeldCount(); 
			}
			else{
			  mtCOVERAGE_TEST_MARKER();
			}
		  }
		  #endif /* configUSE_MUTEXES */
		  /* Check whether any task is blocked due to joining the team. If so, remove the blocking state */
 		  if(listLIST_IS_EMPTY(&( pxQueue->xTasksWaitingToSend)) == pdFALSE){
			if(xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToSend)) != pdFALSE){
			  /* If the priority of the unblocking task is higher than the current task, a task switch is required */
			  queueYIELD_IF_USING_PREEMPTION();
			}
			else{
			  mtCOVERAGE_TEST_MARKER();
			}
		  }
		  else{
			mtCOVERAGE_TEST_MARKER();
		  }
		}
		else{//There is no need to delete the data after reading
		  traceQUEUE_PEEK( pxQueue );
		  pxQueue->u.pcReadFrom = pcOriginalReadPosition;
		  /* Check whether there are tasks blocked due to leaving the team. If so, remove the blocking state */
		  if(listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive)) == pdFALSE){
			if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){
			  /* If the priority of the unblocking task is higher than the current task, a task switch is required */
			  queueYIELD_IF_USING_PREEMPTION();
			}
			else{
			  mtCOVERAGE_TEST_MARKER();
			}
		  }
		  else{
			mtCOVERAGE_TEST_MARKER();
		  }
		}
		taskEXIT_CRITICAL();
		return pdPASS;
	  }
	  else{	//If the queue is empty
		if( xTicksToWait == ( TickType_t ) 0 ){
		  /* If the blocking time is 0, errqueue will be returned directly_ EMPTY */
		  taskEXIT_CRITICAL();
		  traceQUEUE_RECEIVE_FAILED( pxQueue );
		  return errQUEUE_EMPTY;
		}
		else if( xEntryTimeSet == pdFALSE ){
		  /* If the blocking time is not 0, the time state structure is initialized */
		  vTaskSetTimeOutState( &xTimeOut );
		  xEntryTimeSet = pdTRUE;
		}
		else{
		  /* Entry time was already set. */
		  mtCOVERAGE_TEST_MARKER();
		}
	  }
	}
	taskEXIT_CRITICAL();
	vTaskSuspendAll();
	prvLockQueue( pxQueue );
	/* Update the time status structure and check whether it times out */
	if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){
	  /* Check whether the queue is empty */
	  if( prvIsQueueEmpty( pxQueue ) != pdFALSE){//If the queue is empty
		traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
		#if ( configUSE_MUTEXES == 1 )
		{
		  if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){
			taskENTER_CRITICAL();
			{
			  /* Priority inheritance for handling mutually exclusive semaphores */
			  vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
			}
			taskEXIT_CRITICAL();
		  }
		  else{
			mtCOVERAGE_TEST_MARKER();
		  }
		}
		#endif
		/* Because the queue is empty, add the task to the xTasksWaitingToReceive list */
		vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
		prvUnlockQueue( pxQueue );
		if( xTaskResumeAll() == pdFALSE )
		{
		  portYIELD_WITHIN_API();
		}
		else
		{
		  mtCOVERAGE_TEST_MARKER();
		}
	  }
	  else	//If the queue is not empty, try to get out of the queue again
	  {
		prvUnlockQueue( pxQueue );
		( void ) xTaskResumeAll();
	  }
	}
	else
	{
	  prvUnlockQueue( pxQueue );
	  ( void ) xTaskResumeAll();
      if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
	  {
		traceQUEUE_RECEIVE_FAILED( pxQueue );
		return errQUEUE_EMPTY;
	  }
	  else
	  {
		mtCOVERAGE_TEST_MARKER();
	  }
	}
  }
}

4. Application examples of mutually exclusive semaphores

This example introduces and simulates how to use mutually exclusive semaphores to avoid priority reversal in Chapter 1

Porting FreeRTOS to the project using STM32CubeMX creates three tasks with high, medium and low priority and one mutually exclusive semaphore

High_Task: for high priority tasks, the mutex semaphore will be acquired. After successful acquisition, the mutex semaphore will be processed accordingly. After processing, the mutex semaphore will be released
Middle_Task: medium priority task, simple application task
Low_Task: for low priority tasks, the mutex semaphore will be acquired. After successful acquisition, the corresponding processing will be carried out. After processing, the mutex semaphore will be released. However, tasks take longer than high priority tasks

4.1 STM32CubeMX setting
  • RCC is set with external HSE, and the clock is set to 72M
  • USART1 is selected as asynchronous communication mode, the baud rate is set to 115200Bits/s, the transmission data length is 8Bit, there is no parity check, and 1 stop bit;
  • Activate FreeRTOS, add tasks, and set parameters such as task name, priority, stack size, function name, etc

  • Dynamically create mutually exclusive semaphores

  • When using FreeRTOS operating system, you must change the Timebase Source of HAL library from SysTick to other timers. After selecting the timer, the system will automatically configure TIM
  • Enter the project name, select the path (do not have Chinese), and select MDK-ARM V5; Check Generated periphera initialization as a pair of ' c/.h’ files per IP ; Click GENERATE CODE to generate the engineering code
4.2 MDK-ARM software programming
  • Add High_Task,Middle_Task,Low_Task function code
/******************HighTask**************************/
void HighTask(void const * argument){
  for(;;){
    vTaskDelay(500);
	printf("High task Pend Semaphore\r\n");
	xSemaphoreTake(MutexSemHandle,portMAX_DELAY);
	printf("High task running!\r\n");
	HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_1);
	xSemaphoreGive(MutexSemHandle);
	vTaskDelay(500);
  }
}
/******************MiddleTask***********************/
void MiddleTask(void const * argument){
  for(;;){
    printf("Middle task running!\r\n");
	HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
	vTaskDelay(1000);
  }
}
/******************LowTask**************************/
void LowTask(void const * argument){
  for(;;){
    xSemaphoreTake(MutexSemHandle,portMAX_DELAY);
	printf("Low task running!\r\n");
	for(int i=0;i<20000000;i++){
	  taskYIELD();			
	}		
	xSemaphoreGive(MutexSemHandle);
	vTaskDelay(1000);
  }
}
4.3 download verification

After the compilation is correct and downloaded to the development board, open the serial port debugging assistant, and the serial port outputs the debugging information as shown in the figure below:

  • Due to high_ The task is delayed for 500ms, so it is middle_ The task starts running first;
  • Then low_ Run the task to obtain the mutually exclusive signal;
  • High_ The task starts running, blocks the request mutex semaphore, and waits for Low_Task release mutex semaphore
  • At this time, due to Low_Task is using mutex semaphores, so low_ The priority of task is temporarily raised to high_ Tasks have the same priority, so Middle_Task cannot interrupt low_ Operation of task. Low_ Release the mutex semaphore after the task is run, High_Task gets the mutex semaphore and runs it

This problem is solved by flipping the priority of mutually exclusive signals

Pay attention to my official account and send the following message to the official account, and I will get the corresponding engineering source code:

FreeRTOS mutex semaphore instance

Topics: FreeRTOS Semaphore Mutex