任务通信过程中,如果消息类型不同,使用一条队列来实现则有些麻烦。
FreeRTOS 提供队列集合,用于对多个队列以及信号量进行“监听”,只要其中不管哪一个有消息到来,都可以让任务退出阻塞状态。这就类似于linux网络编程时的select(IO复用)。
先看一下队列结构体
多了一个pxQueueSetContainer成员变量,队列所属队列集。
在插入队列项的时候,用于查找队列所属的队列集并通知正在监听该队列集的任务。
/* 队列结构体 */
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 (configUSE_QUEUE_SETS == 1)
struct QueueDefinition *pxQueueSetContainer; /* 队列所属队列集 */
#endif
......
}xQUEUE;
typedef xQUEUE Queue_t;
队列集本身也是一个队列,将所有队列加入队列集,就可以对所有队列进行监听了
/* 创建队列集 */
QueueSetHandle_t xQueueCreateSet(const UBaseType_t uxEventQueueLength)
{
QueueSetHandle_t pxQueue;
/* 创建队列项大小为sizeof(Queue_t *),长度为uxEventQueueLength的队列 */
pxQueue = xQueueGenericCreate(uxEventQueueLength, (UBaseType_t)sizeof(Queue_t *), queueQUEUE_TYPE_SET);
return pxQueue;
}
将队列加入队列集,本质上就是将队列的所属队列集设置为该队列集
/* 将队列加入队列集 */
BaseType_t xQueueAddToSet(QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet)
{
BaseType_t xReturn;
/* 进入临界区 */
taskENTER_CRITICAL();
{
/* 队列所属队列集不为空 */
if(((Queue_t *)xQueueOrSemaphore)->pxQueueSetContainer != NULL)
{
/* 返回错误 */
xReturn = pdFAIL;
}
/* 队列中已经有队列项 */
else if(((Queue_t *)xQueueOrSemaphore)->uxMessagesWaiting != (UBaseType_t)0)
{
/* 返回错误 */
xReturn = pdFAIL;
}
/* 队列不属于任何队列集,队列中没有队列项 */
else
{
/* 将队列所属队列集设置该队列集 */
((Queue_t *)xQueueOrSemaphore)->pxQueueSetContainer = xQueueSet;
/* 返回成功 */
xReturn = pdPASS;
}
}
/* 退出临界区 */
taskEXIT_CRITICAL();
return xReturn;
}
和普通队列不同的是:
如果队列被加入队列集,则通知队列集
如果队列没有加入队列集,则通知队列(解除那些等待的队列)
注意:某条队列中如果已经存在队列项,再插入一个队列项的时候并不会再通知队列集,这就意味着监听到某条队列有队列项的时候,要一次性把队列取干净
/* 发送队列项 */
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)
{
/* 覆盖型插入或者先前队列项数不为0,说明已经通知过队列集 */
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
{
......
}
#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;
}
}
}
源码中使用prvNotifyQueueSetContainer函数,通知队列集
通知队列集,其实就是将队列作为队列项插入到队列集中,并通知监听而阻塞的任务
/* 通知队列集 */
static BaseType_t prvNotifyQueueSetContainer(const Queue_t *const pxQueue, const BaseType_t xCopyPosition)
{
Queue_t *pxQueueSetContainer = pxQueue->pxQueueSetContainer;
BaseType_t xReturn = pdFALSE;
configASSERT(pxQueueSetContainer);
configASSERT(pxQueueSetContainer->uxMessagesWaiting < pxQueueSetContainer->uxLength);
/* 队列集还没满 */
if(pxQueueSetContainer->uxMessagesWaiting < pxQueueSetContainer->uxLength)
{
const int8_t cTxLock = pxQueueSetContainer->cTxLock;
traceQUEUE_SEND(pxQueueSetContainer);
/* 将队列拷贝进队列集 */
xReturn = prvCopyDataToQueue(pxQueueSetContainer, &pxQueue, xCopyPosition);
/* 没锁定 */
if(cTxLock == queueUNLOCKED)
{
/* 有任务在监听队列集 */
if(listLIST_IS_EMPTY(&(pxQueueSetContainer->xTasksWaitingToReceive)) == pdFALSE)
{
/* 将正在监听队列集而阻塞的任务从事件列表中移除,加入就绪列表 */
if(xTaskRemoveFromEventList(&(pxQueueSetContainer->xTasksWaitingToReceive)) != pdFALSE)
{
xReturn = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 锁定 */
else
{
/* 锁定期间发送次数加一,解锁的时候补偿处理这么多次 */
pxQueueSetContainer->cTxLock = (int8_t)(cTxLock + 1);
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
监听队列集,其实就是等待从队列集中取出队列指针
/* 监听队列集 */
QueueSetMemberHandle_t xQueueSelectFromSet(QueueSetHandle_t xQueueSet, TickType_t const xTicksToWait)
{
QueueSetMemberHandle_t xReturn = NULL;
/* 从队列集中取出队列指针 */
(void)xQueueReceive((QueueHandle_t)xQueueSet, &xReturn, xTicksToWait);
return xReturn;
}