FreeRTOS队列集

任务通信过程中,如果消息类型不同,使用一条队列来实现则有些麻烦。

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;
}

 

你可能感兴趣的:(FreeRTOS)