FreeRTOS消息队列queue.c文件详解

消息队列的作用

消息队列主要用来传递消息,可以在任务与任务之间、中断与任务之间传递消息。

  1. 传递消息是通过复制的形式,发送方发送时需要不断复制,接收方接收时也需要不断复制。虽然会有内存资源的浪费,但是可以保证安全。
    在这里插入图片描述
    假设:如果发送方和接收方直接操作一块内存空间,多个发送方操作写,接收方操作读,会导致时序混乱,是不安全的。
    在这里插入图片描述
  2. 简述传递消息的过程
    初始状态:队列在初始的状态下,头指针和尾指针都指向一个位置,是空队列。
    在这里插入图片描述
    入队:发送方往队列里写数据,写在头指针指向的位置,尾指针自动向后偏移一格,尾指针永远指向可以存内容的格子。
    在这里插入图片描述
    出队:接收方从队列中读数据,头指针往后移。形成一个循环队列。
    在这里插入图片描述

消息队列定义

在这里插入图片描述

  1. 消息队列的结构体成员除了上图所示,还有一下成员:
    ① pcReadFrom:指向出队消息空间的最后一个,读取消息时候是从pcReadFrom指向的空间读取消息内容。
    ② uxRecursiveCallCount:当结构体用作互斥量时,用于计数,记录递归互斥量被“调用”的次数。
    ③ xTasksWaitingToSend :发送方消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序。(由于队列已满,想要发送消息的任务无法发送消息,所以需要保存阻塞在此队列的任务。)
    ④ xTasksWaitingToReceive:接收方消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序。(由于队列是空的,想要获取消息的任务无法获取到消息。)
    ⑤ cRxLock:列表上锁后,存储出队的数量。
    ⑥ cTxLock:列表上锁后,存储入队的数量。
  2. 消息队列的定义代码:
typedef struct QueueDefinition
{
	int8_t *pcHead;			/*< pcHead指向队列消息存储区域的起始位置 */
	int8_t *pcTail;			/*< pcTail指向消息队列信息存储区的结束位置 */
	int8_t *pcWriteTo;		/*< pcWriteTo指向队列存储下一个可用消息空间 */

	union	
	{
		int8_t *pcReadFrom;		/*< 当结构体用作队列时,pcReadFrom指向出队消息空间的最后一个。*/
		UBaseType_t uxRecursiveCallCount;	/*< 当结构体用作互斥量时,uxRecursiveCallCount用于计数,记录递归互斥量被“调用”的次数。*/
	} u;

	List_t xTasksWaitingToSend;		/*< xTasksWaitingToSend是一个发送消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序,由于队列已满,想要发送消息的任务无法发送消息。 */
	List_t xTasksWaitingToReceive;	/*< xTasksWaitingToReceive是一个获取消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序,由于队列是空的,想要获取消息的任务无法获取到消息。*/

	volatile UBaseType_t uxMessagesWaiting;/*< uxMessagesWaiting用于记录当前消息队列的消息个数,如果消息队列被用于信号量的时候,这个值就表示有效信号量个数。*/
	UBaseType_t uxLength;			/*< uxLength表示队列的长度,也就是能存放多少消息。*/
	UBaseType_t uxItemSize;			/*< uxItemSize表示单个消息的大小。*/

	volatile int8_t cRxLock;		/*< 队列上锁后,储存从队列收到的列表项数目,也就是出队的数量,如果队列没有上锁,设置为queueUNLOCKED。*/
	volatile int8_t cTxLock;		/*< 队列上锁后,储存发送到队列的列表项数目,也就是入队的数量,如果队列没有上锁,设置为queueUNLOCKED。 */

	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
		uint8_t ucStaticallyAllocated;	/*< 如果队列使用的内存是静态分配的,则设置为pdTRUE以确保不会尝试释放内存 */
	#endif

	#if ( configUSE_QUEUE_SETS == 1 )
		struct QueueDefinition *pxQueueSetContainer;
	#endif

	#if ( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t uxQueueNumber;
		uint8_t ucQueueType;
	#endif
} xQUEUE;

