前言
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创建一样,也分为静态和动态两个方法,xQueueGenericCreateStatic
、xQueueGenericCreate
下面具体看相关代码
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;
}
队列数据的接收和发送
可以想象一下流程,队列的发送一般是检测等待接收队列的列表,如果等待接收列表不为空,那么就直接唤醒等待列表中的等待任务,否则就将数据写入到队列存储区域。