第一节 引言
Study 队列相关部分内容。
对于FreeRTOS系统,队列被用在任务与任务间、中断和任务间进行一条或多条信息的传递,队列不属于某个特定的任务,系统中任何任务都可以向队列中发送消息,或者从队列中提取消息。往队列中发送数据叫做入队,从队列中提取数据叫做出队。FreeRTOS系统通常使用先进先出(FIFO)的方式进行信息传递,即,任务从队列中读取到的消息是最先进入到消息队列的消息,同时也支持后进先出方式(LIFO)。
FreeRTOS 中数据发送到队列中会导致数据拷贝,将要发送的数据拷贝到队列中,这就意味着在队列中存储的是数据的原始值,使用的是值传递,而不是原数据的引用(即只传递数据的指针)。
正常情况下,队列会有存储已满的情况或者队列是空的情况,对于这两种情况在入队与出队时就会有些不同。
当队列已满时,就需要有入队阻塞的设计。在队列已满的情况下,一个任务向队列直接发送数据则会出现错误,所以该情况下就要有设计入队阻塞的设计,当任务向队列发送消息时设置阻塞时间,如果在阻塞时间内等待已满的队列有空间,则任务可以将发送的数据放入队列中。
当队列已空时,就需要有出队阻塞的设计。在队列的为空情况下,一个任务从队列直接读取数据则会出现错误,所以该情况下就要有设计出队阻塞的设计,当任务从队列读取消息时设置阻塞时间,如果在阻塞时间内等待已空的队列有数据了,则任务可以将读取队列中的数据。
第二节 队列结构体
typedef struct QueueDefinition
{
int8_t *pcHead; /*此为队列存储区域的开始地址*/
int8_t *pcTail; /*队列存储区的最后一个字节*/
int8_t *pcWriteTo; /*存储区中下一个空闲区域*/
union
{
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; /*队列锁定时,从队列接收(从队列中删除)的出队项目数。 如果队列没有上锁,设置为queueUNLOCKED*/
volatile int8_t cTxLock; /*队列锁定时,传输到队列(添加到队列)的入队项目数。 如果队列没有上锁,设置为queueUNLOCKED*/
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType; /* 队列的类型 0: 队列或队列集 1: 互斥信号量 2: 计数型信号量 3: 二值信号量 4: 可递归信号量 */
#endif
} xQUEUE;
第三节 队列创建函数 xQueueGenericCreate()
创建队列有两个函数可以调用,使用动态内存分配的xQueueCreate()函数与使用静态内存分配 xQueueCreateStatic()函数。两个函数最后返回的都是可用于访问新创建的队列的队列句柄。
函数的调用顺序如下:
xQueueCreate()或xQueueCreateStatic() ->xQueueGenericCreate()
->prvInitialiseNewQueue() ->xQueueGenericReset()
下面是对函数的一些说明。
1.xQueueGenericCreate()
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
xQueueGenericCreate()用于创建一个新的队列并返回可用于访问此队列的队列句柄。每创建一个新的队列都需要为其分配 RAM,用于存储队列的状态以及队列消息的存储区域。
uxQueueLength表示消息队列能够存储的最大单元数,即队列深度或队列长度。
uxItemSize表示设置消息队列中单个消息单元的长度,以字节为单位。
ucQueueType表示设置消息队列的类型,0: 队列或队列集 1: 互斥信号量 2: 计数型信号量 3: 二值信号量 4: 可递归信号量 。
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{
Queue_t *pxNewQueue;
size_t xQueueSizeInBytes;
uint8_t *pucQueueStorage;
configASSERT( uxQueueLength > ( UBaseType_t ) 0 );
//计算队列环形存储空间需要的字节大小,并为队列申请内存空间,空间大小 = 队列结构体+队列环形存储区域
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 /* configSUPPORT_STATIC_ALLOCATION */
prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue ); //对队列进行初始化
}
return pxNewQueue;
}
2.prvInitialiseNewQueue()
prvInitialiseNewQueue()被用来初始化队列结构体。
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; //如果队列项目大小为 0(类型为信号量),则不需要队列存储区,pcHead指向队列存储区的起始地址
}
else
{
pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage; //pcHead指向队列存储区域的起始地址
}
pxNewQueue->uxLength = uxQueueLength; //初始化队列长度
pxNewQueue->uxItemSize = uxItemSize; //队列消息的大小
(void) xQueueGenericReset( pxNewQueue, pdTRUE ); //
pxNewQueue->ucQueueType = ucQueueType; //队列的类型
traceQUEUE_CREATE( pxNewQueue );
}
3.xQueueGenericReset()
xQueueGenericReset()函数来重置队列,继续初始化队列结构体中的成员。
BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue )
{
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
configASSERT( pxQueue );
taskENTER_CRITICAL();
{
pxQueue->pcTail = pxQueue->pcHead + ( pxQueue->uxLength * pxQueue->uxItemSize ); //队列存储区域的结束地址
pxQueue->uxMessagesWaiting = ( UBaseType_t ) 0U; //队列中现有消息数量初始化为0
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
{
//当队列为新创建时,则需要初始化队列结构体中的两个阻塞链表
vListInitialise( &( pxQueue->xTasksWaitingToSend ) );
vListInitialise( &( pxQueue->xTasksWaitingToReceive ) );
}
}
taskEXIT_CRITICAL();
return pdPASS;
}