消息队列控制块的初始化及创建

在这里插入图片描述

图例的简单解释:

  1. 这是消息队列整个的框架图,左侧和右侧是上下连接起来看的。
  2. 首先首指针和指向队列空间首地址,pcWriteTo指向写入的位置,当发送方写入一个数据的时候会从这个位置开始。
  3. pcTail和左侧的xMutexHolder,pcReadFrom和左侧的xTasksWaitingToReceive是共用体,内存数据一样,名称不同。可以用左边解释也可以用右边来解释。左侧是信号量处理时用的,这里使用右侧。pcTail当指向队列空间末尾地址,pcReadFrom指向读取的位置。
  4. 发送列表,假设队列空间已满,再发送数据需要在发送列表中等待。接收列表,假设队列是空的,接收数据需要在接收列表里等待。
  5. uxMessagesWaiting消息等待计数,默认为0。在使用信号量模式时,没有队列空间。就是使用的uxMessagesWaiting来计数。在使用队列模式时,uxMessagesWaiting用来计数队列空间里的消息,也就是目前队列空间里现存的消息数。通过它,很容易判断队列空间里有没有成员。
  6. 队列空间部分是发送消息存放的空间。发送消息时,需要把发送的内容复制到队列空间,往外读消息时,需要从队列空间中读取消息。队列空间的长度N由创建时uxLength决定。
  7. 每个队列空间的大小由uxItemSize决定,uxItemSize以字节为单位。假设uxItemSize=5,发送时发送3个字节,往外读消息时仍然是5个字节。
  8. cRxLock接收锁,默认状态为1,不上锁。
  9. cTxLock发送锁,默认状态为1,不上锁。
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
  • 如消息队列控制模块图所示,在队列空间前的部分是消息队列的自有属性。创建消息队列时,FreeRTOS会先给消息队列分配一块内存空间。内存空间的大小=消息队列控制块大小(自有属性的大小)+ 队列空间大小(单个消息空间大小uxItemSize*消息队列长度uxQueueLength)。接着,初始化消息队列,此时消息队列为空。每个消息队列都与消息空间在同一段连续的内存空间中,在创建成功的时候,这些内存就被占用了,只有删除了消息队列的时候,这段内存才会被释放掉。
  • 创建队列时传入的第一个参数是uxQueueLength,uxLength=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 ) /* 递归互斥信号量 */

下面是消息队列的创建完整代码,首先pvPortMalloc分配内存,然后调用prvInitialiseNewQueue()进行队列的初始化。返回值:pxNewQueue消息队列控制块,消息队列在内存中的地址。

#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{
    Queue_t *pxNewQueue; /* 消息队列控制块,一个结构体指针*/
    size_t xQueueSizeInBytes; /* 需要分配的内存大小,Bytes为单位 */
    uint8_t *pucQueueStorage; /* 实际存放消息的地址,即消息队列控制块的后面 */

    configASSERT( uxQueueLength > ( UBaseType_t ) 0 ); /*判断要创建的消息队列的长度是否大于0*/
    if( uxItemSize == ( UBaseType_t ) 0 )
    {   /* 若消息队列单个项的大小为0,则不会分配队列存储区域 */
        xQueueSizeInBytes = ( size_t ) 0;
    }
    else
    {   /* 计算需要分配的内存大小:队列的长度*单个消息的大小 */
        xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
    }

    /* 调用pvPortMalloc分配内存:其大小为消息队列控制块的大小+实际的消息占用的大小 */
    pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
    /* 若成功分配了内存,则pxNewQueue这个结构体指针(消息队列控制块)不为NULL */
    if( pxNewQueue != NULL )
    {    /* 跳过消息队列控制块以获取实际的队列存储区域的指针 */ 
        pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );
        #if( configSUPPORT_STATIC_ALLOCATION == 1 )
        {   /* 队列可以静态创建,也可以动态创建,因此请注意,此任务是动态创建的,以防以后删除 */
            pxNewQueue->ucStaticallyAllocated = pdFALSE;
        }
        #endif /* configSUPPORT_STATIC_ALLOCATION */

        /*初始化一个队列*/
        prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
    }

    return pxNewQueue;
}

