FreeRTOS源码分析-7 消息队列

目录

1 消息队列的概念和作用

2 应用

2.1功能需求

2.2接口函数API

2.3 功能实现

3 消息队列源码分析

3.1消息队列控制块

3.2消息队列创建

3.3消息队列删除

3.4消息队列在任务中发送

3.5消息队列在中断中发送

3.6消息队列在任务中接收

3.7消息队列在中断中接收 


1 消息队列的概念和作用

消息队列(queue),可以在任务与任务间、中断和任务间传递消息实现任务接收来自其他任务或中断的不固定长度的消息。后面的二值信号量、互斥信号量等都是基于消息队列衍生出来的。

队列是什么?

解决三个问题:

  • 排队无序的问题(先入先出,有序)
  • 不可能插队的问题(在一端插入)
  • 高效的问题(只要入队都能买到票)

FreeRTOS程序中的消息队列

中断和任务不断的发送消息

在固定时间内等待(Timeout,相当于osdelay挂起)消息,没有消息的时候把cpu交给其他任务。

 

 “深度”。在队列创建时需要设定其深度和每个单元的大小。

通常情况下,队列被作为 FIFO(先进先出)使用,即数据由队列尾写入,从队列首读出。当然,由队列首写人也是有可能的。

向队列写入数据是通过字节拷贝把数据存储到队列中,从队列读出数据使得把队列中的数据拷贝删除。

在FreeROTS中,操作消息队列控制块,只要对头有消息,就会取,直到去完为止。

2 应用

2.1功能需求

  • 1、使用消息队列检测串口输入
  • 2、通过串口发送字符串openled6,openled7,openled8,openled9,分别打开板载led6,led7,led8, led9
  • 3、通过串口发送字符串closeled6,closeled7,closeled8,closeled9,分别关闭板载led6,led7,led8, led9

2.2接口函数API

 一般在调度器开启之前创建

可以发送在队头、队尾,一般先入先出,有紧急消息可以队头插队,常用的是Send和SendtoBack

 启动调度器之前是不能调用此函数的,因为是在中断中触发的,在中断中不能阻塞。第三个参数NULL,已经不用了。

第三个参数NULL,已经不用了。 

2.3 功能实现

 CubeMX功能配置

led端口配置、usart1中断配置、创建消息队列

消息队列接收和发送功能开发

串口中断使能

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */

  /* USER CODE END USART1_MspInit 0 */
    /* USART1 clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();
  
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration    
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART1 interrupt Init */
    HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspInit 1 */
  //开启接收中断
  __HAL_UART_ENABLE_IT(uartHandle,UART_IT_RXNE);

  /* USER CODE END USART1_MspInit 1 */
  }
}

串口中断服务函数入队操作 

void USART1_IRQHandler(void)
{
	uint8_t u8Data;
	//接收中断标志位
	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE) == SET){
		//读取接收寄存器
		u8Data = huart1.Instance->DR;
		//进行入队操作
		xQueueSendFromISR(CmdQueueHandle,&u8Data,NULL);
	}

  HAL_UART_IRQHandler(&huart1);
}

从消息队列出队一直等待,当接收到第一个消息循环从消息队列出队,阻塞等待50ms,完成消息接收

uint8_t u8CmdBuff[20]; //全局变量

void Usart_Task(void const * argument)
{
	uint8_t u8Index;

  for(;;)
  {
	  //每次读取消息之前,把索引初始化为0
	  u8Index = 0;
	  //1、一直等待接收消息,第一个消息应该放在消息缓冲区的第一个元素上
		if(xQueueReceive(CmdQueueHandle,&u8CmdBuff[u8Index++],portMAX_DELAY)==pdPASS){
			while(xQueueReceive(CmdQueueHandle,&u8CmdBuff[u8Index++],50)){}
			u8CmdBuff[u8Index] = '\0';//保证一包完成字符串信息
			vParseString(u8CmdBuff);
			//完成解析以后,要清空接收缓冲区
		    memset(u8CmdBuff,0,20);
		
		}
  }
}

消息解析控制功能开发

