rt-thread的IPC机制之消息队列源码分析

消息队列是另一种常用的线程间通讯方式,它能够接收来自线程的不固定长度的消息,并把消息缓存在自己的内存空间中。其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。而当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。消息队列是一种异步的通信方式。

rt-thread的IPC机制之消息队列源码分析_第1张图片

如上图所示,通过消息队列服务,线程或中断服务子程序可以将一条或多条消息放入消息队列。同样,一个或多个线程可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则(FIFO)。

RT-Thread的消息队列对象由多个元素组成,当消息队列被创建时,它就被分配了消息队列控制块:队列名,内存缓冲区,消息大小以及队列长度等。同时每个消息队列对象中包含着多个消息框,每个消息框可以存放一条消息。消息队列中的第一个和最后一个消息框被分别称为队首和队尾,对应于消息队列控制块中的msg queue head和msg queue tail;有些消息框中可能是空的,它们通过msg queue free线程一个空闲消息框链表。所有消息队列中的消息框总数即是消息队列的长度,这个长度可在消息队列创建时指定。


1 消息队列控制块

struct rt_messagequeue
{
    struct rt_ipc_object parent;   //IPC对象

    void                *msg_pool;    //消息池首

    rt_uint16_t          msg_size;     //每条消息的大小
    rt_uint16_t          max_msgs;     //消息池最大可容纳的消息条数

    rt_uint16_t          entry;        //当前消息队列中存在的消息条娄

    void                *msg_queue_head;      //队首
    void                *msg_queue_tail;       //队尾
    void                *msg_queue_free;      //指向空闲队列
};
typedef struct rt_messagequeue *rt_mq_t;
以上是消息队列控制块的定义,那么消息队列内部包含的每条消息元素又是如何定义的呢?且看如下:

struct rt_mq_message
{
    struct rt_mq_message *next;//指向下一条消息元素
};

可能大家会有个疑问,怎么消息元素没有消息的具体数据内容呢?

其实上面这个消息元素只能算作是消息头,就好比短信的标题一般,真正内部跟随在这个消息头后面,一直到下一个消息头之前的所有内存中的数据就是消息的真正内容.rt-thread采用静态队列的方式来实现消息队列的IPC功能的.消息头只是用来做索引功能,通过它可以索引到下一个消息头的位置.如果大家还是有些不明白,那么可以先看看消息队列是如何初始化的,就会明白为什么会有这么一个消息头了.

2 消息队列接口

2.1 初始化消息队列

/**
 * This function will initialize a message queue and put it under control of
 * resource management.
 *
 * @param mq the message object
 * @param name the name of message queue
 * @param msgpool the beginning address of buffer to save messages
 * @param msg_size the maximum size of message
 * @param pool_size the size of buffer to save messages
 * @param flag the flag of message queue
 *
 * @return the operation status, RT_EOK on successful
 */
rt_err_t rt_mq_init(rt_mq_t     mq,
                    const char *name,
                    void       *msgpool,//消息池首地址
                    rt_size_t   msg_size,//每条消息的大小
                    rt_size_t   pool_size,//消息池的总大小
                    rt_uint8_t  flag)
{
    struct rt_mq_message *head;
    register rt_base_t temp;

    /* parameter check */
    RT_ASSERT(mq != RT_NULL);

    /* init object */
    rt_object_init(&(mq->parent.parent), RT_Object_Class_MessageQueue, name);//初始化消息队列对应的内核对象

    /* set parent flag */
    mq->parent.parent.flag = flag;//设置标志

    /* init ipc object */
    rt_ipc_object_init(&(mq->parent));//初始化IPC对象

    /* set messasge pool */
    mq->msg_pool = msgpool;//设置消息池

    /* get correct message size */
    mq->msg_size = RT_ALIGN(msg_size, RT_ALIGN_SIZE);//设置对齐后的每条消息大小
    mq->max_msgs = pool_size / (mq->msg_size + sizeof(struct rt_mq_message));//计算消息池内总共可容纳多少条消息

    /* init message list */
    mq->msg_queue_head = RT_NULL;//队首
    mq->msg_queue_tail = RT_NULL;//队尾

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

    /* the initial entry is zero */
    mq->entry = 0;//消息队列内包含的消息条数为0

    return RT_EOK;
}

这里需要注意地是,消息队列会将消息池(即一片内存空间)做为静态队列来用,msg_queue_free指向位于最末尾的那个消息头(rt_mq_message 这个消息队列在地址上是从后往前的)

2.2 创建消息队列