上述代码中的初始化队列成员具体由prvInitialiseNewQueue完成,需要参数:初始化队列的长度,消息大小,pucQueueStorage实际存放消息的地址,ucQueueType队列的类型,pxNewQueue消息队列控制块。
初始化队列成员,然后调用xQueueGenericReset()函数进行队列复位(初始化)。

static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, const uint8_t ucQueueType, Queue_t *pxNewQueue )
{
    ( void ) ucQueueType;
    /* 判断单个消息的大小是否为0*/
    if( uxItemSize == ( UBaseType_t ) 0 )
    {
        /* 没有为队列存储区域分配RAM,但是pcHead不能设置为NULL,因为NULL被用作一个键来表示队列被用作互斥锁。
        因此,只需将pcHead设置为消息队列控制块的地址 */
        pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
    }
    else
    {   /* 将pcHead设置为实际存放消息的地址 */
        pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
    }

    /* 按照定义队列类型的位置初始化队列成员 */
    pxNewQueue->uxLength = uxQueueLength;
    pxNewQueue->uxItemSize = uxItemSize;
    ( void ) xQueueGenericReset( pxNewQueue, pdTRUE ); /*队列复位(初始化)*/

    #if ( configUSE_TRACE_FACILITY == 1 )
    {
        pxNewQueue->ucQueueType = ucQueueType;
    }
    #endif /* configUSE_TRACE_FACILITY */

    #if( configUSE_QUEUE_SETS == 1 )
    {
        pxNewQueue->pxQueueSetContainer = NULL;
    }
    #endif /* configUSE_QUEUE_SETS */

    traceQUEUE_CREATE( pxNewQueue );
}

上述初始化队列成员的函数中xQueueGenericReset()函数主要用来重置队列,将队列中的队列项都清空,将队列中的一些指针也都恢复成最初状态。

  1. 队列的尾地址指向队列的最后一项的末尾地址,pxQueue->pcHead已经指向队列的首地址了,那么在该基础上再偏移所有队列项内存占用内存空间和,就指向了该队列所申请空间的最后一个字节。
  2. 队列的消息个数初始化为0,初始化pcWriteTo的指向位置,初始时和 pcHead 指向同一地方。初始化pcReadFrom 指向队列中最后一项的首地址。
  3. 接收消息(出队)不锁定,发送消息(入队)不锁定,是否是新创建的队列,判断因为队列已满而不能入队导致的任务阻塞列表是否为空,任务阻塞列表不为空,则将任务从事件列表中移除,并判断是否需要重新进行任务调度。如果队列为新创建的队列,则初始化任务发送等待列表。
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{
Queue_t * const pxQueue = ( Queue_t * ) xQueue; /* 将内存地址转为消息队列控制块结构体类型,并赋值给局部变量pxQueue */

    configASSERT( pxQueue );
    taskENTER_CRITICAL(); /* 进入临界区 */
    {
        /* 队列尾地址 = 头地址 + 队列长度*单个消息的大小 */
        pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize );
        /* 当前消息队列的消息个数初始化为0 */
        pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;
        /* 可写入消息的位置,初始为pcHead这个位置 */
        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 /* 是新的队列,则构建消息队列结构 */
        {
            vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
            vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
        }
    }
    taskEXIT_CRITICAL(); /* 退出临界区 */
    return pdPASS;
}

消息队列发送与接收

向队列发送消息

  1. 任务级入队函数
/**********发送消息到队尾********************************************************************************/
BaseType_t xQueueSend(QueueHandle_t xQueue,//队列句柄,指明向哪个队列发送数据
					  const void *pvItemToQueue,//指向要发送的消息
					  TickType_t xTicksToWait);//队列满时,任务进入阻塞态等待队列空闲的最大时间(阻塞时间)