根据板载LED端口数量循环遍历,openledX字符串进行比较,如果字符串比较成功打开相关led端口

根据板载LED端口数量循环遍历,closeledX字符串进行比较,如果字符串比较成功打开相关led端口

uint8_t *OpenString[LED_NUM] = {

"openled6",
"openled7",
"openled8",
"openled9",

};

uint8_t *CloseString[LED_NUM] = {

"closeled6",
"closeled7",
"closeled8",
"closeled9",

};


void vParseString(uint8_t *buff){
  
    
    uint8_t i;
    for(i=0;i<LED_NUM;i++){
      if(strcmp((char const*)buff,(char const*)OpenString[i]) == 0){
        HAL_GPIO_WritePin(LedPort[i], LedPin[i], GPIO_PIN_RESET);
        printf("Cmd is %s\n",OpenString[i]);
		  return;
      
      }
    
    }
    for(i=0;i<LED_NUM;i++){
      if(strcmp((char const*)buff,(char const*)CloseString[i]) == 0){
      HAL_GPIO_WritePin(LedPort[i], LedPin[i], GPIO_PIN_SET);
      printf("Cmd is %s\n",CloseString[i]);
		  return;
      
      }
	 
    
    }

}

3 消息队列源码分析

3.1消息队列控制块

 

3.2消息队列创建

//queue.h

//队列实现,实际是xQueueGenericCreate实现的
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )

//队列的创建类型
#define queueQUEUE_TYPE_BASE				( ( uint8_t ) 0U )  //基本类型
#define queueQUEUE_TYPE_SET					( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_MUTEX 				( ( uint8_t ) 1U )  //互斥锁
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE	( ( uint8_t ) 2U )  //计数信号量
#define queueQUEUE_TYPE_BINARY_SEMAPHORE	( ( uint8_t ) 3U )  //二值信号量
#define queueQUEUE_TYPE_RECURSIVE_MUTEX		( ( uint8_t ) 4U )  //递归互斥锁

/*
    参数:
        uxQueueLength 队列长度
        uxItemSize    队列项大小
        ucQueueType   队列类型
    返回值:
        QueueHandle_t 队列的句柄 其实就是队列控制块地址
*/
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{
    Queue_t *pxNewQueue;
    size_t xQueueSizeInBytes;
    uint8_t *pucQueueStorage;

    //队列内存空间为空
	if( uxItemSize == ( UBaseType_t ) 0 )
	{
		/*队列字节大小赋值为0 */
		xQueueSizeInBytes = ( size_t ) 0;
	}
	else
	{
		/* 队列字节大小赋值为 长度*每个队列项大小 */
		xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); 
	}

    // 申请内存空间  消息队列控制块大小+消息队列空间大小
	pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );

	if( pxNewQueue != NULL )
	{
		/* 找到消息队列操作空间的首地址 */
		pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );

        //初始化一个新的消息队列
		prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
	}

	return pxNewQueue;
}

/*
    参数:
        uxQueueLength
        uxItemSize
        pucQueueStorage:队列操作空间的首地址
        ucQueueType:队列类型
        pxNewQueue:队列的句柄
*/
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, const uint8_t ucQueueType, Queue_t *pxNewQueue )
{
	( void ) ucQueueType;

	if( uxItemSize == ( UBaseType_t ) 0 )//是否有队列空间
	{
		/* 把队列控制块首地址赋值到队列头指针 ??这是互斥信号使用,后面分析*/
		pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
	}
	else
	{
		/* 把队列空间的首地址赋值给队列头指针 */
		pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
	}

	/* 长度、单元大小 */
	pxNewQueue->uxLength = uxQueueLength;
	pxNewQueue->uxItemSize = uxItemSize;

    //队列重置函数
	( void ) xQueueGenericReset( pxNewQueue, pdTRUE );

}


