rt-thread消息队列学习笔记

rt-thread消息队列

定义

消息队列定义

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 */

    rt_list_t            suspend_sender_thread;         /**< sender thread suspended on this message queue */
};
typedef struct rt_messagequeue *rt_mq_t;

消息头,就是单向链表

struct rt_mq_message
{
    struct rt_mq_message *next;
};

rt_mq_create函数

参数:名字,消息大小,最多消息数max_msgs,flag

flag只支持按优先级或先进先出两种。在函数体中首先定义消息队列指针,父类为rt_ipc_object类型。分配消息队列内存,爷爷类(rt_object)flag赋值为传入参数,调用父类初始化(其实就是初始化ipc对象链表为空,链表指向挂起线程,通过这个链表,可以找到对应的阻塞线程的控制块,从而能恢复线程)。传入参数赋值给消息队列成员,根据这两个参数,分配内存空间,内存大小为消息大小*消息数。然后使用循环,将内存池里的各个块以消息链表的形式串起来。

rt_mq_t rt_mq_create(const char *name,
                     rt_size_t   msg_size,
                     rt_size_t   max_msgs,
                     rt_uint8_t  flag)
{
    struct rt_messagequeue *mq;//消息队列控制块
    struct rt_mq_message *head;//单向链表
    rt_base_t temp;

    RT_ASSERT((flag == RT_IPC_FLAG_FIFO) || (flag == RT_IPC_FLAG_PRIO));

    RT_DEBUG_NOT_IN_INTERRUPT;

    /* allocate object */
    mq = (rt_mq_t)rt_object_allocate(RT_Object_Class_MessageQueue, name);
    if (mq == RT_NULL)
        return mq;

    /* set parent */
    mq->parent.parent.flag = flag;

    /* initialize ipc object */
    _ipc_object_init(&(mq->parent));

    /* initialize message queue */

    /* get correct message size */
    mq->msg_size = RT_ALIGN(msg_size, RT_ALIGN_SIZE);
    mq->max_msgs = max_msgs;

    /* allocate message pool 分配内存池 */
    mq->msg_pool = RT_KERNEL_MALLOC((mq->msg_size + sizeof(struct rt_mq_message)) * mq->max_msgs);
    if (mq->msg_pool == RT_NULL)
    {
        rt_object_delete(&(mq->parent.parent));

        return RT_NULL;
    }

    /* initialize message list 消息头和消息尾初始化为空,暂不使用*/
    mq->msg_queue_head = RT_NULL;
    mq->msg_queue_tail = RT_NULL;

    /* initialize message empty list 将内存池各个块串起来行程单链表,最终让控制块mq的空闲消息指针指向最后1块*/
    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 = (struct rt_mq_message *)mq->msg_queue_free;
        mq->msg_queue_free = head;
    }

    /* the initial entry is zero 消息个数初始化为0 */
    mq->entry = 0;

    /* initialize an additional list of sender suspend thread 控制块的链表初始化为单节点*/
    rt_list_init(&(mq->suspend_sender_thread));

    return mq;
}
RTM_EXPORT(rt_mq_create);

初始化中申请了内存池,将内存池划分为很多个小块,每个小块均由消息头+消息内容组成。

在这里插入图片描述

循环执行:

在这里插入图片描述

在循环中,形成链表:

在这里插入图片描述

rt_mq_creat执行完成后,消息队列对象mq的msg_queue_free成员,表示了内存池最后1块的地址,认为最后1块是空闲消息 ,内存池的所有块串起来形成单向链表。最后1块指向倒数第2块。创建后,形成了空闲链表,控制块中的空闲变量msg_queue_free表示空闲链表的表头,为内存池中的最后一个内存块。

rt_mq_delete函数

消息队列删除主要做的事情为:1.恢复由于访问队列而阻塞的线程;2.释放内存池;3.删除object对象

rt_mq_send函数

线程或者中断服务程序都可以给消息队列发送消息。send函数嵌套send wait,直接调用,只是超时时间设置为0。

rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size)
{
    return rt_mq_send_wait(mq, buffer, size, 0);
}
rt_err_t rt_mq_send_wait(rt_mq_t     mq,
                         const void *buffer,
                         rt_size_t   size,
                         rt_int32_t  timeout)
{
    rt_base_t level;
    struct rt_mq_message *msg;//单向链表
    rt_uint32_t tick_delta;
    struct rt_thread *thread;

    /* parameter check */
    RT_ASSERT(mq != RT_NULL);
    RT_ASSERT(rt_object_get_type(&mq->parent.parent) == RT_Object_Class_MessageQueue);
    RT_ASSERT(buffer != RT_NULL);
    RT_ASSERT(size != 0);

    /* current context checking */
    RT_DEBUG_SCHEDULER_AVAILABLE(timeout != 0);

    /* greater than one message size 如果发送的消息超过创建时设置的消息大小,则直接退出*/
    if (size > mq->msg_size)
        return -RT_ERROR;

    /* initialize delta tick */
    tick_delta = 0;
    /* get current thread 获取当前线程(消息发送方)*/
    thread = rt_thread_self();

    RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mq->parent.parent)));

    /* disable interrupt */
    level = rt_hw_interrupt_disable();

    /* get a free list, there must be an empty item 根据消息控制块中的free地址获取空闲消息内存块地址*/
    msg = (struct rt_mq_message *)mq->msg_queue_free;
    /* for non-blocking call */
    if (msg == RT_NULL && timeout == 0)
    {
        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        return -RT_EFULL;
    }

    /* message queue is full 循环判断:如果没有空闲消息内存块则直接退出*/
    while ((msg = (struct rt_mq_message *)mq->msg_queue_free) == RT_NULL)
    {
        /* reset error number in thread */
        thread->error = RT_EOK;

        /* no waiting, return timeout */
        if (timeout == 0)
        {
            /* enable interrupt */
            rt_hw_interrupt_enable(level);

            return -RT_EFULL;
        }

        /* suspend current thread */
        _ipc_list_suspend(&(mq->suspend_sender_thread),
                            thread,
                            mq->parent.parent.flag);

        /* has waiting time, start thread timer */
        if (timeout > 0)
        {
            /* get the start tick of timer */
            tick_delta = rt_tick_get();

            RT_DEBUG_LOG(RT_DEBUG_IPC, ("mq_send_wait: start timer of thread:%s\n",
                                        thread->name));

            /* reset the timeout of thread timer and start it */
            rt_timer_control(&(thread->thread_timer),
                             RT_TIMER_CTRL_SET_TIME,
                             &timeout);
            rt_timer_start(&(thread->thread_timer));
        }

        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        /* re-schedule */
        rt_schedule();

        /* resume from suspend state */
        if (thread->error != RT_EOK)
        {
            /* return error */
            return thread->error;
        }

        /* disable interrupt */
        level = rt_hw_interrupt_disable();

        /* if it's not waiting forever and then re-calculate timeout tick */
        if (timeout > 0)
        {
            tick_delta = rt_tick_get() - tick_delta;
            timeout -= tick_delta;
            if (timeout < 0)
                timeout = 0;
        }
    }

    /* move free list pointer 创建的时候,msg->next是倒数第2个内存块*/
    mq->msg_queue_free = msg->next;

    /* enable interrupt */
    rt_hw_interrupt_enable(level);

    /* the msg is the new tailer of list, the next shall be NULL */
    msg->next = RT_NULL;
    /* copy buffer 将线程发送的数据拷贝到内存块地址+1的地方,因为第一个地址用来存链表指针*/
    rt_memcpy(msg + 1, buffer, size);

    /* disable interrupt */
    level = rt_hw_interrupt_disable();
    /* link msg to message queue */
    if (mq->msg_queue_tail != RT_NULL)
    {
        /* if the tail exists 第一次创建后,尾部是为空的。如果有尾部的情况下,就将尾部串上当前这个已使用的消息块*/
        ((struct rt_mq_message *)mq->msg_queue_tail)->next = msg;
    }

    /* set new tail */
    mq->msg_queue_tail = msg;//如果没有尾部,现在就让当前这个已使用的消息块作为尾部
    /* if the head is empty, set head */
    if (mq->msg_queue_head == RT_NULL)//如果没有头部,也让当前这个已使用的消息块作为头部
        mq->msg_queue_head = msg;

    if(mq->entry < RT_MQ_ENTRY_MAX)
    {
        /* increase message entry */
        mq->entry ++;//消息个数++
    }
    else
    {
        rt_hw_interrupt_enable(level); /* enable interrupt */
        return -RT_EFULL; /* value overflowed 消息满,溢出返回错误*/
    }

    /* resume suspended thread */
    if (!rt_list_isempty(&mq->parent.suspend_thread))
    {
        _ipc_list_resume(&(mq->parent.suspend_thread));

        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        rt_schedule();

        return RT_EOK;
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(level);

    return RT_EOK;
}
RTM_EXPORT(rt_mq_send_wait)