/**********发送消息到队尾********************************************************************************/
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,//队列句柄,指明向哪个队列发送数据
					  const void *pvItemToQueue,//指向要发送的消息
					  TickType_t xTicksToWait);//阻塞时间
/**********发送消息到队头********************************************************************************/
BaseType_t xQueueSendToFront(QueueHandle_t xQueue,//队列句柄,指明向哪个队列发送数据
					  const void *pvItemToQueue,//指向要发送的消息
					  TickType_t xTicksToWait);//阻塞时间
/**********发送消息到队列(带覆写功能,即队列满了以后会覆写掉旧的数据)*************************************/
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,//队列句柄,指明向哪个队列发送数据
					  const void *pvItemToQueue);//指向要发送的消息
/**********任务级通用入队函数***************************************************************************/
BaseType_t xQueueGenericSend(QueueHandle_t xQueue, //队列句柄,指明向哪个队列发送数据
							 const void * const pvItemToQueue, //指向要发送的消息
							 TickType_t xTicksToWait,//阻塞时间
							 const BaseType_t xCopyPosition);//入队方式(后向/前向/覆写)
/******************************************************************************************************/
返回值:发送消息成功,返回pdPASS;队列满消息发送失败,返回errQUEUE_FULL

任务级入队函数最终都是调用xQueueGenericSend()函数

BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition )

参数1:消息队列控制块,参数2:要发送的消息数据,参数3:发送允许的阻塞时间,参数4:发送到消息队列的位置。其中参数4的位置可选为队尾、队头、覆盖写入3种:

#define queueSEND_TO_BACK       ( ( BaseType_t ) 0 )
#define queueSEND_TO_FRONT      ( ( BaseType_t ) 1 )
#define queueOVERWRITE          ( ( BaseType_t ) 2 )

xQueueGenericSend()函数源码:

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(); //进入了临界段
		{
			/* 判断消息队列是否满了以及是否允许覆盖入队,任一条件成立都执行入队操作	*/
			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;			//返回pdPASS,标记入队成功
			}
			else{
				/* 队列满了不允许入队时,先判断是否需要阻塞 */
				if( xTicksToWait == ( TickType_t ) 0 ){
					/* 为0表示,表示没有阻塞时间 */
					taskEXIT_CRITICAL();//退出临界段
					traceQUEUE_SEND_FAILED( pxQueue );
					return errQUEUE_FULL;//返回队列已满
				}
				else if( xEntryTimeSet == pdFALSE ){
					/* 若有阻塞时间,就初始化时间结构体*/
					vTaskSetTimeOutState( &xTimeOut );
					xEntryTimeSet = pdTRUE;
				}
				else{
					/* 时间结构体已经初始化过了 */
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}		
		taskEXIT_CRITICAL();//退出临界段		
		/* 执行到这里说明当前队列已满,并且设置了不为0的阻塞时间 */
		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{
				/* 若此时队列未满,但未超时,则重新进行入队操作 */
				prvUnlockQueue( pxQueue );
				( void ) xTaskResumeAll();
			}
		}
		else{
			/* 若已超时,则解锁队列,恢复任务调度器 */
			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();
			traceQUEUE_SEND_FAILED( pxQueue );
			return errQUEUE_FULL;//返回队列已满
		}
	}
}

  1. 中断级入队函数
/**********发送消息到队尾*****************************************************************************/
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,//队列句柄,指明向哪个队列发送数据 
							 const void * pvItemToQueue,//指向要发送的消息 
							 BaseType_t * pxHigherPriorityTaskWoken);//标记退出此函数后是否进行任务切换
/**********发送消息到队尾****************************************************************************/
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue, 
							 	   const void * pvItemToQueue, 
							 	   BaseType_t * pxHigherPriorityTaskWoken);
/**********发送消息到队头****************************************************************************/
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue, 
							 	    const void * pvItemToQueue, 
							 		BaseType_t * pxHigherPriorityTaskWoken);