/*
    参数:
        xQueue 队列句柄
        xNewQueue 操作队列的状态是什么,新建pdTRUE还是已经创建好了
    返回值:

*/
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{
    Queue_t * const pxQueue = ( Queue_t * ) xQueue;

    //进入临界段,这时候操作队列控制块,不允许打断
	taskENTER_CRITICAL();
	{
        /*
            1、头地址赋值
            2、等待处理的消息个数为0
            3、写入指针赋值为队列头指针
            4、读出指针写入最后一个可用消息
            5、赋值队列锁位解锁状态
        */
		pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );
		pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;
		pxQueue->pcWriteTo = pxQueue->pcHead;
		pxQueue->u.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - ( UBaseType_t ) 1U ) * pxQueue->uxItemSize );
		pxQueue->cRxLock = queueUNLOCKED;
		pxQueue->cTxLock = queueUNLOCKED;

        //判断是否位新建队列
		if( xNewQueue == pdFALSE )//不是新任务
		{
			/* 判断发送等待列表里面是否有任务 */
			if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
			{
                // 移除事件列表中的任务,如延时任务
				if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
				{
                    //进行上下文切换
					queueYIELD_IF_USING_PREEMPTION();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else//新建队列,直接初始化 发送和接收 列表项
		{
			/* Ensure the event queues start in the correct state. */
			vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
			vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
		}
	}
	taskEXIT_CRITICAL();

	/* A value is returned for calling semantic consistency with previous
	versions. */
	return pdPASS;
}

3.3消息队列删除

void vQueueDelete( QueueHandle_t xQueue )
{
    Queue_t * const pxQueue = ( Queue_t * ) xQueue;


	#if ( configQUEUE_REGISTRY_SIZE > 0 )
	{
		vQueueUnregisterQueue( pxQueue );
	}
	#endif

	#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) )
	{
		/* 释放消息队列的内存空间 */
		vPortFree( pxQueue );
	}
	#elif( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) )
	{
		/* 释放消息队列的内存空间 */
		if( pxQueue->ucStaticallyAllocated == ( uint8_t ) pdFALSE )
		{
			vPortFree( pxQueue );
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#else
	{
		( void ) pxQueue;
	}
	#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}

3.4消息队列在任务中发送

分支一:

  • 队列未满或覆盖入队,那么看下是否有接收任务,有的话解除一个接收任务,退出临界段。   
  • 这队列已满,不需要等待的话直接退出临界段。如果需要等待,初始化超时结构体。最后退出临界段挂起调度器。

分支二:

  • 锁定队列,判断超时等待是否溢出,溢出解锁跌了恢复调度。
  • 没有溢出,再次判断一下队列是否满,没满解锁队列恢复调度器,如果满了将任务插入等待发送列表,解锁队列,恢复调度。

问:为什么在进入临界段?

因为操作的消息是共享资源,可以被多个任务或中断接收和发送,那么操作的时候是不希望被别的任务打断的。

问:为什么要挂起调度器?

不让内核调度,不让其他任务打断下面程序的处理。

问:为什么要锁定队列?

因为消息队列是可以从任务中发送的,锁定队列是告诉任务不要打断下面程序的查询队列和插入队列,否则整个程序会打乱。



/*
    queueSEND_TO_BACK ?
    由于信号量都是有消息队列实现的,这个时候,操作系统,定义了消息队列的类型
    类型:
        #define	queueSEND_TO_BACK		( ( BaseType_t ) 0 ) 1从队尾加入
        #define	queueSEND_TO_FRONT		( ( BaseType_t ) 1 ) 2从对头加入
        #define queueOVERWRITE			( ( BaseType_t ) 2 ) 3覆盖入队
*/
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

/*
    最终实现消息队列发送的是xQueueGenericSend接口
    参数:
        xQueue  消息队列句柄
        pvItemToQueue 要发送的消息的地址
        xTicksToWait  超时时间
        xCopyPosition 队列操作类型
    返回值:BaseTyp_t

*/
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition )
{
    BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
    TimeOut_t xTimeOut;
    Queue_t * const pxQueue = ( Queue_t * ) xQueue;

	/* 
        使用for循环的目的:为了快速处理消息拷贝,消息处理功能
     */
	for( ;; )
	{
        //进入临界段
		taskENTER_CRITICAL();
		{
			/* 
                1、判断消息队列是否满了
                2、判断是否允许覆盖入队
                任何一个成立,执行入队操作
             */
			if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
			{
                //拷贝数据到队列操作空间内
				xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

				#if ( configUSE_QUEUE_SETS == 1 )
				{
					//宏定义不看
				}
				#else /* configUSE_QUEUE_SETS */
				{
					/* 判断等待接收的列表是否为空. */
					if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
					{
                        //移除等待接收任务的列表,改变等待接收任务的状态为就绪态
						if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
						{
							/* 操作成功,进行上下文切换,让任务赶紧处理 */
							queueYIELD_IF_USING_PREEMPTION();
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
					}
                    //如果在拷贝数据的时候提示需要调度
					else if( xYieldRequired != pdFALSE )
					{
						/* 再次调度,进行上下文切换*/
						queueYIELD_IF_USING_PREEMPTION();
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				#endif /* configUSE_QUEUE_SETS */
                //退出临界段,返回成功
				taskEXIT_CRITICAL();
				return pdPASS;
			}
			else  //如果不允许入队
			{
                //是否需要阻塞
				if( xTicksToWait == ( TickType_t ) 0 )
				{
					/* 不需要阻塞,退出临界段,之后返回队列队满 */
					taskEXIT_CRITICAL();
					traceQUEUE_SEND_FAILED( pxQueue );
					return errQUEUE_FULL;
				}
                //超时结构体是否操作过
				else if( xEntryTimeSet == pdFALSE )
				{
					/* 超时结构体初始化 */
					vTaskSetTimeOutState( &xTimeOut );
					xEntryTimeSet = pdTRUE;
				}
				else
				{
					/* Entry time was already set. */
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
        //退出临界段
		taskEXIT_CRITICAL();

		/* 后面的代码都是允许阻塞处理 */

		/* 
			1、挂起了调度器 ----不让其他任务打断
			2、队列上锁------不让中断打断 (因为之前已经退出临界段了)

		*/

		vTaskSuspendAll();
		prvLockQueue( pxQueue );

		/* 判断阻塞时间是否超时了 */
		if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
		{
			//判断队列是否满
			if( prvIsQueueFull( pxQueue ) != pdFALSE )
			{
				//队满,把当前任务,添加到等待发送的事件列表中,内部还把任务添加到延时列表中去
				traceBLOCKING_ON_QUEUE_SEND( pxQueue );
				vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );

				/* 解锁*/
				prvUnlockQueue( pxQueue );

				/* 恢复调度器 */
				if( xTaskResumeAll() == pdFALSE )
				{
					//进行上下文切换
					portYIELD_WITHIN_API();
				}
			}
			else
			{
				//队列未满 解锁,恢复调度器,重新进行入队操作
				/* Try again. */
				prvUnlockQueue( pxQueue );
				( void ) xTaskResumeAll();
			}
		}//
		//已经超时了,解锁,开始调度器 返回队满
		else
		{
			/* The timeout has expired. */
			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();

			traceQUEUE_SEND_FAILED( pxQueue );
			return errQUEUE_FULL;
		}
	}
}

3.5消息队列在中断中发送

问:上锁的目的是什么?

如果上锁了,在中断中不会处理,上面3.5在任务中发送的功能。如果中断能打断,那么中断也能操作队列、中断也能操作队列,那么优先级会出现混乱。没有上锁,那么可以操作。

/*
	最终调用发送消息接口是xQueueGenericSendFromISR???为什么
	由于信号量都是有消息队列实现的,这个时候,操作系统,定义了消息队列的类型
	类型:
	#define	queueSEND_TO_BACK		( ( BaseType_t ) 0 ) 1、从队尾加入
	#define	queueSEND_TO_FRONT		( ( BaseType_t ) 1 ) 2、从队头加入
	#define queueOVERWRITE			( ( BaseType_t ) 2 ) 3、覆盖入队
*/
#define xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) 
xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
/*
	参数:
		xQueue
		pvItemToQueue
		NULL
		queueSEND_TO_BACK
	返回值:BaseType_t 

*/
BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, const void * const pvItemToQueue, BaseType_t * const pxHigherPriorityTaskWoken, const BaseType_t xCopyPosition )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;


	/*带返回值的关闭中断,需要保存上次关闭中断的状态值,恢复时候,写入 */
	uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
	{
		//队满?覆盖入队?
		if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
		{
			//获取了队列发送锁的状态值
			const int8_t cTxLock = pxQueue->cTxLock;
			//拷贝数据到队列操作空间内
			( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );

			/* 判断队列是否上锁 */
			/*
				#define queueUNLOCKED					( ( int8_t ) -1 ) 解锁状态
				#define queueLOCKED_UNMODIFIED			( ( int8_t ) 0 )  上锁状态初值
			*/
			if( cTxLock == queueUNLOCKED )
			{
				#if ( configUSE_QUEUE_SETS == 1 )
				
				#else /* configUSE_QUEUE_SETS */
				{
					//恢复等待消息任务,中断内没有进行上下文切换,会在开启调度器的时候进行
					if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
					{
						if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
						{
							/* The task waiting has a higher priority so record that a
							context	switch is required. */
							if( pxHigherPriorityTaskWoken != NULL )
							{
								*pxHigherPriorityTaskWoken = pdTRUE;
							}
							else
							{
								mtCOVERAGE_TEST_MARKER();
							}
						}
						else
						{
							mtCOVERAGE_TEST_MARKER();
						}
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				#endif /* configUSE_QUEUE_SETS */
			}
			else //队列已经上锁
			{
				/* 发送锁加一 */
				pxQueue->cTxLock = ( int8_t ) ( cTxLock + 1 );
			}
			//返回成功
			xReturn = pdPASS;
		}
		else
		{	
			//返回队满
			traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
			xReturn = errQUEUE_FULL;
		}
	}
	//开启中断,保存上次状态值
	portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

	return xReturn;
}
//队列上锁
把发送和接受锁都赋值为上锁的初始值
#define prvLockQueue( pxQueue )								\
	taskENTER_CRITICAL();									\
	{														\
		if( ( pxQueue )->cRxLock == queueUNLOCKED )			\
		{													\
			( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED;	\
		}													\
		if( ( pxQueue )->cTxLock == queueUNLOCKED )			\
		{													\
			( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED;	\
		}													\
	}														\
	taskEXIT_CRITICAL()
	
/*
	队列解锁
	参数:消息队列句柄
*/
static void prvUnlockQueue( Queue_t * const pxQueue )
{
	/* THIS FUNCTION MUST BE CALLED WITH THE SCHEDULER SUSPENDED. */

	/* 进入临界段 */
	taskENTER_CRITICAL();
	{
		//获取发送锁的状态值
		int8_t cTxLock = pxQueue->cTxLock;

		/* 遍历解锁  直到解锁为止 */
		while( cTxLock > queueLOCKED_UNMODIFIED )
		{
			/* Data was posted while the queue was locked.  Are any tasks
			blocked waiting for data to become available? */
			#if ( configUSE_QUEUE_SETS == 1 )
		
			#else /* configUSE_QUEUE_SETS */
			{
				/* 解除等待消息任务,进行上下文切换 */
				if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
				{
					if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
					{
						/* The task waiting has a higher priority so record that
						a context switch is required. */
						vTaskMissedYield();
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
				}
				else
				{
					break;
				}
			}
			#endif /* configUSE_QUEUE_SETS */
			//队列发送锁减一
			--cTxLock;
		}
		//最后,解除发送锁
		pxQueue->cTxLock = queueUNLOCKED;
	}
	//退出临界段
	taskEXIT_CRITICAL();

	/* Do the same for the Rx lock. 接收锁也是一样的 */
	taskENTER_CRITICAL();
	{
		int8_t cRxLock = pxQueue->cRxLock;

		while( cRxLock > queueLOCKED_UNMODIFIED )
		{
			if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
			{
				if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
				{
					vTaskMissedYield();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}

				--cRxLock;
			}
			else
			{
				break;
			}
		}

		pxQueue->cRxLock = queueUNLOCKED;
	}
	taskEXIT_CRITICAL();
}

3.6消息队列在任务中接收

 分支一:

是出队删除,还是出队不删除,不删除是留个多个任务读取一个消息的情况使用的。

分支二:

接收消息为空,需要判断是否允许阻塞

/*
	最终调用发送消息接口是xQueueGenericReceive???为什么
	队列出队,有两种模式
		一种是:出队后,删除已经读取到队列项或者消息空间
		另一种是:出队后,不删除,然后恢复出队记录地址,让其他任务或者中断,继续读取使用
	类型:
		pdFALSE		删除
		pdTRUE		不删除

*/
#define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) 
xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdFALSE )

BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait, const BaseType_t xJustPeeking )

				//重点分析这一个,其他流程和发送流程差不多
				//判断是否删除已经接收到的消息空间
 				if( xJustPeeking == pdFALSE )
 				{
 					traceQUEUE_RECEIVE( pxQueue );
						
					//更新消息等待的记录值,让它减一
 					pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;
 				}
 				else
 				{
					//不删除 就重新赋值未读取消息之前的地址,到出队指针
 					pxQueue->u.pcReadFrom = pcOriginalReadPosition;
 
 				}

3.7消息队列在中断中接收 

 源码分析,参考3.5中断中发送最后一部分。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/52673.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

导出文件下载进度条简单实现

前言 今天要跟大家分享的是一个导出数据进度条的简单实现&#xff0c;适用场景用在数据量大、组织数据耗时的情况下的简单实现。 一、设计思路 1、导出数据生成文件上传到OSS&#xff0c; 2、导出数据状态存redis缓存&#xff0c; 3、前端发导出请求后&#xff0c;返回的文件k…

动态sql以及常用的标签

什么是动态sql&#xff1a; 指根据不同的条件生成不同的sql 搭建环境&#xff1a; 建表&#xff1a; create table blog( id varchar(50) not null comment 博客id, title varchar(100) not null comment 博客标题, author varchar(30) not null comment 博客作者, create_ti…

第三章 ref与reactive

ref ref 变为响应式数据shallowRef 浅层响应式数据&#xff08;响应式到 .value为止&#xff09;isRef 判断是否为ref响应式数据triggerRef 强制触发依赖更新customRef 自定义ref函数 <template><div class"App">{{ stu }}<button click"chang…

国际化警告Fall back to translate ‘creator‘ key with ‘zn‘ locale.

发现是自己粗心写错了一个单词 这个需要改成zh messages里面也是zh:zh

忘记安卓图案/密码锁如何解锁?

如何解锁Android手机图案锁&#xff1f;如何删除忘记的密码&#xff1f;Android 手机锁定后如何重置&#xff1f;这是许多智能手机用户在网上提出的几个问题。为了回答这些问题&#xff0c;我们想出了一些简单有效的方法来解锁任何设备而不丢失数据。 忘记手机密码可能会令人恐…

大数据技术之ClickHouse---入门篇---介绍

星光下的赶路人star的个人主页 一棵树长到它想长到的高度之后&#xff0c;它才知道怎样的空气适合它 文章目录 1、Clickhouse入门1.1 什么是Clickhouse1.1.1 Clickhouse的特点1.1.1.1 列示储存1.1.1.2 DBMS的功能1.1.1.3 多样化引擎1.1.1.4 高吞吐写入能力1.1.1.5 数据分区与线…

机器学习:自动编码器Auto-encoder

Self-supervised Learning Framework 不用标注数据就能学习的任务&#xff0c;比如Bert之类的。但最早的方法是Auto-encoder。 Outline Auto-encoder encoder输出的向量&#xff0c;被decoder还原的图片&#xff0c;让输出的图片与输入的图片越接近越好。 将原始的高维向量变…

ChatGPT+MidJourney 3分钟生成你的动画故事

chatgpt是真的火了&#xff0c;chatgpt产生了一个划时代的意义——自chatgpt起&#xff0c;AI是真的要落地了。 chatgpt能做的事情太多了&#xff0c;多到最初开发模型的程序员自己&#xff0c;也没法说得清楚chatgpt都能做啥&#xff0c;似乎只要你能想得到&#xff0c;它都有…

自动callback

using UnityEngine;public class AsyncCallbackScript : MonoBehaviour {public delegate void fun(string msg);void Start(){fun test AAA;//test.BeginInvoke("天王盖地虎", asyncCallback > BBB(), null);test.BeginInvoke("天王盖地虎 宝塔镇河妖"…

测试用例实战

测试用例实战 三角形判断 三角形测试用例设计 测试用例编写 先做正向数据&#xff0c;再做反向数据。 只要有一条边长为0&#xff0c;那就是不符合要求&#xff0c;不需要再进行判断&#xff0c;重复。 四边形 四边形测试用例

Linux6.2 ansible 自动化运维工具(机器管理工具)

文章目录 计算机系统5G云计算第一章 LINUX ansible 自动化运维工具&#xff08;机器管理工具&#xff09;一、概述二、ansible 环境安装部署三、ansible 命令行模块1.command 模块2.shell 模块3.cron 模块4.user 模块5.group 模块6.copy 模块7.file 模块8.hostname 模块9.ping …

day49-Todo List(待办事项列表)

50 天学习 50 个项目 - HTMLCSS and JavaScript day49-Todo List&#xff08;待办事项列表&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" co…

word xls有用小技巧

不少office、代码编辑等软件&#xff0c;很简单高效小技巧。Word xlsx 某一行或列不动&#xff1a; 视图》冻结窗格》冻结首行 eclispe 全局搜索 CtrlH 制定变量、名称搜索 鼠标左键点中CtrlAltG

el-table 表格头部合并

<el-table v-loading"listLoading" :key"tableKey" :data"list" stripe border fit highlight-current-rowstyle"width: 100%;" size"mini"><el-table-column label"第一行" align"center">…

使用DataX实现mysql与hive数据互相导入导出

一、概论 1.1 什么是DataX DataX 是阿里巴巴开源的一个异构数据源离线同步工具&#xff0c;致力于实现包括关系型数据库(MySQL、Oracle 等)、HDFS、Hive、ODPS、HBase、FTP 等各种异构数据源之间稳定高效的数据同步功能。 1.2 DataX 的设计 为了解决异构数据源同步问题&#xf…

HTML5前端开发工程师的岗位职责说明(合集)

HTML5前端开发工程师的岗位职责说明1 职责 1、根据产品设计文档和视觉文件&#xff0c;利用HTML5相关技术开发移动平台的web前端页面; 2、基于HTML5.0标准进行页面制作&#xff0c;编写可复用的用户界面组件; 3、持续的优化前端体验和页面响应速度&#xff0c;并保证兼容性和…

C# NDArray System.IO.FileLoadException报错原因分析

C# NDArray System.IO.FileLoadException 报错原因分析&#xff1a; 1.NuGet程序包版本有冲突 2.统一项目版本 1.打开解决方案NuGet程序包设置 2.查看是否有版本冲突 3.统一版本冲突

【数据结构与算法】基数排序

基数排序 基数排序&#xff08;Radix Sort&#xff09;属于“分配式排序”&#xff0c;又称“桶子法”或 bin sort&#xff0c;顾名思义&#xff0c;它是通过键值的各个位的值&#xff0c;将要排序的元素分配至某些“桶”中&#xff0c;达到排序的作用。基数排序法是属于稳定性…

【UE5】快速认识入门

目录 &#x1f31f;1. 快速安装&#x1f31f;2. 简单快捷键操作&#x1f31f;3. 切换默认的打开场景&#x1f31f;4. 虚幻引擎术语 &#x1f31f;1. 快速安装 进入Unreal Engine 5官网进行下载即可&#xff1a;UE5 &#x1f4dd;官方帮助文档 打开后在启动器里创建5.2.1引擎…

Java并发系列之一:JVM线程模型

什么是线程模型&#xff1a; Java字节码运行在JVM中&#xff0c;JVM运行在各个操作系统上。所以当JVM想要进行线程创建回收这种操作时&#xff0c;势必需要调用操作系统的相关接口。也就是说&#xff0c;JVM线程与操作系统线程之间存在着某种映射关系&#xff0c;这两种不同维…