队列是FreeRTOS主要的任务间通讯方式,包括二进制信号量、计数信号量、互斥量和递归互斥量都是使用队列来实现的。
先看一下,队列的数据结构
/* 队列结构体 */
typedef struct QueueDefinition
{
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; /* 每个队列项的大小 */
volatile int8_t cRxLock; /* 锁定期间,从队列中接收队列项的次数 */
volatile int8_t cTxLock; /* 锁定期间,向队列中发送队列项的次数 */
#if ((configSUPPORT_STATIC_ALLOCATION == 1) && (configSUPPORT_DYNAMIC_ALLOCATION == 1))
uint8_t ucStaticallyAllocated;
#endif
#if (configUSE_QUEUE_SETS == 1)
struct QueueDefinition *pxQueueSetContainer;
#endif
#if (configUSE_TRACE_FACILITY == 1)
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
}xQUEUE;
typedef xQUEUE Queue_t;
创建队列:
1.为队列申请内存空间
2.初始化队列结构体成员
/* 创建队列 */
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);
/* 队列项大小为零,比如互斥锁等 */
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;
pucQueueStorage += sizeof(Queue_t);
#if (configSUPPORT_STATIC_ALLOCATION == 1)
{
pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif
/* 初始化新队列,即初始化队列结构体成员 */
prvInitialiseNewQueue(uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue);
}
else
{
traceQUEUE_CREATE_FAILED(ucQueueType);
mtCOVERAGE_TEST_MARKER();
}
/* 返回新队列句柄 */
return pxNewQueue;
}
从程序中可以看出,结构体和队列项在存储空间上是连续的
初始化新的队列
/* 初始化新队列 */
static void prvInitialiseNewQueue(const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, const uint8_t ucQueueType, Queue_t *pxNewQueue)
{
(void)ucQueueType;
/* 队列项大小为0,比如互斥锁等 */
if(uxItemSize == (UBaseType_t)0)
{
/* 由于没有队列存储区,初始化队列头部指针指向队列结构体 */
pxNewQueue->pcHead = (int8_t *)pxNewQueue;
}
/* 队列项大小不为0 */
else
{
/* 初始化队列头部指针为队列存储区起始地址 */
pxNewQueue->pcHead = (int8_t *)pucQueueStorage;
}
/* 初始化队列存储区队列项容量 */
pxNewQueue->uxLength = uxQueueLength;
/* 初始化队列项大小 */
pxNewQueue->uxItemSize = uxItemSize;
/* 复位队列的一些可变参数 */
(void)xQueueGenericReset(pxNewQueue, pdTRUE);
#if (configUSE_TRACE_FACILITY == 1)
{
pxNewQueue->ucQueueType = ucQueueType;
}
#endif
#if (configUSE_QUEUE_SETS == 1)
{
pxNewQueue->pxQueueSetContainer = NULL;
}
#endif
traceQUEUE_CREATE(pxNewQueue);
}
/* 队列复位 */
BaseType_t xQueueGenericReset(QueueHandle_t xQueue, BaseType_t xNewQueue)
{
Queue_t *const pxQueue = xQueue;
configASSERT(pxQueue);
/* 进入临界区 */
taskENTER_CRITICAL();
{
/* 初始化队列尾部为队列项存储区结束位置 */
pxQueue->u.xQueue.pcTail = pxQueue->pcHead + (pxQueue->uxLength * pxQueue->uxItemSize);
/* 当前插入队列项数初始化为0 */
pxQueue->uxMessagesWaiting = (UBaseType_t)0U;
/* 初始化队列项插入指针为队列项存储区头部 */
pxQueue->pcWriteTo = pxQueue->pcHead;
/* 初始化队列项读取指针为最后一个队列项 */
pxQueue->u.xQueue.pcReadFrom = pxQueue->pcHead + ((pxQueue->uxLength - 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;
}
队列(uxLength=3、uxItemSize=4)初始化完成之后,队列内存空间排列如图:
入队:
队列项入队分为带中断保护的入队操作和不带中断保护的入队操作。每种情况下又分为从队列尾部入队和从队列首部入队两种操作,从队列尾部入队还有一种特殊情况,覆盖式入队,即队列满后自动覆盖最旧的队列项。
入队的主要步骤:
1.将队列项数据拷贝到队列中
2.如果队列没满,则顺利插入,同时将等待接收队列项而阻塞的任务移除并重新挂接到就绪列表
3.如果队列已满,将队列挂接到等待发送列表中,并挂接到延时列表中进行阻塞
/* 发送队列项 */
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;
configASSERT(pxQueue);
configASSERT(!((pvItemToQueue == NULL) && (pxQueue->uxItemSize != (UBaseType_t)0U)));
configASSERT(!((xCopyPosition == queueOVERWRITE) && (pxQueue->uxLength != 1)));
#if ((INCLUDE_xTaskGetSchedulerState == 1) || (configUSE_TIMERS == 1))
{
configASSERT(!((xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) && (xTicksToWait != 0)));
}
#endif
for(;;)
{
/* 进入临界区 */
taskENTER_CRITICAL();
{
/* 目前已插入队列项数小于最大可插入队列数或者覆盖型插入 */
if((pxQueue->uxMessagesWaiting < pxQueue->uxLength) || (xCopyPosition == queueOVERWRITE))
{
traceQUEUE_SEND(pxQueue);
#if (configUSE_QUEUE_SETS == 1)
{
UBaseType_t uxPreviousMessagesWaiting = pxQueue->uxMessagesWaiting;
xYieldRequired = prvCopyDataToQueue(pxQueue, pvItemToQueue, xCopyPosition);
if(pxQueue->pxQueueSetContainer != NULL)
{
if((xCopyPosition == queueOVERWRITE) && (uxPreviousMessagesWaiting != (UBaseType_t)0))
{
mtCOVERAGE_TEST_MARKER();
}
else if(prvNotifyQueueSetContainer(pxQueue, xCopyPosition) != pdFALSE)
{
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
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();
}
}
}
#else
{
/* 根据不同的入队方式,将队列项数据拷贝到队列中 */
xYieldRequired = prvCopyDataToQueue(pxQueue, pvItemToQueue, xCopyPosition);
/* 等待接收队列项而阻塞的任务列表不为空 */
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
/* 退出临界区 */
taskEXIT_CRITICAL();
/* 成功 */
return pdPASS;
}
/* 目前队列已经满了,且不是覆盖型插入 */
else
{
/* 阻塞时间为0 */
if(xTicksToWait == (TickType_t)0)
{
taskEXIT_CRITICAL();
traceQUEUE_SEND_FAILED(pxQueue);
/* 返回队列已满错误 */
return errQUEUE_FULL;
}
/* 当前节拍状态还未记录 */
else if(xEntryTimeSet == pdFALSE)
{
/* 记录当前节拍状态 */
vTaskInternalSetTimeOutState(&xTimeOut);
/* 当前节拍状态已经记录 */
xEntryTimeSet = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
/* 退出临界区 */
taskEXIT_CRITICAL();
/* 挂起调度器 */
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();
}
}
/* 刚好队列出现空位,下一次while循环重新插入 */
else
{
/* 解锁队列 */
prvUnlockQueue(pxQueue);
/* 解除调度器挂起 */
(void)xTaskResumeAll();
}
}
/* 已经超时或者超时之后 */
else
{
/* 解锁队列 */
prvUnlockQueue(pxQueue);
/* 解除调度器挂起 */
(void)xTaskResumeAll();
traceQUEUE_SEND_FAILED(pxQueue);
/* 队列已满 */
return errQUEUE_FULL;
}
}
}
出队:
出队也分为带中断保护的出队操作和不带中断保护的出队操作。每种出队情况都可以选择是否删除队列项。
出队的主要步骤:
1.将数据从队列项中拷贝出来
2.如果队列不是空的,则顺利取出,同时将等待发送队列项而阻塞的任务移除并重新挂接到就绪列表
3.如果队列是空的,将队列挂接到等待接收列表中,并挂接到延时列表中进行阻塞
/* 接收队列项 */
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *const pvBuffer, TickType_t xTicksToWait)
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
Queue_t *const pxQueue = xQueue;
configASSERT((pxQueue));
configASSERT(!(((pvBuffer) == NULL) && ((pxQueue)->uxItemSize != (UBaseType_t)0U)));
#if ((INCLUDE_xTaskGetSchedulerState == 1) || (configUSE_TIMERS == 1))
{
configASSERT(!((xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) && (xTicksToWait != 0)));
}
#endif
for(;;)
{
taskENTER_CRITICAL();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
/* 队列项数 */
if(uxMessagesWaiting > (UBaseType_t)0)
{
/* 将数据从队列项中拷贝出来 */
prvCopyDataFromQueue(pxQueue, pvBuffer);
traceQUEUE_RECEIVE(pxQueue);
/* 队列项数减一 */
pxQueue->uxMessagesWaiting = uxMessagesWaiting - (UBaseType_t)1;
/* 判断等待发送队列项而阻塞的任务列表是否为空,不为空 */
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;
}
/* 队列项数为0 */
else
{
/* 等待时间为0 */
if(xTicksToWait == (TickType_t)0)
{
/* 退出临界区 */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED(pxQueue);
/* 返回队列为空错误 */
return errQUEUE_EMPTY;
}
/* 还未记录当前节拍 */
else if(xEntryTimeSet == pdFALSE)
{
/* 记录当前节拍状态 */
vTaskInternalSetTimeOutState(&xTimeOut);
/* 已经记录当前节拍 */
xEntryTimeSet = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
/* 退出临界区 */
taskEXIT_CRITICAL();
/* 挂起调度器 */
vTaskSuspendAll();
/* 锁定队列 */
prvLockQueue(pxQueue);
/* 检查任务是否超时,没有超时 */
if(xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait) == pdFALSE)
{
/* 判断队列是否为空,为空 */
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();
}
}
/* 队列不为空,直接在while下一个循环中接收队列项 */
else
{
/* 解锁队列 */
prvUnlockQueue(pxQueue);
/* 解除调度器挂起 */
(void)xTaskResumeAll();
}
}
/* 已经超时或者超时之后 */
else
{
/* 解锁队列 */
prvUnlockQueue(pxQueue);
/* 解除调度器挂起 */
(void)xTaskResumeAll();
/* 队列为空 */
if(prvIsQueueEmpty(pxQueue) != pdFALSE)
{
traceQUEUE_RECEIVE_FAILED(pxQueue);
/* 返回队列为空错误 */
return errQUEUE_EMPTY;
}
/* 队列不为空,直接在while下一个循环中接收队列项 */
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
队列锁定用于中断中,队列锁定并不表示不准入队/出队。队列锁定的情况下不允许操作因为等待而阻塞的任务列表,用cRxLock和cTxLock来做记录,并在解锁的时候补上该操作。
关于锁定,只分析出队
/* 在中断中接收队列项 */
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue, void *const pvBuffer, BaseType_t * const pxHigherPriorityTaskWoken)
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t *const pxQueue = xQueue;
configASSERT(pxQueue);
configASSERT(!((pvBuffer == NULL) && (pxQueue->uxItemSize != (UBaseType_t)0U)));
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
/* 屏蔽大于系统调用优先级的中断 */
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
/* 队列项大于0 */
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(cRxLock == queueUNLOCKED)
{
/* 判断等待发送列表中是否为空,不为空 */
if(listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToSend)) == pdFALSE)
{
/* 将任务从等待发送而阻塞的任务列表中移除并挂接到就绪列表,任务优先级高于当前任务优先级 */
if(xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToSend)) != pdFALSE)
{
/* 允许高优先级抢占 */
if(pxHigherPriorityTaskWoken != NULL)
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 队列被锁定 */
else
{
/* 在锁定过程中接收队列项数加一 */
pxQueue->cRxLock = (int8_t)(cRxLock + 1);
}
/* 成功 */
xReturn = pdPASS;
}
/* 队列项为0 */
else
{
/* 失败 */
xReturn = pdFAIL;
traceQUEUE_RECEIVE_FROM_ISR_FAILED(pxQueue);
}
}
/* 打开中断屏蔽 */
portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus);
return xReturn;
}
解锁的时候,补上对任务列表的操作
/* 解锁队列 */
static void prvUnlockQueue(Queue_t *const pxQueue)
{
......
/* 进入临界区 */
taskENTER_CRITICAL();
{
int8_t cRxLock = pxQueue->cRxLock;
/* 中断期间上锁,接收队列项次数 */
while(cRxLock > queueLOCKED_UNMODIFIED)
{
/* 检查等待发送队列项列表 */
if(listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToSend)) == pdFALSE)
{
/* 将任务从等待发送队列项而阻塞的任务列表中移除并挂接到就绪列表 */
if(xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToSend)) != pdFALSE)
{
/* 请求切换 */
vTaskMissedYield();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 接收上锁次数减一 */
--cRxLock;
}
else
{
break;
}
}
/* 接收未上锁 */
pxQueue->cRxLock = queueUNLOCKED;
}
/* 退出临界区 */
taskEXIT_CRITICAL();
}