/**********发送消息到队列(带覆写功能,即队列满了以后会覆写掉旧的数据)********************************/
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue, 
							 	  const void * pvItemToQueue, 
							 	  BaseType_t * pxHigherPriorityTaskWoken);
/**********中断级通用入队函数***********************************************************************/
BaseType_t xQueueGenericSendFromISR(QueueHandle_t xQueue, 
									const void * pvItemToQueue, 
									BaseType_t * pxHigherPriorityTaskWoken, 
									BaseType_t xCopyPosition);//入队方式(后向/前向/覆写)
/*************************************************************************************************/
返回值:发送消息成功,返回pdPASS;队列满消息发送失败,返回errQUEUE_FULL

中断级入队函数最终都是调用xQueueGenericSendFromISR()函数,下面来分析该函数源码

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 );
			/* 判断队列是否上锁 */
			if( cTxLock == queueUNLOCKED ){	//若队列未上锁
				#if ( configUSE_QUEUE_SETS == 1 )
				{/*......省略掉与队列集相关代码......*/}
				#else /* configUSE_QUEUE_SETS */
				{
					/* 判断等待接收的列表是否为空. */
					if(listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive) == pdFALSE){
						/* 若不为空,表示有任务由于请求消息而阻塞,则改变阻塞态为就绪态. */
						if(xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive))!= pdFALSE){
							/* 若上一步变成就绪态的任务优先级比当前任务高,则标记为pdTRUE,表示要进行任务切换 */
							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;//返回pdPASS,表示入队完成
		}
		else{	//若队列满		
			traceQUEUE_SEND_FROM_ISR_FAILED( pxQueue );
			xReturn = errQUEUE_FULL;//返回errQUEUE_FULL,表示队列满
		}
	}
	/* 开启中断,保存上次状态值 */
	portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
	return xReturn;
}

从队列读取消息

  1. 任务级出队函数
/**********从队列中读取队列项,读取完后删除队列项**************************************************/
BaseType_t xQueueReceive(QueueHandle_t xQueue,//队列句柄 
						 void * const pvBuffer,//保存数据的缓冲区
						 TickType_t xTicksToWait);//阻塞时间,表示队列空时进入阻塞态等待数据的最大时间
/**********从队列中读取队列项,读取完后不删除队列项************************************************/
BaseType_t xQueuePeek(QueueHandle_t xQueue, 
					  void * const pvBuffer, 
					  TickType_t xTicksToWait);
/**********从队列中读取队列项通用函数************************************************************/
BaseType_t xQueueGenericReceive(QueueHandle_t xQueue, 
								void * const pvBuffer, 
								TickType_t xTicksToWait, 
								const BaseType_t xJustPeeking);//标记读取成功后是否需要删除队列项
/**********************************************************************************************/
返回值:读取数据成功,返回pdTRUE;读取失败,返回pdFALSE

