freertos之queue浅析

前言

queue队列是freertos实现计数型信号量、互斥型信号量等的基础,在freertos中最重要的结构除了TCB 、List,就是Queue了。

队列数据结构分析

freertos中信号量、互斥信号量等操作都是基于这个xQUEUE结构的,十分重要

typedef struct QueueDefinition
{
	int8_t *pcHead;					
	/*指向队列数据存储的起始位置*/
	int8_t *pcTail;					
	/*指向队列存储区域末尾的字节。一旦分配了多余的字节来存储队列项,这是用来做记号的。 */
	int8_t *pcWriteTo;				
	/*< 指向存储区域中空闲的下一个位置。 */

	union/* 使用union是编码标准的一个例外,以确保两个互斥结构成员不会同时出现(浪费RAM)。*/
	{
		int8_t *pcReadFrom;			
		/*指向结构用作队列时读取队列项的最后一个位置。*/
		UBaseType_t uxRecursiveCallCount;
		/*在结构用作互斥锁时,维护递归互斥锁被递归地“占用”的次数的计数。*/
	} u;

	List_t xTasksWaitingToSend;		
	/*阻塞的任务列表,这些任务等待发送到此队列。按优先顺序存储。*/
	List_t xTasksWaitingToReceive;	
	/*队列等待读取的任务列表,这些任务等待接收此队列的数据。按优先顺序存储。 */

	volatile UBaseType_t uxMessagesWaiting;
	/*当前队列中的列表项数目。 */
	UBaseType_t uxLength;			
	/*队列的长度定义为它将持有的链表项的数量,而不是字节的数量。*/
	UBaseType_t uxItemSize;			
	/*队列持有的每个列表项的大小 */

	volatile int8_t cRxLock;		
	/*存储在锁定队列时从队列接收(从队列中删除)的项数。当队列未锁定时,设置为queue解锁。*/
	volatile int8_t cTxLock;		
	/*< 存储传输到队列的项数(添加到队列中)当队列被锁定时。当队列未锁定时,设置为queue解锁。*/
} xQUEUE;

代码分析

队列创建

跟task创建一样,也分为静态和动态两个方法,xQueueGenericCreateStaticxQueueGenericCreate下面具体看相关代码

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 ){
		/*没有队列存储区域。*/
		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 );

		#if( configSUPPORT_STATIC_ALLOCATION == 1 ){
			/*可以静态或动态创建队列,因此注意,此任务是动态创建的,以备稍后使用删除。*/
			pxNewQueue->ucStaticallyAllocated = pdFALSE;
		}
		#endif 
		prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
	}
	return pxNewQueue;
}


static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, 
				const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, 
				const uint8_t ucQueueType, Queue_t *pxNewQueue ){
	if( uxItemSize == ( UBaseType_t ) 0 )
	{
		/* 没有为队列存储区域分配RAM,但是PC头不能设置为NULL,
		因为NULL用作表示队列被用作的键一个互斥锁。
		因此,只需将pcHead设置为指向队列为良性已知位于内存映射中的值。*/
		pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
	}else{
		/* Set the head to the start of the queue storage area. */
		pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
	}

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



BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
	taskENTER_CRITICAL();
	{
		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{
			/*确保事件队列以正确的状态启动。*/
			vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
			vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
		}
	}
	taskEXIT_CRITICAL();
	return pdPASS;
}

mutex创建

其本质与queue创建是一样的,但是有几个细节需要注意。
顺序是先创建一个queue,然后针对于mutex特定的数据结构(使用的是union)进一步做初始化

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, ucQueueType );
	prvInitialiseMutex( pxNewQueue );
	return pxNewQueue;
}


static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
	if( pxNewQueue != NULL ){
		/* 队列创建函数将设置所有队列结构成员对于一般队列是正确的,
		但是这个函数正在创建一个互斥锁。覆盖那些需要不同设置的成员-
		特别是优先级继承所需的信息。*/
		pxNewQueue->pxMutexHolder = NULL;
		pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
		/* 如果是地柜互斥锁的话 */
		pxNewQueue->u.uxRecursiveCallCount = 0;
		/* Start with the semaphore in the expected state. */
		( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
	}
}

计数信号量创建

其本质与queue创建是一样的,但是有几个细节需要注意。
顺序是先创建一个queue,然后针对于CountingSemaphore特定的数据结构(信号量的初始值)进一步做初始化。

QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )
{
QueueHandle_t xHandle;
	xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );
	if( xHandle != NULL )
	{//针对于计数信号量初始值初始化
		( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
	}
	return xHandle;
}

队列数据的接收和发送

可以想象一下流程,队列的发送一般是检测等待接收队列的列表,如果等待接收列表不为空,那么就直接唤醒等待列表中的等待任务,否则就将数据写入到队列存储区域。

你可能感兴趣的:(Freertos)