rt-thread的IPC机制之邮箱源码分析

邮箱服务是实时操作系统中一种典型的任务间通信方法,通常开销比较低,效率较高,每一封邮件只能容纳固定的4字节内容(针对32位处理系统,刚好能够容纳一个指针).

如下图所示,线程或中断服务例程把一则4字节长度(典型的是一个指针)的邮件发送到邮箱中。而一个或多个线程可以从邮箱中接收这些邮件进行处理。

RT-Thread采用的邮箱通信机制有点类型传统意义上的管道,用于线程间通讯。它是线程,中断服务,定时器向线程发送消息的有效手段。邮箱与线程对象等之间是相互独立的。线程,中断服务和定时器都可以向邮箱发送消息,但是只有线程能够接收消息(因为当邮箱为空时,线程将有可能被挂起)。RT-Thread的邮箱中共可存放固定条数的邮件,邮箱容量在创建邮箱时设定,每个邮件大小为4字节,正好是一个指针的大小。当需要在线程间传递比较大的消息时,可以传递指向一个缓冲区的指针。当邮箱满时,线程等不再发送新邮件,返回-RT EFULL。当邮箱空时,将可能挂起正在试图接收邮件的线程,使其等待,当邮箱中有新邮件时,再唤醒等待在邮箱上的线程,使其能够接收新邮件并继续后续的处理。


1 邮箱控制块

/**
 * mailbox structure
 */
struct rt_mailbox
{
    struct rt_ipc_object parent; //继承自IPC对象

    rt_uint32_t         *msg_pool;//消息缓冲地址

    rt_uint16_t          size;   //可存放的消息最大条数

    rt_uint16_t          entry;    //当前邮箱中存放的消息条数
    rt_uint16_t          in_offset;   //消息存入的偏移位置
    rt_uint16_t          out_offset;  //消息取出时的偏移位置

    rt_list_t            suspend_sender_thread; //发送邮件的线程(当没有取走时,发送线程会被挂起)
};
typedef struct rt_mailbox *rt_mailbox_t;

2 邮箱相关接口源码分析

2.1 初始化

/**
 * This function will initialize a mailbox and put it under control of resource
 * management.
 *
 * @param mb the mailbox object
 * @param name the name of mailbox
 * @param msgpool the begin address of buffer to save received mail
 * @param size the size of mailbox
 * @param flag the flag of mailbox
 *
 * @return the operation status, RT_EOK on successful
 */
rt_err_t rt_mb_init(rt_mailbox_t mb,
                    const char  *name,
                    void        *msgpool,//消息缓冲地址
                    rt_size_t    size,//可容纳的消息条数
                    rt_uint8_t   flag)
{
    RT_ASSERT(mb != RT_NULL);

    /* init object */
    rt_object_init(&(mb->parent.parent), RT_Object_Class_MailBox, name);//初始化内核对象

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

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

    /* init mailbox */
    mb->msg_pool   = msgpool;//设置消息缓冲地址
    mb->size       = size;//设置最大可容纳消息条数
    mb->entry      = 0;//当前接收到的消息条数为0条
    mb->in_offset  = 0;//入口消息偏移位置为0
    mb->out_offset = 0;//出口消息偏移位置为0

    /* init an additional list of sender suspend thread */
    rt_list_init(&(mb->suspend_sender_thread));//初始化邮箱的发送线程挂起链表

    return RT_EOK;
}

2.2 创建邮箱

/**
 * This function will create a mailbox object from system resource
 *
 * @param name the name of mailbox
 * @param size the size of mailbox
 * @param flag the flag of mailbox
 *
 * @return the created mailbox, RT_NULL on error happen
 */
rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag)
{
    rt_mailbox_t mb;

    RT_DEBUG_NOT_IN_INTERRUPT;

    /* allocate object */
    mb = (rt_mailbox_t)rt_object_allocate(RT_Object_Class_MailBox, name);//动态分配邮箱内核对象
    if (mb == RT_NULL)
        return mb;

    /* set parent */
    mb->parent.parent.flag = flag;//设置内核标志

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

    /* init mailbox */
    mb->size     = size;//设置最大可容纳消息条数
    mb->msg_pool = rt_malloc(mb->size * sizeof(rt_uint32_t));//动态分配消息接收缓冲
    if (mb->msg_pool == RT_NULL)
    {
        /* delete mailbox object */
        rt_object_delete(&(mb->parent.parent));

        return RT_NULL;
    }
    mb->entry      = 0;//默认邮箱内的消息条数为0
    mb->in_offset  = 0;//入口偏移位置为0
    mb->out_offset = 0;//出口偏移位置为0

    /* init an additional list of sender suspend thread */
    rt_list_init(&(mb->suspend_sender_thread));//初始化邮箱的发送线程挂起链表

    return mb;
}

2.3 脱离邮箱

/**
 * This function will detach a mailbox from resource management
 *
 * @param mb the mailbox object
 *
 * @return the operation status, RT_EOK on successful
 */
rt_err_t rt_mb_detach(rt_mailbox_t mb)
{
    /* parameter check */
    RT_ASSERT(mb != RT_NULL);

    /* resume all suspended thread */
    rt_ipc_list_resume_all(&(mb->parent.suspend_thread));//唤醒所有挂起的接收线程
    /* also resume all mailbox private suspended thread */
    rt_ipc_list_resume_all(&(mb->suspend_sender_thread));//唤醒所有挂起的发送线程

    /* detach mailbox object */
    rt_object_detach(&(mb->parent.parent));//脱离邮箱对应的内核对象

    return RT_EOK;
}

2.4 删除邮箱

/**
 * This function will delete a mailbox object and release the memory
 *
 * @param mb the mailbox object
 *
 * @return the error code
 */
rt_err_t rt_mb_delete(rt_mailbox_t mb)
{
    RT_DEBUG_NOT_IN_INTERRUPT;

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

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

    /* also resume all mailbox private suspended thread */
    rt_ipc_list_resume_all(&(mb->suspend_sender_thread));//唤醒所有挂起的发送线程

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

    /* free mailbox pool */
    rt_free(mb->msg_pool);//删除接收缓冲

    /* delete mailbox object */
    rt_object_delete(&(mb->parent.parent));//删除邮箱对应的内核对象

    return RT_EOK;
}

2.5 接收邮件