使用消息队列发送函数,会将最后一个内存块作为首先使用,然后消息控制块mq的msg_queue_head成员变量会永久记录首先使用的这个内存块,第一次调用头部和尾部都是最后一个内存块。如下所示,发送第一个消息执行到最后,这一个链表已经断开了,内存池最后1块自成一派形成一个新的链表,只是这个链表只有1个节点,相当于最后一个内存块填充消息之后,从空闲链表中剥离出来,形成了消息链表,原来的空闲链表节点减少1个。

在这里插入图片描述

第二次发送消息的时候,过程如下。消息链表逐渐壮大,空闲链表逐渐萎缩。

在这里插入图片描述

在这里插入图片描述

发送时,消息依次插到消息链表的尾部。在rtthread中支持加急,使用rt_mq_urgent函数,可将消息插到消息链表头部。

接下来分析,发送消息对于发送方有何影响?

当空闲链表用完了,程序会在while ((msg = (struct rt_mq_message *)mq->msg_queue_free) == RT_NULL)进行循环,由于传参时timeput为0,因此直接退出循环,返回错误。如果在其他地方直接调用这个底层函数并且超时时间大于0时,遇到空闲链表用完的情况则会启动线程定时器,线程阻塞,一会儿再看链表是否空出来。

如果空闲链表没有用完,则会直接跳过这个while循环,函数末尾判断ipc挂起链表是否为空,即如果当前有线程因为访问队列而进入阻塞,现在 有消息了则可以将该线程从阻塞中恢复,执行任务调度。

在发送消息操作的时候,为了保护数据,当且仅当空闲消息链表上有可用的空闲消息块时,发送者才能成功发送消息;当空闲消息链表上无可用消息块,说明消息队列已满, 此时,发送消息的的线程或者中断程序会收到一个错误码(-RT_EFULL),发送消息并不 带有阻塞机制的,因为发送消息的环境可能是在中断中,不允许有阻塞的情况。

rt_mq_recv函数

当消息队列中有消息时,接收线程才能接收到消息,接收消息是有阻塞机制的,用户 可以自定义等待时间,RT-Thread 的接收消息过程是:接收一个消息后消息队列的头链表 消息被转移到了空闲消息链表中。