/**
 * This function will create a message queue object from system resource
 *
 * @param name the name of message queue
 * @param msg_size the size of message
 * @param max_msgs the maximum number of message in queue
 * @param flag the flag of message queue
 *
 * @return the created message queue, RT_NULL on error happen
 */
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;
    register rt_base_t temp;

    RT_DEBUG_NOT_IN_INTERRUPT;//确保本函数不是在ISR中使用

    /* 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;//设置内核对象标志

    /* init ipc object */
    rt_ipc_object_init(&(mq->parent));//IPC对象初始化

    /* init 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_malloc((mq->msg_size + sizeof(struct rt_mq_message))* mq->max_msgs);//动态分配消息池
    if (mq->msg_pool == RT_NULL)
    {
        rt_mq_delete(mq);

        return RT_NULL;
    }

    /* init message list */
    mq->msg_queue_head = RT_NULL;
    mq->msg_queue_tail = RT_NULL;

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

    /* the initial entry is zero */
    mq->entry = 0;

    return mq;
}
创建消息队列与初始化消息队列类似,只不过一个是把一片已经存在的内存变成消息队列,一个是动态分配一片内存,然后再将其变成消息队列.

2.3 脱离消息队列

/**
 * This function will detach a message queue object from resource management
 *
 * @param mq the message queue object
 *
 * @return the operation status, RT_EOK on successful
 */
rt_err_t rt_mq_detach(rt_mq_t mq)
{
    /* parameter check */
    RT_ASSERT(mq != RT_NULL);

    /* resume all suspended thread */
    rt_ipc_list_resume_all(&mq->parent.suspend_thread);//还原所有挂起在消息队列中的线程

    /* detach message queue object */
    rt_object_detach(&(mq->parent.parent));//脱离消息队列的内核对象

    return RT_EOK;
}

2.4 删除消息队列

/**
 * This function will delete a message queue object and release the memory
 *
 * @param mq the message queue object
 *
 * @return the error code
 */
rt_err_t rt_mq_delete(rt_mq_t mq)
{
    RT_DEBUG_NOT_IN_INTERRUPT;

    /* parameter check */
    RT_ASSERT(mq != RT_NULL);

    /* resume all suspended thread */
    rt_ipc_list_resume_all(&(mq->parent.suspend_thread));//唤醒所有挂起的线程

#if defined(RT_USING_MODULE) && defined(RT_USING_SLAB)
    /* the mq object belongs to an application module */
    if (mq->parent.parent.flag & RT_OBJECT_FLAG_MODULE)
        rt_module_free(mq->parent.parent.module_id, mq->msg_pool);//卸载模块
    else
#endif

    /* free message queue pool */
    rt_free(mq->msg_pool);//释放消息池

    /* delete message queue object */
    rt_object_delete(&(mq->parent.parent));//删除消息队列的内核对象

    return RT_EOK;
}

2.5 发送消息

/**
 * This function will send a message to message queue object, if there are
 * threads suspended on message queue object, it will be waked up.
 *
 * @param mq the message queue object
 * @param buffer the message
 * @param size the size of buffer
 *
 * @return the error code
 */
rt_err_t rt_mq_send(rt_mq_t mq, void *buffer, rt_size_t size)
{
    register rt_ubase_t temp;
    struct rt_mq_message *msg;

    RT_ASSERT(mq != RT_NULL);
    RT_ASSERT(buffer != RT_NULL);
    RT_ASSERT(size != 0);

    /* greater than one message size */
    if (size > mq->msg_size)//如果发送的消息大小比消息队列允许每条消息的的大小,则直接返回错误
        return -RT_ERROR;

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

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();//关中断

    /* get a free list, there must be an empty item */
    msg = (struct rt_mq_message*)mq->msg_queue_free;//从空闲队列中获取一个空闲位置
    /* message queue is full */
    if (msg == RT_NULL)//如果消息队列已满
    {
        /* enable interrupt */
        rt_hw_interrupt_enable(temp);

        return -RT_EFULL;//返回消息队列已满错误
    }
    /* move free list pointer *///获得空闲位置成功
    mq->msg_queue_free = msg->next;//空闲指针指向下一条消息

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);//开中断

    /* the msg is the new tailer of list, the next shall be NULL */
    msg->next = RT_NULL;
    /* copy buffer */
    rt_memcpy(msg + 1, buffer, size);//保存消息内容,msg+1是指消息头后面的位置

    /* disable interrupt */
    temp = 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;

    /* increase message entry */
    mq->entry ++;//消息队列包含的消息条数加1

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

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);//开中断

    return RT_EOK;
}

2.6 发送紧急消息

/**
 * This function will send an urgent message to message queue object, which
 * means the message will be inserted to the head of message queue. If there
 * are threads suspended on message queue object, it will be waked up.
 *
 * @param mq the message queue object
 * @param buffer the message
 * @param size the size of buffer
 *
 * @return the error code
 */