任务级出队函数最终都是调用xQueueGenericReceive()函数
xQueue:消息队列控制块
pvBuffer:要接收的消息数据
xTicksToWait:接收允许的阻塞时间
xJustPeeking:是否是Peek(只获取不删除)模式

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;

    configASSERT( pxQueue );
    configASSERT( !( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
    #if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
    {
        configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
    }
    #endif

    /* 这个函数放松了编码标准,允许在函数本身中使用返回语句。这样做是为了提高执行时间效率 */
    for( ;; )
    {
        taskENTER_CRITICAL();
        {
            const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
            /* 现在队列中是否有数据?要运行调用任务,必须是希望访问队列的优先级最高的任务 */
            if( uxMessagesWaiting > ( UBaseType_t ) 0 ) /*消息队列中有数据*/
            {
                pcOriginalReadPosition = pxQueue->u.pcReadFrom;/* 记住读取位置,以防队列被读取 */
                prvCopyDataFromQueue( pxQueue, pvBuffer );/* 从队列中复制数据 */
                if( xJustPeeking == pdFALSE ) /*不是Peeking模式读取消息*/
                {
                    traceQUEUE_RECEIVE( pxQueue );
                    pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;/* 实际上删除数据,而不仅仅是读取 */
                    #if ( configUSE_MUTEXES == 1 )
                    {
                        if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
                        {   /* 如果有必要,记录实现优先级继承所需的信息 */
                            pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount();
                        }
                    }
                    #endif /* configUSE_MUTEXES */
                    if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
                    {
                        if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                        { queueYIELD_IF_USING_PREEMPTION(); }
                    }
                }
                else  /*是Peeking模式读取消息(只读取,不删除)*/
                {
                    traceQUEUE_PEEK( pxQueue ); /*执行PEEK*/
                    pxQueue->u.pcReadFrom = pcOriginalReadPosition;/* 数据没有被移除,所以重置读指针 */
                    /* 数据被留在队列中,因此请查看是否有其他任务在等待该数据 */
                    if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
                    {
                        if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                        {  /* 等待的任务具有比该任务更高的优先级 */
                            queueYIELD_IF_USING_PREEMPTION(); /*进行任务切换*/
                        }
                    }
                }
                taskEXIT_CRITICAL();
                return pdPASS;
            }
            else /*消息队列中没有数据*/
            {
                if( xTicksToWait == ( TickType_t ) 0 )
                {   /* 队列是空的,没有指定块时间(或者块时间已经过期),所以现在离开 */
                    taskEXIT_CRITICAL();
                    traceQUEUE_RECEIVE_FAILED( pxQueue ); 
                    return errQUEUE_EMPTY;
                }
                else if( xEntryTimeSet == pdFALSE )
                {   /* 对列是空的,并且指定了块时间,因此配置超时结构 */
                    vTaskSetTimeOutState( &xTimeOut );
                    xEntryTimeSet = pdTRUE;
                }
            }
        }
        taskEXIT_CRITICAL();

        /* 临界区已经退出,中断和其他任务可以向队列发送或从队列接收消息 */
        vTaskSuspendAll();
        prvLockQueue( pxQueue );
        /* 更新超时状态,查看它是否已经过期 */
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )//阻塞等待未超时
        {
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )//消息队列为空
            {
                #if ( configUSE_MUTEXES == 1 )
                {
                    if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
                    {
                        taskENTER_CRITICAL();
                        { vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder ); }
                        taskEXIT_CRITICAL();
                    }
                }
                #endif
                vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );/*添加任务到阻塞列表*/
                prvUnlockQueue( pxQueue );
                if( xTaskResumeAll() == pdFALSE )
                { portYIELD_WITHIN_API(); }
            }
            else //消息队列为空
            {   /* 再试一次 */
                prvUnlockQueue( pxQueue );
                ( void ) xTaskResumeAll();
            }
        }
        else//阻塞等待已超时
        {
            prvUnlockQueue( pxQueue );
            ( void ) xTaskResumeAll();
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )//消息队列为空
            {
                traceQUEUE_RECEIVE_FAILED( pxQueue );
                return errQUEUE_EMPTY;
            }
        }
    }
}
  1. 中断级出队函数
/**********从队列中读取队列项,读取完后删除队列项*******************************************/
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,//队列句柄 
						 	    void * const pvBuffer,//保存数据的缓冲区
						 		BaseType_t * pxTaskWoken);//标记退出函数后是否进行任务切换
/**********从队列中读取队列项,读取后不删除队列项*******************************************/
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,//队列句柄 
						 	 void * const pvBuffer,//保存数据的缓冲区);
返回值:读取数据成功,返回pdTRUE;读取失败,返回pdFALSE

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

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

相关文章

为什么descriptor和data分离可以内存高效率

以下为例&#xff0c;需要有前面的6个bytes开始&#xff0c;用来处理数据&#xff0c;一旦这6个bytes有了即可以处理了。 Descriptor和data是同样的&#xff0c;只有descriptor有了&#xff0c;即可以开始处理data了。所以data不需要停留更长时间。

函数递归练习