/**
 * This function will receive a mail from mailbox object, if there is no mail
 * in mailbox object, the thread shall wait for a specified time.
 *
 * @param mb the mailbox object
 * @param value the received mail will be saved in
 * @param timeout the waiting time
 *
 * @return the error code
 */
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_uint32_t *value, rt_int32_t timeout)
{
    struct rt_thread *thread;
    register rt_ubase_t temp;
    rt_uint32_t tick_delta;

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

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

    RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mb->parent.parent)));

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

    /* for non-blocking call */
    if (mb->entry == 0 && timeout == 0)//如果当前邮箱中无信件且当前线程等待时间为0,则立即返回超时错误
    {
        rt_hw_interrupt_enable(temp);//开中断

        return -RT_ETIMEOUT;
    }

    /* mailbox is empty */
    while (mb->entry == 0)//如果当前邮箱无信件
    {
        /* reset error number in thread */
        thread->error = RT_EOK;//初始化error值为RT_EOK

        /* no waiting, return timeout */
        if (timeout == 0)//如果当前线程无等待时间,则立即返回超时错误
        {
            /* enable interrupt */
            rt_hw_interrupt_enable(temp);

            thread->error = -RT_ETIMEOUT;
            
            return -RT_ETIMEOUT;
        }

        RT_DEBUG_NOT_IN_INTERRUPT;//确保当前不是在ISR中使用
        /* suspend current thread */
        rt_ipc_list_suspend(&(mb->parent.suspend_thread),//挂起当前接收线程,(当前线程的timeout!=0)
                            thread,
                            mb->parent.parent.flag);

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

            RT_DEBUG_LOG(RT_DEBUG_IPC, ("mb_recv: 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(temp);//开中断

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

        /* resume from suspend state */
        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)//如果时间参数不是RT_WAITING_FOREVER
        {
            tick_delta = rt_tick_get() - tick_delta;//得到当前已经过了多长时间
            timeout -= tick_delta;//计算剩下等待时间
            if (timeout < 0)//如果所有等待时间都已耗尽,则直接就timeout设置为0,再进入下一循环
                timeout = 0;
        }
    }

    /* fill ptr */
    *value = mb->msg_pool[mb->out_offset];//保存接收到的邮件内容

    /* increase output offset */
    ++ mb->out_offset;//出品偏移加1
    if (mb->out_offset >= mb->size)//如果指向缓冲末尾,则改为指向首地址,由此得出邮箱的接收缓冲为一个ring buffer
        mb->out_offset = 0;
    /* decrease message entry */
    mb->entry --;//取出邮件后,邮件个数应减1

    /* resume suspended thread */
    if (!rt_list_isempty(&(mb->suspend_sender_thread)))//如果邮箱的挂起发送线程不为空
    {
        rt_ipc_list_resume(&(mb->suspend_sender_thread));//则唤醒第一个挂起的发送线程

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

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

        rt_schedule();//重新调度

        return RT_EOK;
    }

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

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

    return RT_EOK;
}
接收邮件函数会判断当前邮箱是否为空且时间参数为0, 如果是则直接返回,然后进行一个while循环,在这个while循环中的判断条件是邮箱为空,为什么要这么做呢?因此程序将在这个 while循环内一直等待邮件的到达,只有邮件到达才会跳出这个循环(如果等待超时则直接函数返回了),另一个是原因呆会再解释。进入while循环后,程序再次判断当前的等待时间参数是否为0,如果是则直接返回,为什么这里还要这样判断呢?这里是有一定的用意的,且接着往下看。接着挂起当前线程,再次判断时间参数是否大于0,这里主要是扫除timeout=RT_WAITING_FOREVER的情况,因为RT_WAITING_FOREVER宏定义为-1.在if语句内记录当前的时间点,然后设置一定时器并开启,接着重新调度。在重新调度后,系统可能切换到其它线程,假设一段时间内,系统再次切换回来,原因可能有多种,1 邮箱被脱离,此时当前线程thread->error=-RT_ERROR.2 定时器时间到达,但是邮件还未到达,此时thread->error=-RT_ETIMEOUT;3:邮件到达,本线程在发送邮件函数中被唤醒(注:发送邮件函数中只是唤醒第一条等待邮件的线程),此时,thread->error还是保持原值RT_EOK不变;4:其它原因假设一段时间后线程切换回来,此时error的值也一直保持原样RT_EOK不变.因此,在重新调度了线程之后,才会有一个if语句通过判断thread->error的值是否为RT_EOK来判断当前线程是否被发送邮件函数唤醒。如果不是,则直接返回错误。接下来,按原则上说,当前线程一定是被发送邮件函数唤醒,因此,当前一定会存在接收的邮件,但是接下来的几行代码却是再次判断时间参数大于0的情况下,计算还剩余多多时间,然后返回到while循环接着循环...等等,不是当前已经接收到了邮件么?那么为什么不直接取邮件,而是还要进行下一次循环?这不是浪费时间么?这里给出的答案是,第一,确实原则上这时应该是收到邮件才会执行到这,但是,如果真的来了邮件的话,判断的唯一标准是while循环的判断条件,即邮箱内的接收信件条数不能为空,或为空,则判断循环,或不为空,则自然不会进行到while循环中了。但如果这时发现原来邮箱还是为空,那么当前线程则应该继续等待了,此时就应该计算出下一次循环中需要等待的剩下时间,好让下一循环进行精确等待这段时间。

    接下来就是取出接收到的邮件,并更新邮箱的进出口偏移位置及邮箱中的邮件数减1,这样操作过后,不要忘记邮箱内可能还保留着因之前邮箱空间不中而挂起的发送线程,这个时候由于读取邮件操作,那么肯定是至少有一个空出的位置,那么是时候唤醒可能挂起的发送线程了(如果存在的话)。最后重新调度一下。

2.6 发送邮件

