在 FreeRTOS 中使用字符串队列

Posted

技术标签:

【中文标题】在 FreeRTOS 中使用字符串队列【英文标题】:Using Queue of string in FreeRTOS 【发布时间】:2021-07-24 13:00:39 【问题描述】:

我正在使用 Ardunio/ESP32,而且我对 FreeRTOS 还很陌生。我想要一个单独负责在串口上打印文本的任务,其他任务可以推送消息。 所以,我决定使用一个容量为 10 项的 char 数组(或 std::string)队列。 但是我不确定队列存储分配对于不同长度的元素是如何工作的!

您能否告诉我应该如何创建和使用队列以及应该考虑哪些因素?

【问题讨论】:

这有点基于意见,但您的方法听起来很合理,因为它是相当普遍的说法。详细阅读队列文档,以及有关 FreeRTOS 内存要求的文档。队列的内存要求取决于您是对数据的引用还是对数据的副本进行排队。 FreeRTOS 队列允许两者之一。如果您的字符串很少且非常小,则副本可能没问题。如果没有,那么你应该使用引用。 std::string 也有小字符串优化,所以最多 16 个字符的字符串(包括 '\0')不会在堆中分配。但是我希望从多个线程中新建/删除问题(至少我对此有所怀疑) 【参考方案1】:

TL;DR:创建一个指向std::string 的指针队列并在任一侧处理new/delete。确保生产者和消费者都在使用共享内存空间。

在像 FreeRTOS Queue 这样的“原始”内存 API 中使用 std::string 的问题实际上并不是对象大小的问题。事实上,std::string 对象的大小是固定的,与对象存储的字符数组的大小无关。不相信我?尝试在您自己的机器上编译和运行这个简单的程序:

#include <iostream>

int main(int argc, char **argv)

    std::string str1 = "short";
    std::string str2 = "very very very very very very long";

    std::cout << "str1 length = " << sizeof(str1) << "\n";
    std::cout << "str2 length = " << sizeof(str2) << "\n";
    return 0;

你会得到这样的东西(实际大小会因你的平台而异):

str1 length = 24
str2 length = 24

请注意,如果您使用str1.size(),此结果会有所不同。

原因是像std::string 这样的标准库容器通常将它们的内容存储在malloc'ed 块中,因此std::string 对象本身只会存储一个指向包含字符串中字符数据的数组的指针。在这种情况下,sizeof() 从编译器的角度告诉您对象的大小,这将是指针和其他固定元数据(即结构的字段)的大小。 .size() 方法告诉您实现中的字符串大小,其中将包括对分配的字符数组的字符串长度计算。

您不能将std::string 对象复制到xQueueSend() 变体中的原因是生命周期管理问题。 FreeRTOS API 通过原始memcpy 执行*Send*Receive。标准库容器通常不是 POD 类型,这意味着它们必须通过专用的复制构造函数进行复制。如果您不这样做,您可能会使对象的某些内部状态无效,除非您真的知道自己在做什么。

因此,完成这项工作的最简单方法如下所示:

    xQueueCreate() 上,将对象大小设置为sizeof(std::string *)
xQueue = xQueueCreate(NUM_OBJECTS, sizeof(std::string *));
    xQueueSend() 上,通过运算符new 创建std::string,并将地址传递给该指针以进行复制。不要删除对象。
std::string *pStr = new std::string("hello world!");
xQueueSend(xQueue, &pStr, (TickType_t)0);
    xQueueReceive() 上,复制出指针。用这个指针做你需要做的事情,然后delete它。
std::string *pStr = NULL;
xQueueReceive(xQueue, &pStr, (TickType_t)10);
// do stuff with pStr
delete pStr;

【讨论】:

感谢 Jon 提供全面的解决方案。我完全相信你,因为我注意到在队列中使用 std::string 时出现异常行为。我对内存问题非常紧张,不确定谁会负责发布部分。我什至不知道 FreeRTOS 是否承诺释放它们。但是您的解决方案非常清晰明了。最后一个问题,您认为使用 std::string 会更好还是纯字符数组? @Reza 除非您使用的是内存受限的系统,否则我会继续使用std::string。这将为您提供 STL 类的所有功能,而无需自己重新实现功能。使用纯 char 数组的唯一真正优势是避免每个 std::string 结构分配的开销。如果您受到内存限制,请务必使用纯字符数组,请注意,您最终会在发送/接收的任一侧编写更多用于字符串管理的代码。 感谢您的帮助。我可能有点内存受限,但很可能我将字符串(可能是任务)移动到 PSRAM 中,因此不再有内存限制。【参考方案2】:

嗨,在这个例子中,我将使用结构体在队列中发送一个字符串数据:

首先我们创建结构体:

typedef struct log_payload_t

    uint8_t message[256];
 log_payload_t;

创建队列:

xQueueHandle log_queue;
int main(int argc, char **argv)

   log_queue = xQueueCreate(10, sizeof(log_payload_t));

将字符串发送到队列:

log_payload_t payload;

memset(payload.message, 0, 256);
memcpy(payload.message, "Example text", strlen(str));

if (xQueueSend(log_queue, &payload, 0))
    
            printf("added message to log queue  \n");
    
    else
    
            printf("failed to add message to log queue\n");
        
    

从队列接收:

log_payload_t payload;
if (xQueueReceive(log_queue, &payload, 0))

    printf("Log Queue Data %s \n", payload.message);


else

    vTaskDelay(10);
           

【讨论】:

感谢您的回复。我担心 std::string 的另一个解决方案是为 ram 提供更好的利用。在您的解决方案中,无论如何我们都会保留 2kb,并且我们可能需要更多的空间来存储某些消息。 是的我同意你的观点,这个提议是针对C的,没有std字符串类型。【参考方案3】:

在我看来,使用固定大小缓冲区的容器可能不是最理想的。向/从队列发送/接收的每个操作都将涉及整个缓冲区的完整副本。它还需要更大且精心挑选的堆栈大小。

freeRTOS message buffers 在我看来非常方便解决您将几乎任意大小的数据块从一个任务发送到另一个任务的需求。

【讨论】:

感谢您的建议,这看起来是一个不错的解决方案。但是,我仍然不确定消息缓冲区如何比常规队列更有用。 您想发送字符串,本质上是可变长度的数据,并且有一个发送任务和一个接收任务。对于这种情况,消息缓冲区在内存使用方面比this solution 更有效。如果您可以摆脱创建释放 std::string 对象的开销,那么这是堆操作方面的另一种优化。 IMO,最灵活的解决方案是this one,使用指向 std::string 的指针队列,在发送任务中分配并在接收任务中释放。

以上是关于在 FreeRTOS 中使用字符串队列的主要内容,如果未能解决你的问题,请参考以下文章

在 freertos API 中使用队列

FreeRTOS消息队列 & ESP32使用

FreeRTOS高级篇5---FreeRTOS队列分析

FreeRTOSFreeRTOS学习笔记(14)— FreeRTOS的消息队列(原生API)

FreeRTOS学习笔记 ——消息队列

FreeRTOS学习笔记 ——消息队列