目录 1.分析下面选择题 2.实现求第n个斐波那契数 3.编写一个函数实现n的k次方&#xff0c;使用递归实现。 4.写一个递归函数DigitSum(n)&#xff0c;输入一个非负整数&#xff0c;返回组成它的数字之和 5.递归方式实现打印一个整数的每一位 6.实现求n的阶乘 1.分析下面选择…

vs2022中添加头文件和声明

总结帖 数组存储 matlab中3维数组–>C中1维数组 数组转置函数 #include <stdio.h>// 转置二维数组 void transpose(int *src, int *dest, int rows, int cols) {for (int i 0; i < rows; i) {for (int j 0; j < cols; j) {dest[j * rows i] src[i * col…

解析C++ 网络输入输出缓冲区Buffer类的设计与实现(muduo库)

网络输入输出缓冲区&#xff08;Buffer&#xff09;是为了优化网络通信性能而设计的。通过将数据存储在缓冲区中&#xff0c;可以减少对网络的频繁访问&#xff0c;提高数据传输效率。缓冲区还可以帮助处理数据流中的突发性和短时延&#xff0c;使得数据的发送和接收更加稳定和…

最新版Ceph( Reef版本)块存储简单对接k8s(上集)

当前ceph 你的ceph集群上执行 1.创建名为k8s-rbd 的存储池 ceph osd pool create k8s-rbd 64 642.初始化 rbd pool init k8s-rbd3 创建k8s访问块设备的认证用户 ceph auth get-or-create client.kubernetes mon profile rbd osd profile rbd poolk8s-rbd部署 ceph-rbd-csi c…

C++map容器关联式容器

Cmap 1. 关联式容器 vector、list、deque、forward_list(C11)等STL容器&#xff0c;其底层为线性序列的数据结构&#xff0c;里面存储的是元素本身&#xff0c;这样的容器被统称为序列式容器。而map、set是一种关联式容器&#xff0c;关联式容器也是用来存储数据的&#xff0…

专“蜀”盛会!CGT Asia 2024 第六届亚洲细胞与基因治疗创新峰会(成都站)7月火热相邀

在细胞与基因治疗领域&#xff0c;我们正站在一个科技革命的风口上。中国的CGT市场预计将持续快速增长。根据相关分析&#xff0c;预计到2025年整体市场规模将达到25.9亿美元&#xff0c;显示出276%的复合年增长率。这一增长趋势预计将持续到2030年&#xff0c;细胞与基因治疗领…

【利用数组处理批量数据-谭浩强配套】(适合专升本、考研)

无偿分享学习资料&#xff0c;需要的小伙伴评论区或私信dd。。。 无偿分享学习资料&#xff0c;需要的小伙伴评论区或私信dd。。。 无偿分享学习资料&#xff0c;需要的小伙伴评论区或私信dd。。。 完整资料如下&#xff1a;纯干货、纯干货、纯干货&#xff01;&#xff01;…

电子邮箱是什么?付费电子邮箱和免费电子邮箱有什么区别?

注册电子邮箱前&#xff0c;有付费电子邮箱和免费电子邮箱两类选择。付费的电子邮箱和免费的电子邮箱有什么区别呢&#xff1f;区别主要在于存储空间、功能丰富度和售后服务等方面&#xff0c;本文将为您详细介绍。 一、电子邮箱是什么&#xff1f; 电子邮箱就是线上的邮局&a…

等保2.0|定级、备案、整改、测评流程

从个人数据泄露&#xff0c;到企业遭到黑客攻击&#xff0c;网络安全风险已经越发严重。随着互联网的不断发展&#xff0c;数字化经济的普及&#xff0c;信息安全等级保护既是行业标准&#xff0c;又是国家要求。如果企业不做等保&#xff0c;轻则罚款、重则停业。 我国等级保…

练习题(2024/5/15)

1有多少小于当前数字的数字 给你一个数组 nums&#xff0c;对于其中每个元素 nums[i]&#xff0c;请你统计数组中比它小的所有数字的数目。 换而言之&#xff0c;对于每个 nums[i] 你必须计算出有效的 j 的数量&#xff0c;其中 j 满足 j ! i 且 nums[j] < nums[i] 。 以…

