Zephyr RTOS -- Message Queues

Posted 搬砖-工人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Zephyr RTOS -- Message Queues相关的知识,希望对你有一定的参考价值。

文章目录

本笔记基于 Zephyr 版本 2.6.0-rc2

 

前言

本人正在学习 Zephyr,一个可移植性较强,可以兼容多种开发板及物联网设备的操作系统,如果你感兴趣,可以点此查看我的 学习笔记总述 进行了解!

 

摘要

消息队列是一个内核对象,它允许线程和 ISR 异步发送和接收固定大小的数据项。

 

1. Concepts

可以定义任意数量的消息队列 (仅受可用 RAM 限制)。每个消息队列由其内存地址引用。

消息队列具有下列属性:

  • 一个已发送但尚未接收的数据项的 环形缓冲区(ring buffer)
  • 数据项的大小(data item size),以字节为单位。
  • 可在循环缓冲区中排队的数据项的 最大数量(maximum quantity)

消息队列的环形缓冲区必须保持 N 字节对齐,其中的 N 是 2 的幂(即 1,2,4,8,…),为确保存储在环形缓冲区中的消息与此边界类似地对齐,数据项的大小必须是 N 的倍数。

数据项可以由线程或 ISR 发送到消息队列。如果存在,发送线程指向的数据项被复制到等待线程;否则,如果空间可用,则将该项目复制到消息队列的环形缓冲区。在任一情况下,被发送的数据区的大小 必须等于消息队列的数据项大小。

如果线程在环形缓冲区已满时尝试发送数据项,则发送线程可能会选择等待空间变得可用。当环形缓冲区已满时,任何数量的发送线程都可以同时等待;当空间可用时,它会分配给等待时间最长的最高优先级发送线程。

如果线程在环形缓冲区为空时尝试接收数据项,则接收线程可能会选择等待被发送的数据项。当环形缓冲区为空时,任何数量的接收线程都可以同时等待;当一个数据项可用时,它会被分配给等待时间最长的最高优先级的接收线程。

线程可以从消息队列中接收数据项。将数据项复制到接收线程指定的区域;接收区的大小必须等于消息队列的数据项大小。

线程还可以查看消息队列头部的消息,而无需将其从队列中删除。将数据项复制到接收线程指定的区域;接收区的大小必须等于消息队列的数据项大小。

Note:
内核确实允许 ISR 从消息队列接收项目,但是如果消息队列为空,则 ISR 不得尝试等待。

 

2. Implementation

2.1 Defining a Message Queue

使用 k_msgq 的类型变量来定义消息队列,然后使用 k_msgq_init() 进行初始化。

以下定义并初始化了一个空消息队列,该队列能够容纳 10 个项目,每个项目的长度为 12 个字节:

struct data_item_type 
    uint32_t field1;
    uint32_t field2;
    uint32_t field3;
;

char __aligned(4) my_msgq_buffer[10 * sizeof(struct data_item_type)];
struct k_msgq my_msgq;

k_msgq_init(&my_msgq, my_msgq_buffer, sizeof(struct data_item_type), 10);

也可以在编译时通过调用 K_MSGQ_DEFINE 来定义和初始化消息队列:

K_MSGQ_DEFINE(my_msgq, sizeof(struct data_item_type), 10, 4);

下面的代码演示了前面示例代码中定义的结构的对齐实现。aligned 意味着每个 data_item_type 都将从指定的字节边界开始。 aligned(4) 表示结构与可被 4 整除的地址对齐。

typedef struct 
    uint32_t field1;
    uint32_t field2;
    uint32_t field3;
__attribute__((aligned(4))) data_item_type;

 

2.2 Writing to a Message Queue

通过调用 k_msgq_put() 将数据项添加到消息队列中。

以下代码建立在上述示例的基础上,并使用消息队列将数据项从生产线程传递到一个或多个消费线程。如果消息队列因消费者跟不上而填满,则生产线程会丢弃所有现有数据,以便保存较新的数据。

void producer_thread(void)

    struct data_item_type data;

    while (1) 
        /* create data item to send (e.g. measurement, timestamp, ...) */
        data = ...

        /* send data to consumers */
        while (k_msgq_put(&my_msgq, &data, K_NO_WAIT) != 0) 
            /* message queue is full: purge old data & try again */
            k_msgq_purge(&my_msgq);
        

        /* data item was successfully added to message queue */
    

 

2.3 Reading from a Message Queue

通过调用 k_msgq_get() 从消息队列中获取数据项。

以下代码建立在上述示例的基础上,并使用消息队列处理由一个或多个生产线程生成的数据项。请注意,应该测试 k_msgq_get() 的返回值,因为 k_msgq_purge() 可以返回 -ENOMSG

void consumer_thread(void)

    struct data_item_type data;

    while (1) 
        /* get a data item */
        k_msgq_get(&my_msgq, &data, K_FOREVER);

        /* process data item */
        ...
    

 

2.4 Peeking into a Message Queue

通过调用 k_msgq_peek() 从消息队列中读取数据项。

以下代码查看消息队列以读取由一个或多个生产线程生成的队列头部的数据项。

void consumer_thread(void)

    struct data_item_type data;

    while (1) 
        /* read a data item by peeking into the queue */
        k_msgq_peek(&my_msgq, &data);

        /* process data item */
        ...
    

 

3. Suggested Uses

使用消息队列以异步方式在线程之间传输小数据项。

Note:
如果需要,消息队列可用于传输大数据项。但是,这会增加中断延迟,因为在写入或读取数据项时会锁定中断。通常最好通过交换指向数据项的指针而不是数据项本身来传输大数据项。内核的内存映射和内存池对象类型对这种类型的数据传输很有帮助。

同步传输可以通过使用内核的邮箱对象类型来实现。

 

4. Configuration Options

相关配置选项:

  • None

 

参考链接

https://docs.zephyrproject.org/latest/reference/kernel/data_passing/message_queues.html#

以上是关于Zephyr RTOS -- Message Queues的主要内容,如果未能解决你的问题,请参考以下文章

Zephyr RTOS -- Message Queues

Zephyr RTOS -- Stacks

Zephyr RTOS -- Stacks

Zephyr RTOS -- Stacks

Zephyr RTOS -- Polling API

Zephyr RTOS -- Polling API