阻塞多个 RTOS 对象
队列集简介
队列集是一个 FreeRTOS 功能,
可让 RTOS 任务在同时从多个队列和(或)
信号量接收数据时进行阻塞(挂起)。
队列和信号量被分成集合,之后任务并非对
单个队列或信号量进行阻塞,而是对集合进行阻塞。
注意:虽然有时需要阻塞(挂起)多个队列
(当 FreeRTOS 与遗留代码的第三方集成时),但不受此类限制的设计
通常能以更有效的方式实现相同的功能,方法是使用
替代设计模式
(记录在此页面底部)。
使用队列集
队列集的使用方式与 select() API 函数和相关函数类似,
后者是标准伯克利套接字网络 API 的一部分。
队列集可能包含队列和信号量,
合称队列集成员。 能获得队列句柄或信号量句柄的 API 函数参数和返回值
使用 QueueSetMemberHandle_t 类型。
QueueHandle_t 和 SemaphoreHandle_t 类型的变量通常可以隐式
转换为 QueueSetMemberHandle_t 参数或返回值,而不会生成编译器
警告(通常不需要显式转换到 QueueSetMemberHandle_t 类型或
从该类型进行显式转换)。
创建一个队列集
|
在使用队列集之前,必须使用
xQueueCreateSet() API 函数来创建队列集。
创建后,队列集由
QueueSetHandle_t 类型的变量引用。
|
添加成员到队列集
|
使用 xQueueAddToSet() API 函数
添加队列或信号量至队列集。
|
阻塞队列集 (pending)
|
使用 xQueueSelectFromSet() API 函数
测试任何集合成员是否已为读取准备就绪
——当成员是队列时读取指的是“接收” (receiving),
当成员是信号量时读取指的是“获得” (taking)。
就像使用 xQueueReceive()
和 xSemaphoreTake() API 函数一样,xQueueSelectFromSet()
允许调用任务选择性阻塞,直到队列集成员已为
读取准备就绪为止。
如果调用 xQueueSelectFromSet() 超时,则返回 NULL。 否则
xQueueSelectFromSet() 会返回已为读取准备就绪的队列集成员的句柄,
允许调用任务立即调用 xQueueReceive() 或
xSemaphoreTake()(分别在队列句柄或信号量句柄上),
保证操作将成功。
|
源代码示例
xQueueCreateSet() API 函数文档
页面包括源代码示例。
标准演示/测试文件名为 QueueSet.c(位于
主 FreeRTOS zip 下载文件的 FreeRTOS/Demo/Common/Minimal/ 目录下)
包含一个全面的示例。
除非有特定的集成问题
需要在多个队列上进行阻塞,否则可以使用单个队列
以较小的代码大小、RAM 大小和运行时间开销实现相同的功能
。 FreeRTOS-Plus-UDP 实现提供了一个实用的示例,
说明了如何完成此操作,相关描述请见以下部分。
UDP/IP 堆栈:问题定义
管理 FreeRTOS-Plus-UDP 堆栈的任务是事件驱动的。 事件有多种
来源。 有些事件没有与之关联的任何数据。 有些事件有
与之关联的可变数据量。 事件包括:
-
接收帧的以太网硬件。 帧包含大型
可变的数据量。
-
完成帧传输、
释放网络和 DMA 缓冲区的以太网硬件。
发送数据包的应用程序任务。 数据包可以包含大型
可变的数据量。
各种软件定时器,包括 ARP 定时器。 定时器事件
不与任何数据关联。
UDP/IP 堆栈:解决方案
UDP/IP 堆栈本可以为每个事件源使用不同的队列,然后
使用队列集一次阻塞所有队列。 相反, UDP/IP 堆栈会:
-
定义一个结构体,其中一个成员保存事件
类型,另一成员保存与该事件关联的数据
(或数据指针)。
-
使用创建的单个队列来保存定义的结构体。 各
事件源会发布到同一队列。
结构体定义如下所示。
typedef struct IP_TASK_COMMANDS
{
eIPEvent_t eEventType; /* Tells the receiving task what the event is. */
void *pvData; /* Holds or points to any data associated with the event. */
} xIPStackEvent_t;
使用该结构体的示例:
-
当 ARP 定时器到期时,它会向队列发送一个事件,
同时将 eEventType 设置为 eARPTimerEvent (枚举类型)。 ARP 定时器事件不
与任何 数据关联,因此未设置 pvData。
-
当以太网驱动器接收帧时,它会向队列发送一个事件,
同时将 eEventType 设置为
eEthernetRxEvent,且将 pvData 设置为指向帧缓冲区。
-
等等。
UDP/IP 任务使用简单循环处理事件:
/* The variable used to receive from the queue. */
xIPStackEvent_t xReceivedEvent;
for( ;; )
{
/* Wait until there is something to do. */
xQueueReceive( xNetworkEventQueue, &xReceivedEvent, portMAX_DELAY );
/* Perform a different action for each event type. */
switch( xReceivedEvent.eEventType )
{
case eNetworkDownEvent :
prvProcessNetworkDownEvent();
break;
case eEthernetRxEvent :
prvProcessEthernetFrame( xReceivedEvent.pvData );
break;
case eARPTimerEvent :
prvAgeARPCache();
break;
case eStackTxEvent :
prvProcessGeneratedPacket( xReceivedEvent.pvData );
break;
case eDHCPEvent:
vDHCPProcess();
break;
default :
/* Should not get here. */
break;
}
}
Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.