【ARMv8/v9 系统寄存器 5 -- ARMv8 Cache 控制寄存器 SCTRL_EL1 使用详细介绍】

关于ARM Cache 详细学习推荐专栏&#xff1a; 【ARM Cache 专栏】 【ARM ACE Bus 与 Cache 专栏】 文章目录 ARMv8/v9 Cache 设置寄存器ARMv8 指令 Cache 使能函数测试代码 ARMv8/v9 Cache 设置寄存器 关于寄存器SCTRL_EL1 的详细介绍见文章&#xff1a;【ARMv8/v9 异常模型入…

Nacos+GateWay 搭建微服务架构

文章目录 1.当前项目架构分析1.请求多个模块的方式1.请求renren-fast模块开发环境生产环境 2.请求sunliving-commodity模块1.使用环境变量资源路径的方式2.开发环境 dev.env.js3.生产环境 prod.env.js 3.文件上传请求 sunliving-service模块1.请求后端接口&#xff08;开发环境…

Leetcode - 周赛397

目录 一&#xff0c;3146. 两个字符串的排列差 二&#xff0c;3147. 从魔法师身上吸取的最大能量 三&#xff0c;3148. 矩阵中的最大得分 四&#xff0c;3149. 找出分数最低的排列 一&#xff0c;3146. 两个字符串的排列差 本题就是求同一个字符在两个字符串中的下标之差的…

一物一码数字化营销进军调味品行业,五丰黎红“星厨俱乐部”火啦!

近日&#xff0c;由五丰黎红联合纳宝科技精心打造的小程序“星厨俱乐部”火啦&#xff01;一经上线就吸引了大量用户注册和参与&#xff0c;可以说取得了非常成功的市场反馈&#xff0c;那究竟是一个什么样的小程序&#xff0c;竟然有这么大的吸引力呢&#xff1f; 介绍小程序之…

C++ requires关键字简介

requires 是 C20 中引入的一个新关键字&#xff0c;用于在函数模板或类模板中声明所需的一组语义要求&#xff0c;它可以用来限制模板参数&#xff0c;类似于 typename 和 class 关键字。 requires关键字常与type_traits头文件下类型检查函数匹配使用&#xff0c;当requires后…

视频监控系统中,可变码率和固定码率对录像文件存储大小的影响,如何配置比较好?

目录 一、问题描述 二、视频监控的录像文件计算 &#xff08;一&#xff09;计算方法 &#xff08;二&#xff09;计算工具 三、原因分析 &#xff08;一&#xff09;检查配置 1、IPCa配置 2、IPCb配置 3、录像文件存储大小的理论值 &#xff08;二&#xff09;实际情…

五丰黎红引领新营销模式:布局一物一码数字化营销,提高调味品销量和复购率

调味品行业的销售渠道主要有餐饮、家庭消费和食品加工&#xff0c;按销售额的占比约为6&#xff1a;3&#xff1a;1&#xff0c;餐饮行业是调味品行业的供需主力。在餐饮行业中&#xff0c;“大厨”这一角色具有十分重要的地位。因此&#xff0c;借助大厨的力量成为了许多调味品…

汇聚荣科技:如何有效为拼多多店铺引流?

在电商竞争激烈的今天&#xff0c;为拼多多店铺引流是每个店主必须面对的挑战。有效的引流策略不仅能增加店铺曝光度&#xff0c;还能提升转化率&#xff0c;促进销量增长。 一、社交媒体营销 利用微信、微博等社交平台进行推广&#xff0c;可以通过发布产品信息、用户评价和促…

985大学电子信息专硕,考C语言+数据结构!中央民族大学25计算机考研考情分析!

中央民族大学&#xff08;Minzu University of China&#xff09;坐落于北京市学府林立的海淀区&#xff0c;南邻国家图书馆&#xff0c;北依中关村科技园&#xff0c;校园环境典雅&#xff0c;古朴幽美&#xff0c;人文氛围浓郁&#xff0c;具有鲜明的民族特色。由北京市、国家…