/**
 * This function will send a mail to mailbox object. If the mailbox is full,
 * current thread will be suspended until timeout.
 *
 * @param mb the mailbox object
 * @param value the mail
 * @param timeout the waiting time
 *
 * @return the error code
 */
rt_err_t rt_mb_send_wait(rt_mailbox_t mb,
                         rt_uint32_t  value,
                         rt_int32_t   timeout)
{
    struct rt_thread *thread;
    register rt_ubase_t temp;
    rt_uint32_t tick_delta;

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

    /* initialize delta tick */
    tick_delta = 0;
    /* get current thread */
    thread = rt_thread_self();//得到当前线程

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

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

    /* for non-blocking call */
    if (mb->entry == mb->size && timeout == 0)//如果邮箱满且无等待时间参数为0
    {
        rt_hw_interrupt_enable(temp);

        return -RT_EFULL;
    }

    /* mailbox is full */
    while (mb->entry == mb->size)//如果邮箱满
    {
        /* reset error number in thread */
        thread->error = RT_EOK;

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

            return -RT_EFULL;
        }

        RT_DEBUG_NOT_IN_INTERRUPT;//确保不是在ISR中使用本函数
        /* suspend current thread */
        rt_ipc_list_suspend(&(mb->suspend_sender_thread),//挂起当前发送线程
                            thread,
                            mb->parent.parent.flag);

        /* has waiting time, start thread timer */
        if (timeout > 0)//等待时间大于0
        {
            /* get the start tick of timer */
            tick_delta = rt_tick_get();//得到当前的tick

            RT_DEBUG_LOG(RT_DEBUG_IPC, ("mb_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(temp);//开中断

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

        /* resume from suspend state */
        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;
        }
    }

    /* set ptr */
    mb->msg_pool[mb->in_offset] = value;//开始存放邮件
    /* increase input offset */
    ++ mb->in_offset;
    if (mb->in_offset >= mb->size)
        mb->in_offset = 0;
    /* increase message entry */
    mb->entry ++;

    /* resume suspended thread */
    if (!rt_list_isempty(&mb->parent.suspend_thread))//唤醒接收线程
    {
        rt_ipc_list_resume(&(mb->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 can get or set some extra attributions of a mailbox object.
 *
 * @param mb the mailbox object
 * @param cmd the execution command
 * @param arg the execution argument
 *
 * @return the error code
 */
rt_err_t rt_mb_control(rt_mailbox_t mb, rt_uint8_t cmd, void *arg)
{
    rt_ubase_t level;
    RT_ASSERT(mb != RT_NULL);

    if (cmd == RT_IPC_CMD_RESET)//重围邮箱
    {
        /* disable interrupt */
        level = rt_hw_interrupt_disable();//关中断

        /* resume all waiting thread */
        rt_ipc_list_resume_all(&(mb->parent.suspend_thread));//唤醒所有挂起的接收线程
        /* also resume all mailbox private suspended thread */
        rt_ipc_list_resume_all(&(mb->suspend_sender_thread));//唤醒所有的发送线程

        /* re-init mailbox */
        mb->entry      = 0;
        mb->in_offset  = 0;
        mb->out_offset = 0;

        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        rt_schedule();

        return RT_EOK;
    }

    return -RT_ERROR;
}

邮箱控制控制函数目前只支持重围操作,此操作过程与初始化过程基本上类似.


4 小结

邮箱相关源码主要是在发送与接收上。发送时,由于当前邮箱可能空间已满,放不下要发送的邮件,此时,不得不挂起当前发送线程(如果存在时间参数的话),只要在接收函数读取出一条邮件时才会唤醒它。同理,如果当前邮箱为空,则接收函数会挂起当前的接收线程,直到有新的邮件到达(在发送函数中唤醒接收线程)或等待超时。


另外需要注意地是,rt-thread操作系统支持多个发送线程和多个接收线程,多个发送线程倒还好,倒是多个接收线程就不太好控制了,一般这种情况也不会用的,如果真的需要这种情况,那么多个接收线程就得好好控制了,因为,到底是哪个接收线程接收到邮件还不好说。OK,到此完!

你可能感兴趣的:(rt-thread,RT-Thread)