rt_err_t rt_mq_urgent(rt_mq_t mq, void *buffer, rt_size_t size)
{
    register rt_ubase_t temp;
    struct rt_mq_message *msg;

    RT_ASSERT(mq != RT_NULL);
    RT_ASSERT(buffer != RT_NULL);
    RT_ASSERT(size != 0);

    /* greater than one message size */
    if (size > mq->msg_size)//如果消息大小超过限制
        return -RT_ERROR;

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

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();//关中断

    /* get a free list, there must be an empty item */
    msg = (struct rt_mq_message *)mq->msg_queue_free;//获取空闲位置
    /* message queue is full */
    if (msg == RT_NULL)
    {
        /* enable interrupt */
        rt_hw_interrupt_enable(temp);

        return -RT_EFULL;
    }
    /* move free list pointer */
    mq->msg_queue_free = msg->next;//空闲链表指向下一个位置

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);

    /* copy buffer */
    rt_memcpy(msg + 1, buffer, size);//保存消息

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

    /* link msg to the beginning of message queue */
    msg->next = mq->msg_queue_head;//将当前消息放到消息首之前
    mq->msg_queue_head = msg;

    /* if there is no tail */
    if (mq->msg_queue_tail == RT_NULL)//如果没有消息尾,则将消息尾也指向此消息
        mq->msg_queue_tail = msg;

    /* increase message entry */
    mq->entry ++;//消息条数加1

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

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);//开中断

    return RT_EOK;
}

2.7 接收消息

/**
 * This function will receive a message from message queue object, if there is
 * no message in message queue object, the thread shall wait for a specified
 * time.
 *
 * @param mq the message queue object
 * @param buffer the received message will be saved in
 * @param size the size of buffer
 * @param timeout the waiting time
 *
 * @return the error code
 */
rt_err_t rt_mq_recv(rt_mq_t    mq,
                    void      *buffer,
                    rt_size_t  size,
                    rt_int32_t timeout)
{
    struct rt_thread *thread;
    register rt_ubase_t temp;
    struct rt_mq_message *msg;
    rt_uint32_t tick_delta;

    RT_ASSERT(mq != RT_NULL);
    RT_ASSERT(buffer != RT_NULL);
    RT_ASSERT(size != 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 */
    temp = rt_hw_interrupt_disable();//关中断

    /* for non-blocking call */
    if (mq->entry == 0 && timeout == 0)//如果当前消息队列中没有消息,且当前等待时间参数为0,则直接返回错误
    {
        rt_hw_interrupt_enable(temp);

        return -RT_ETIMEOUT;
    }

    /* message queue is empty */
    while (mq->entry == 0)//一直等待到有消息到达
    {
        RT_DEBUG_NOT_IN_INTERRUPT;

        /* reset error number in thread */
        thread->error = RT_EOK;

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

            thread->error = -RT_ETIMEOUT;

            return -RT_ETIMEOUT;
        }

        /* suspend current thread */
        rt_ipc_list_suspend(&(mq->parent.suspend_thread),//如果时间参数不为0,则挂起当前线程
                            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(temp);

        /* re-schedule */
        rt_schedule();//重新调度

        /* recv message */
        if (thread->error != RT_EOK)//如果没有收到消息,则返回错误
        {
            /* return error */
            return thread->error;
        }

        /* disable interrupt */
        temp = 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 */
    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 */
    mq->entry --;//消息条数减1

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);

    /* copy message */
    rt_memcpy(buffer, msg + 1, size > mq->msg_size ? mq->msg_size : size);//取出消息内容

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();
    /* put message to free list */
    msg->next = (struct rt_mq_message *)mq->msg_queue_free;//更新空闲消息链表,将读出后的消息队列放到空闲链表
    mq->msg_queue_free = msg;
    /* enable interrupt */
    rt_hw_interrupt_enable(temp);

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

    return RT_EOK;
}

2.8 消息控制

/**
 * This function can get or set some extra attributions of a message queue
 * object.
 *
 * @param mq the message queue object
 * @param cmd the execution command
 * @param arg the execution argument
 *
 * @return the error code
 */
rt_err_t rt_mq_control(rt_mq_t mq, rt_uint8_t cmd, void *arg)
{
    rt_ubase_t level;
    struct rt_mq_message *msg;

    RT_ASSERT(mq != RT_NULL);

    if (cmd == RT_IPC_CMD_RESET)//消息重置
    {
        /* disable interrupt */
        level = rt_hw_interrupt_disable();

        /* resume all waiting thread */
        rt_ipc_list_resume_all(&mq->parent.suspend_thread);

        /* release all message in the queue */
        while (mq->msg_queue_head != RT_NULL)
        {
            /* get message from queue */
            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;

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

        /* clean entry */
        mq->entry = 0;

        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        rt_schedule();

        return RT_EOK;
    }

    return -RT_ERROR;
}

此接口只支持消息重置,它基本上与消息队列初始化类似,就不做解释了.

你可能感兴趣的:(rt-thread的IPC机制之消息队列源码分析)