下载 FreeRTOS
 

出色的 RTOS & 嵌入式软件

内核
最新资讯
简化任何设备的身份验证云连接。
利用 CoAP 设计节能型云连接 IoT 解决方案。
11.0.0 版 FreeRTOS 内核简介:
FreeRTOS 路线图和代码贡献流程。
使用 FreeRTOS 实现 OPC-UA over TSN。

阻塞多个 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 堆栈会:
    1. 定义一个结构体,其中一个成员保存事件 类型,另一成员保存与该事件关联的数据 (或数据指针)。

    2. 使用创建的单个队列来保存定义的结构体。 各 事件源会发布到同一队列。

    结构体定义如下所示。

    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.