RT-Thread消息队列源码机制讲解

文章目录

一、前言

  • 本文源码摘自 v3.0.3,新版本丰富了不少功能,欢迎评论指出。

二、MQ 源码流程介绍

  • rtthread 中 ipc 都继承自 rt_object,都以链表的形式呈现。
  • mq 的管理结构体为 rt_messagequeue,msg_pool 即为分配的队列池。
  • 在后面的内容中会了解到实际上有两个链表:rt_messagequeue 中 msg_queue_tail 和 msg_queue_tail表示的使用中链表,以及 msg_pool 和 msg_queue_free表征的空闲队列链表。当需要发送消息时,从空闲链表取出一个节点然后放到使用中链表中;而在接收时,将节点重新放回空闲链表。
c 复制代码
struct rt_messagequeue
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    void                *msg_pool;                      /**< start address of message queue */

    rt_uint16_t          msg_size;                      /**< message size of each message */
    rt_uint16_t          max_msgs;                      /**< max number of messages */

    rt_uint16_t          entry;                         /**< index of messages in the queue */

    void                *msg_queue_head;                /**< list head */
    void                *msg_queue_tail;                /**< list tail */
    void                *msg_queue_free;                /**< pointer indicated the free node of queue */
};
typedef struct rt_messagequeue *rt_mq_t;

1、MQ 创建

  • mq 的创建位于 rt_mq_create(), 首先申请管理结构体 rt_messagequeue, 然后初始化 ipc object 为循环链表,该链表用于存储等待该队列的线程。根据需求的 msg_size 和 max_msgs 进行队列池申请,需要留意的是,这里需要额外申请链表指针占用的空间
  • 初始化空闲消息队列链表,也就是 rt_mq_message:
    将起始地址作为链表最后一个节点进行串联,free指针指向最后节点
c 复制代码
rt_mq_t rt_mq_create(const char *name,
                     rt_size_t   msg_size,
                     rt_size_t   max_msgs,
                     rt_uint8_t  flag)
{
    /* allocate object */
    mq = (rt_mq_t)rt_object_allocate(RT_Object_Class_MessageQueue, name);
    ...
    /* init ipc object */
    rt_ipc_object_init(&(mq->parent));
    ...
    /* allocate message pool */
    mq->msg_pool = RT_KERNEL_MALLOC((mq->msg_size + sizeof(struct rt_mq_message)) * mq->max_msgs);
    ...
    /* init message empty list */
    mq->msg_queue_free = RT_NULL;
    for (temp = 0; temp < mq->max_msgs; temp ++)
    {
        head = (struct rt_mq_message *)((rt_uint8_t *)mq->msg_pool +
                                        temp * (mq->msg_size + sizeof(struct rt_mq_message)));
        head->next = mq->msg_queue_free;
        mq->msg_queue_free = head;
    }
}

2、发送消息

  • 首先从空闲队列中获取 空闲的节点,更新相关指针,然后将 buf copy 到节点后边的地址中,这里需要注意,由于 rt_mq_message存储的是一个指针,即 4字节,因此 (msg + 1) 其实就是 ( (uint8_t*)msg + 4 )。另外,我们可以留意到,这里 copy 时并没有相关参数用于描述 buf中的有效字节数(v5版本才增加了相关描述),这将会导致一些问题,在 recv时会讲

  • 接着将会检查是否有线程在等待该 ipc,如果有,将该线程设置为 就绪状态,并进行调度,保证实时性能。

c 复制代码
rt_err_t rt_mq_send(rt_mq_t mq, void *buffer, rt_size_t size)
{
    /* get a free list, there must be an empty item */
    msg = (struct rt_mq_message *)mq->msg_queue_free;
    /* move free list pointer */
    mq->msg_queue_free = msg->next;
    ...
    /* copy buffer */
    rt_memcpy(msg + 1, buffer, size);
    ...
    /* resume suspended thread */
    if (!rt_list_isempty(&mq->parent.suspend_thread))
    {
        rt_ipc_list_resume(&(mq->parent.suspend_thread));

        /* enable interrupt */
        rt_hw_interrupt_enable(temp);

        rt_schedule();

        return RT_EOK;
    }
    ...
}

3、接收消息

  • 当设置了超时时,在没有消息的情况下,会将当前线程设置为 suspend 状态,绑定定时器并开启,然后开启调度,直到有消息或者超时。
  • 当有消息时,从链表中获取头指针,并将数据 copy 到 buf,注意到没有?
    size > mq->msg_size ? mq->msg_size : size,只是检查传入的 size是否超过 msg_size,因此如果你想当然的类似 socket 一样进行信息接收,例如 rt_mq_recv(mq->msg_size),那么当我先 send 12345,再 send 67,第二次接收到的将是 67345,也就是粘连历史信息。关于这个问题,rtt在 v5版本才增加了相关描述,在此之前,需要用户自己通过结构体传输相关字节,但这样存在操作数据量大的问题;或者使用不那么优雅的解决方法。
c 复制代码
rt_err_t rt_mq_recv(rt_mq_t    mq,
                    void      *buffer,
                    rt_size_t  size,
                    rt_int32_t timeout)
{
    /* message queue is empty */
    while (mq->entry == 0)
    {
        /* suspend current thread */
        rt_ipc_list_suspend(&(mq->parent.suspend_thread),
                            thread,
                            mq->parent.parent.flag);

        /* re-schedule */
        rt_schedule();
    }

    /* get message from queue */
    msg = (struct rt_mq_message *)mq->msg_queue_head;
    /* move message queue head */
    mq->msg_queue_head = msg->next;

    /* copy message */
    rt_memcpy(buffer, msg + 1, size > mq->msg_size ? mq->msg_size : size);

    /* put message to free list */
    msg->next = (struct rt_mq_message *)mq->msg_queue_free;
    mq->msg_queue_free = msg;
}

三、总结

  • 该版本的 rtt 消息队列实现非常简单,基本只是链表操作,关于消息队列没有记录发送数据大小的问题,在 v5版本也更新了相关内容,前面版本的使用则需要留意使用。
相关推荐
m0_531237171 小时前
C语言-函数练习2
c语言·开发语言
小付同学呀1 小时前
C语言学习(四)——C语言变量、常量
c语言·开发语言
艾莉丝努力练剑2 小时前
【Linux:文件】进程间通信
linux·运维·服务器·c语言·网络·c++·人工智能
小野嵌入式2 小时前
3小时精通嵌入式串口通信!从零玩转ESP32+Modbus+OTA(1)
c语言·单片机·嵌入式硬件·mcu·物联网
枫叶丹43 小时前
【Qt开发】Qt界面优化(五)-> Qt样式表(QSS) 子控件选择器
c语言·开发语言·数据库·c++·qt
Hello_Embed4 小时前
Modbus 传感器开发:从寄存器规划到点表设计
笔记·stm32·单片机·学习·modbus
Once_day4 小时前
GCC编译(4)构造和析构函数
c语言·c++·编译和链接
StandbyTime4 小时前
C语言学习-菜鸟教程C经典100例-练习76
c语言
StandbyTime4 小时前
C语言学习-菜鸟教程C经典100例-练习77
c语言