队列用在任务与任务,任务与中断之间传递消息。队列类型有普通队列,二值信号量队列,计数信号量队列,互斥信号量队列等。
队列项:可以是各种数据类型
队列长度:队列项的数量
队列通常采用先进先出的方式,即FIFO,从队列尾部入队,从队列头部出队。入队是将数据拷贝到队列,数据量大时需要的时间就多。
入队,出队可以设置阻塞时间。0是不阻塞,portMAX_DELAY是一直阻塞。
读队列阻塞:读队列时,队列为空,则可以设置阻塞时间,该任务进入阻塞态,队列不为空时自动由阻塞态进入就绪态,超时也由阻塞态进入就绪态
写队列阻塞:写队列是队列满,则设置阻塞时间,超时强制写入还是放弃写入?
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; /* 创建队列时指定的每个队列项(消息)最大长度*/
/* 当队列上锁后用来统计从队列中接收到(出队的)的队列项数量,若未上锁为queueUNLOCKED*/
volatile int8_t cRxLock;
/* 当队列上锁后用来统计发送到(入队的)队列中的队列项数量,若未上锁为queueUNLOCKED */
volatile int8_t cTxLock;
#if((configSUPPORT_STATIC_ALLOCATION==1)&&(configSUPPORT_DYNAMIC_ALLOCATION==1))
uint8_t ucStaticallyAllocated; /* 若使用静态存储,赋值为pdTURE */
#endif
#if ( configUSE_QUEUE_SETS == 1 ) /* 队列集相关宏 */
struct QueueDefinition *pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 ) /* 跟踪调试相关宏 */
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
/* 老版本的FreeRTOS中队列可能会使用xQUEUE这个名字,新版本都用Queue_t */
typedef xQUEUE Queue_t;
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 ){
/* 如果队列项大小为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 );
/* 初始化消息队列 */
prvInitialiseNewQueue(uxQueueLength,uxItemSize,pucQueueStorage,ucQueueType,pxNewQueue);
}
return pxNewQueue;
}
QueueHandle_t xQueueGenericCreateStatic( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, StaticQueue_t *pxStaticQueue, const uint8_t ucQueueType )
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){
/* 若队列项长度为0,说明没有队列存储区 */
pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
}
else{
/* 把队列空间首地址指向队列项存储区首地址 */
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;//等待处理的消息个数为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;
}
函数首先进行了一系列的断言检查,确保队列和参数的有效性。
接下来,函数进入一个无限循环。在循环中,函数首先进入临界区,检查队列是否有足够的空间来存储待发送的数据。如果队列有足够的空间,函数会调用prvCopyDataToQueue函数将数据复制到队列中,并根据情况决定是否需要进行任务切换。
如果队列已满且设置了阻塞时间,则函数会进入阻塞状态,并在队列有空间可用或超时时解除阻塞。在阻塞期间,函数会将当前任务挂起,并添加到等待发送队列中,然后解锁队列并恢复调度器。如果等待时间已过期,函数将解锁队列、恢复调度器,并返回错误码。
如果队列已满且没有设置阻塞时间,则函数直接解除临界区,返回队列已满的错误码。
总之,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;//返回队列已满
}
}
}
首先,函数使用了一些断言(configASSERT)来确保传入的参数和队列的状态是有效的。
接下来,函数保存了当前的中断状态,并禁用了中断。这是为了确保在操作队列期间不会发生上下文切换或者竞争条件。
然后,函数检查队列是否有足够的空间来接收要发送的数据,或者是否要执行覆盖操作(如果队列已满)。如果满足条件,将数据复制到队列中。
如果队列未被锁定(没有其他任务正在访问它),则检查是否有任务正在等待从队列中接收数据。如果有,从等待接收任务的列表中移除一个任务,并检查是否需要唤醒更高优先级的任务。
如果队列被锁定,增加锁定计数器以表示在锁定期间有数据被发送。
最后,恢复之前保存的中断状态,并返回相应的结果(成功或失败)。
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;
}
/*-----------------------------------------------------------*/
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
/* Check the pointer is not NULL. */
configASSERT( ( pxQueue ) );
/* The buffer into which data is received can only be NULL if the data size
is zero (so no data is copied into the buffer. */
configASSERT( !( ( ( pvBuffer ) == NULL ) && ( ( pxQueue )->uxItemSize != ( UBaseType_t ) 0U ) ) );
/* Cannot block if the scheduler is suspended. */
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
/* This function relaxes the coding standard somewhat to allow return
statements within the function itself. This is done in the interest
of execution time efficiency. */
for( ;; )
{
taskENTER_CRITICAL();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
/* Is there data in the queue now? To be running the calling task
must be the highest priority task wanting to access the queue. */
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
/* Data available, remove one item. */
prvCopyDataFromQueue( pxQueue, pvBuffer );
traceQUEUE_RECEIVE( pxQueue );
pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;
/* There is now space in the queue, were any tasks waiting to
post to the queue? If so, unblock the highest priority waiting
task. */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
taskEXIT_CRITICAL();
return pdPASS;
}
else
{
if( xTicksToWait == ( TickType_t ) 0 )
{
/* The queue was empty and no block time is specified (or
the block time has expired) so leave now. */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
/* The queue was empty and a block time was specified so
configure the timeout structure. */
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 )
{
/* The timeout has not expired. If the queue is still empty place
the task on the list of tasks waiting to receive from the queue. */
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
prvUnlockQueue( pxQueue );
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* The queue contains data again. Loop back to try and read the
data. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else
{
/* Timed out. If there is no data in the queue exit, otherwise loop
back and attempt to read the data. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
3.8在中断中接收队列
/*-----------------------------------------------------------*/
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, void * const pvBuffer, BaseType_t * const pxHigherPriorityTaskWoken )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
configASSERT( pxQueue );
configASSERT( !( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
/* RTOS ports that support interrupt nesting have the concept of a maximum
system call (or maximum API call) interrupt priority. Interrupts that are
above the maximum system call priority are kept permanently enabled, even
when the RTOS kernel is in a critical section, but cannot make any calls to
FreeRTOS API functions. If configASSERT() is defined in FreeRTOSConfig.h
then portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
failure if a FreeRTOS API function is called from an interrupt that has been
assigned a priority above the configured maximum system call priority.
Only FreeRTOS functions that end in FromISR can be called from interrupts
that have been assigned a priority at or (logically) below the maximum
system call interrupt priority. FreeRTOS maintains a separate interrupt
safe API to ensure interrupt entry is as fast and as simple as possible.
More information (albeit Cortex-M specific) is provided on the following
link: http://www.freertos.org/RTOS-Cortex-M3-M4.html */
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
/* Cannot block in an ISR, so check there is data available. */
if( uxMessagesWaiting > ( UBaseType_t ) 0 )
{
const int8_t cRxLock = pxQueue->cRxLock;
traceQUEUE_RECEIVE_FROM_ISR( pxQueue );
prvCopyDataFromQueue( pxQueue, pvBuffer );
pxQueue->uxMessagesWaiting = uxMessagesWaiting - ( UBaseType_t ) 1;
/* If the queue is locked the event list will not be modified.
Instead update the lock count so the task that unlocks the queue
will know that an ISR has removed data while the queue was
locked. */
if( cRxLock == queueUNLOCKED )
{
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
/* The task waiting has a higher priority than us so
force a context switch. */
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* Increment the lock count so the task that unlocks the queue
knows that data was removed while it was locked. */
pxQueue->cRxLock = ( int8_t ) ( cRxLock + 1 );
}
xReturn = pdPASS;
}
else
{
xReturn = pdFAIL;
traceQUEUE_RECEIVE_FROM_ISR_FAILED( pxQueue );
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}