rt_err_t rt_mq_recv(rt_mq_t    mq,
                    void      *buffer,
                    rt_size_t  size,
                    rt_int32_t timeout)
{
    struct rt_thread *thread;
    rt_base_t level;
    struct rt_mq_message *msg;//单向链表
    rt_uint32_t tick_delta;

    /* parameter check */
    RT_ASSERT(mq != RT_NULL);
    RT_ASSERT(rt_object_get_type(&mq->parent.parent) == RT_Object_Class_MessageQueue);
    RT_ASSERT(buffer != RT_NULL);
    RT_ASSERT(size != 0);

    /* current context checking */
    RT_DEBUG_SCHEDULER_AVAILABLE(timeout != 0);

    /* initialize delta tick */
    tick_delta = 0;
    /* get current thread */
    thread = rt_thread_self();//消息接收方
    RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mq->parent.parent)));

    /* disable interrupt */
    level = rt_hw_interrupt_disable();

    /* for non-blocking call */
    if (mq->entry == 0 && timeout == 0)//如果消息个数为0并且不允许等待则直接返回超时
    {
        rt_hw_interrupt_enable(level);

        return -RT_ETIMEOUT;
    }

    /* message queue is empty */
    while (mq->entry == 0)//消息个数等于0,但是允许等待的情况
    {
        /* reset error number in thread */
        thread->error = RT_EOK;//接收方error重置为OK

        /* no waiting, return timeout */
        if (timeout == 0)//如果等待时间为0则返回超时
        {
            /* enable interrupt */
            rt_hw_interrupt_enable(level);

            thread->error = -RT_ETIMEOUT;

            return -RT_ETIMEOUT;
        }

        /* suspend current thread 消息个数为0,且未超时的情况下,进行接收方挂起*/
        _ipc_list_suspend(&(mq->parent.suspend_thread),
                            thread,
                            mq->parent.parent.flag);

        /* has waiting time, start thread timer 重新设置线程的定时器*/
        if (timeout > 0)
        {
            /* get the start tick of timer */
            tick_delta = rt_tick_get();

            RT_DEBUG_LOG(RT_DEBUG_IPC, ("set thread:%s to timer list\n",
                                        thread->name));

            /* reset the timeout of thread timer and start it */
            rt_timer_control(&(thread->thread_timer),
                             RT_TIMER_CTRL_SET_TIME,
                             &timeout);
            rt_timer_start(&(thread->thread_timer));
        }

        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        /* re-schedule */
        rt_schedule();

        /* recv message */
        if (thread->error != RT_EOK)
        {
            /* return error */
            return thread->error;
        }

        /* disable interrupt */
        level = rt_hw_interrupt_disable();

        /* if it's not waiting forever and then re-calculate timeout tick */
        if (timeout > 0)
        {
            tick_delta = rt_tick_get() - tick_delta;
            timeout -= tick_delta;
            if (timeout < 0)
                timeout = 0;
        }
    }

    /* get message from queue 消息个数大于0的情况,首先获取消息链表头部节点*/
    msg = (struct rt_mq_message *)mq->msg_queue_head;

    /* move message queue head 让下一个节点作为新的头部*/
    mq->msg_queue_head = msg->next;
    /* reach queue tail, set to NULL 如果消息链表头部和尾部相等,说明消息链表空了*/
    if (mq->msg_queue_tail == msg)
        mq->msg_queue_tail = RT_NULL;

    /* decrease message entry */
    if(mq->entry > 0)
    {
        mq->entry --;//消息个数减1
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(level);

    /* copy message 将消息的内容拷贝给buffer*/
    rt_memcpy(buffer, msg + 1, size > mq->msg_size ? mq->msg_size : size);

    /* disable interrupt */
    level = rt_hw_interrupt_disable();
    /* put message to free list */
    msg->next = (struct rt_mq_message *)mq->msg_queue_free;
    mq->msg_queue_free = msg;

    /* resume suspended thread */
    if (!rt_list_isempty(&(mq->suspend_sender_thread)))
    {
        _ipc_list_resume(&(mq->suspend_sender_thread));

        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mq->parent.parent)));

        rt_schedule();

        return RT_EOK;
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(level);

    RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mq->parent.parent)));

    return RT_EOK;
}
RTM_EXPORT(rt_mq_recv);

继续以发送了两个消息的情况为例。执行接收之后,消息链表头部节点的信息得以提取,然后消息链表头部节点重新作为空闲链表头部,并从消息链表中剥离,下一次接收时提取新的消息链表头部里的内容。由此可见,内存池中靠后的内存块使用率更高一些,内存池靠前的部分有可能不会用到。

在这里插入图片描述

假设有一个线程 A 对某个队列进行读操作的时候(也就是我们所说的出队),发现它没有消息,那么此时线程 A 有 3 个选择:第一个选择,线程 A 扭头就走,既然队列没有消息, 那我也不等了,干其它事情去,这样子线程 A 不会进入阻塞态;第二个选择,线程 A 还是 在这里等等吧,可能过一会队列就有消息,此时线程 A 会进入阻塞状态,在等待着消息的 道来,而线程 A 的等待时间就由我们自己定义,比如设置 1000 个 tick 的等待,在这 1000 个 tick 到来之前线程 A 都是处于阻塞态,当阻塞的这段时间线程 A 等到了队列的消息,那么线程 A 就会从阻塞态变成就绪态,如果此时线程 A 比当前运行的线程优先级还高,那么, 线程 A 就会得到消息并且运行;假如 1000 个tick 都过去了,队列还没消息,那线程 A 就不等了,从阻塞态中唤醒,返回一个没等到消息的错误代码,然后继续执行线程 A 的其他代码;第三个选择,线程 A死等,不等到消息就不走了,这样子线程 A 就会进入阻塞态, 直到完成读取队列的消息。

/* 队列读取(接收),等待时间为一直等待 */
uwRet = rt_mq_recv(test_mq, /* 读取(接收)队列的 ID(句柄) */ 
 					&r_queue, /* 读取(接收)的数据保存位置 */ 
 					sizeof(r_queue), /* 读取(接收)的数据的长度 */ 
 					RT_WAITING_FOREVER); /* 等待时间:一直等 */ 
 if (RT_EOK == uwRet){
 	rt_kprintf("本次接收到的数据是:%d\n",r_queue);
 } else{
 	rt_kprintf("数据接收出错,错误代码: 0x%lx\n",uwRet);
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值