xQueueCreate( uxQueueLength, uxItemSize );
xQueueSend( xQueue, pvItemToQueue, xTicksToWait );
BaseType_t xQueueReceive( QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait );
我们先来看下消息队列的结构体:
typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
int8_t * pcHead; /*< 指向队列存储的起始位置。*/
int8_t * pcWriteTo; /*< 指向队列下一块空闲位置。 */
union
{
QueuePointers_t xQueue; /*< 当结构体作为一个队列时使用. */
SemaphoreData_t xSemaphore; /*< 当队列作为信号量时使用. */
} u;
List_t xTasksWaitingToSend; /*< 阻塞等待向队列中放入元素的任务链表。按照优先级顺序存储。 */
List_t xTasksWaitingToReceive; /*< 阻塞等待从队列中读取元素的任务链表,按照优先级顺序存储。 */
volatile UBaseType_t uxMessagesWaiting; /*<当前队列中的元素个数. */
UBaseType_t uxLength; /*< 队列能够存储的元素个数。 */
UBaseType_t uxItemSize; /*< 每一个元素的大小,如果这个值为0表示Mutex*/
volatile int8_t cRxLock; /*< Stores the number of items received from the queue (removed from the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */
volatile int8_t cTxLock; /*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */
#if ( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
#endif
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition * pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType; //队列类型
#endif
} xQUEUE;
/* The old xQUEUE name is maintained above then typedefed to the new Queue_t
* name below to enable the use of older kernel aware debuggers. */
typedef xQUEUE Queue_t;
接下来我们看下队列是如何创建的。xQueueCreate函数的原型是xQueueGenericCreate函数。
#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 )//队列的类型
FreeRtos的消息队列、信号量、互斥锁、递归锁都是利用队列来实现的,它们都使用Queue_t结构体。队列的类型ucQueueType的取值如下:
#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 )//队列类型为递归锁
xQueueGenericCreate的主要作用有两个,一个是给队列分配内存,另外一个是初始化队列结构体的指针。
第一步,给队列分配内存,部分源代码如下:
Queue_t * pxNewQueue;
size_t xQueueSizeInBytes;
/*分配足够的存储空间,保证队列能够放下最大数目的队列元素。如果uxItemSize为0表示队列作为信号量使用。*/
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
从上面的代码可以看出,队列的内存是动态分配的,分配的内存空间比实际需要的空间多出了一个队列结构体Queue_t的大小。这是必须的,因为每一个队列都需要使用Queue_t这个结构体对队列进行管理。
第二步,初始化队列中的各个指针,部分源代码如下(pxQueue->pcHead指向动态分配内存中的第一个元素地址):
pxQueue->u.xQueue.pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize ); /*lint !e9016 Pointer arithmetic allowed on char types, especially when it assists conveying intent. */
pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U;//队列中元素个数初始化为0
pxQueue->pcWriteTo = pxQueue->pcHead;
pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead + ( ( pxQueue->uxLength - 1U ) * pxQueue->uxItemSize ); /*指向队列最后一个元素的位置。 */
pxQueue->cRxLock = queueUNLOCKED;
pxQueue->cTxLock = queueUNLOCKED;
pcHead和pcTail这两个指针用来记录队列的起始地址和结束地址,这两个值始终不变。数据的写入和读取分别使用pvWriteTo和pcReadFrom两个指针,pcReadFrom始终指向pvWriteTo指向元素的前一个元素的地址。
FreeRtos提供了3种向队列写入数据的方式:
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, //队列句柄
const void * pvItemToQueue,//写入队列的数据
const BaseType_t xPosition )//写入位置
{
BaseType_t xReturn = pdFALSE;
UBaseType_t uxMessagesWaiting;
/* This function is called from a critical section. */
uxMessagesWaiting = pxQueue->uxMessagesWaiting;//记录队列中已有元素个数
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )//如果元素大小为0表示互斥锁
{
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* The mutex is no longer being held. */
xReturn = xTaskPriorityDisinherit( pxQueue->u.xSemaphore.xMutexHolder );
pxQueue->u.xSemaphore.xMutexHolder = NULL;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
}
else if( xPosition == queueSEND_TO_BACK )
{
//将数据拷贝到队列中,每一个元素占用大小为pxQueue->uxItemSize
( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize );
pxQueue->pcWriteTo += pxQueue->uxItemSize; //更新队列空闲内存指针
if( pxQueue->pcWriteTo >= pxQueue->u.xQueue.pcTail ) //如果队列满了,则将写指针指向队列首部(第一个元素的位置),因此是否需要覆盖队列中的数据需要在该函数外判断
{
pxQueue->pcWriteTo = pxQueue->pcHead;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
( void ) memcpy( ( void * ) pxQueue->u.xQueue.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); //将数据存储到队列最右边的内存中
pxQueue->u.xQueue.pcReadFrom -= pxQueue->uxItemSize;//队列指针向左偏移一个元素的大小
if( pxQueue->u.xQueue.pcReadFrom < pxQueue->pcHead ) //如果u.xQueue.pcReadFrom小于队列的其实地址,说明队列已经满了,将其指向队列最后一个元素的位置
{
pxQueue->u.xQueue.pcReadFrom = ( pxQueue->u.xQueue.pcTail - pxQueue->uxItemSize );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xPosition == queueOVERWRITE )//如果队列的写入方式是queueOVERWRITE则需要保证队列中时刻只有一个元素
{
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
/* An item is not being added but overwritten, so subtract
* one from the recorded number of items in the queue so when
* one is added again below the number of recorded items remains
* correct. */
--uxMessagesWaiting;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
//更新队列中的元素个数
pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;
return xReturn;
}
当xPosition的值为queueSEND_TO_BACK时,向队列中写入一个元素,各个指针的变化如下(只有写指针向右偏移一个元素的大小):
当xPosition的值为queueSEND_TO_FRONT时,向队列中写入一个元素,各个指针的变化如下:
FreeRtos使用xQueueSend函数向队列中写入数据,其定义如下:
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) \
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
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 = xQueue;
//此处省略断言部分代码
for( ; ; )
{
taskENTER_CRITICAL();
{
/* 判断当前队列是否还有剩余空间。 如果写入类型是queueOVERWRITE则无需关心队列是否已满. */
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
#if ( configUSE_QUEUE_SETS == 1 )
{
}
#else /* configUSE_QUEUE_SETS */
{
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
/* If there was a task waiting for data to arrive on the
* queue then unblock it now. */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* The unblocked task has a priority higher than
* our own so yield immediately. Yes it is ok to do
* this from within the critical section - the kernel
* takes care of that. */
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else if( xYieldRequired != pdFALSE )
{
/* This path is a special case that will only get
* executed if the task was holding multiple mutexes and
* the mutexes were given back in an order that is
* different to that in which they were taken. */
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_QUEUE_SETS */
taskEXIT_CRITICAL();
return pdPASS;
}
else//如果队列已经满了
{
if( xTicksToWait == ( TickType_t ) 0 )
{
/*如果队列已经满了,或者阻塞时间耗尽。*/
taskEXIT_CRITICAL();
return errQUEUE_FULL;
}
else if( xEntryTimeSet == pdFALSE )
{
/*如果队列满了,并且阻塞时间不为0,则初始化阻塞时间。*/
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
/* Entry time was already set. */
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
/* Interrupts and other tasks can send to and receive from the queue
* now the critical section has been exited. */
vTaskSuspendAll();
prvLockQueue( pxQueue );
/* Update the timeout state to see if it has expired yet. */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )//如果等待时间没有耗尽,尝试向队列中写入数据
{
if( prvIsQueueFull( pxQueue ) != pdFALSE )//如果队列没有满
{
//将任务放入等待列表
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;
}
} /*lint -restore */
}
上述代码主要做了两件事情,首先是判断队列是否已经满了,如果队列有剩余空间则向队列中写入元素,否则检查指定的等待时间是否耗尽,如果在时间耗尽时队列为满,则返回false,否则返回true。
如果队列已经满了,那么新的向队列中写入的元素会被丢弃,队列中已有的元素不会被覆盖。
FreeRtos使用prvCopyDataFromQueue函数从队列中读取数据,其源码如下:
static void prvCopyDataFromQueue( Queue_t * const pxQueue,
void * const pvBuffer )
{
if( pxQueue->uxItemSize != ( UBaseType_t ) 0 )//如果队列中每个元素大小不为0
{
pxQueue->u.xQueue.pcReadFrom += pxQueue->uxItemSize; //队列读指针向后偏移一个单位,这个指针始终指向队列第一个元素的前一个元素的位置
if( pxQueue->u.xQueue.pcReadFrom >= pxQueue->u.xQueue.pcTail ) //如果取出的元素位于队列尾部,则将指针指向队列首部
{
pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//取出队列的内容
( void ) memcpy( ( void * ) pvBuffer, ( void * ) pxQueue->u.xQueue.pcReadFrom, ( size_t ) pxQueue->uxItemSize );
}
}
从上面的图解中可以很清晰的看出来,queueSEND_TO_BACK的入队方式是先进先出,而queueSEND_TO_FRONT的入队方式是先进后出。