RT - thread学习(四)邮箱

一、邮箱介绍

邮箱在rtthread操作系统中是一种常用的IPC通信方式, 邮箱可以在线程与线程之间,中断与线程之间进行消息的传递,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针, 即可以容纳一个处理函数指针).
以下是我对邮箱的一些简单见解
在这里插入图片描述
在这里插入图片描述
发送邮箱是将每个事件触发时暂不处理, 放在某个线程同一处理. 便于可以将事件触发函数和事件处理函数分开.
在这里插入图片描述
同时不同邮箱事件集中爆发时,能记住邮箱事件发生的前后顺序.

二、邮箱的运作机制

RT-Thread操作系统的邮箱对象由多个元素组成,当邮箱被创建时,它就被分配了邮箱控制块;邮箱名称;邮箱缓冲区起始地址;邮箱大小等。同时,每个邮箱对象中包含着多个邮件框,每个邮件框可以存放一封邮件,所有邮箱中的邮件框总数既是邮箱的大小,这个大小可在邮箱创建时指定。

三、邮箱控制块

在 RT-Thread 中,邮箱控制块是操作系统用于管理邮箱的一个数据结构,由结构体 struct rt_mailbox 表示。另外一种 C 表达方式 rt_mailbox_t,表示的是邮箱的句柄,在 C 语言中的实现是邮箱控制块的指针。邮箱控制块结构的详细定义请见以下代码:

struct rt_mailbox
{
    struct rt_ipc_object parent;
    rt_uint32_t* msg_pool;                /* 邮箱缓冲区的开始地址 */
    rt_uint16_t size;                     /* 邮箱缓冲区的大小     */
    rt_uint16_t entry;                    /* 邮箱中邮件的数目     */
    rt_uint16_t in_offset, out_offset;    /* 邮箱缓冲的进出指针   */
    rt_list_t suspend_sender_thread;      /* 发送线程的挂起等待队列 */
};

以上是整个邮箱控制块
msg_pool, 消息池的首地址, 将每个邮件发送的value保存在消息池中, (消息池放着所有的油件)
size , 消息池的总大小, 一个消息4字节(32位),
entry, 消息池中邮件的个数
in_offset, out_offset, 进出指针同于管理邮件的发送和接收在环中保存和取出的顺序.
suspend_sender_thread, 发送邮件时因为消息池满了而挂起的线程.

四、邮箱创建

RT-Thread 中邮箱的创建其实就是邮箱控制块的创建. 并将创建的邮箱控制块进行初值的赋值.

//静态创建方式  需要外面创建好消息控制块   rt_mailbox_t  rt_mailbox;
//需要外面创建好消息池  unsigned long msgpool[MSG_POOL_SIZE];
//#define MSG_POOL_SIZE 256;
rt_err_t rt_mb_init(rt_mailbox_t mb,			//创建好的消息控制块   rt_mailbox
                    const char  *name,			//邮箱名字(IPC)使用  APP_MAILBOX
                    void        *msgpool,		//消息池首地址  msgpool
                    rt_size_t    size,			//消息池大小   MSG_POOL_SIZE
                    rt_uint8_t   flag)			//FIFO方式.
{
    RT_ASSERT(mb != RT_NULL);
    /* initialize object */
    rt_object_init(&(mb->parent.parent), RT_Object_Class_MailBox, name);
    /* set parent flag */
    mb->parent.parent.flag = flag;
    /* initialize ipc object 初始化OBJECT链表指针*/
    rt_ipc_object_init(&(mb->parent));

    /* initialize mailbox 将函数传过来的值赋值到邮箱控制块中*/
    mb->msg_pool   = (rt_ubase_t *)msgpool;
    mb->size       = size;
    mb->entry      = 0;
    mb->in_offset  = 0;
    mb->out_offset = 0;
	
    /* initialize an additional list of sender suspend thread 
    初始化发送挂起链表指针*/
    rt_list_init(&(mb->suspend_sender_thread));
    return RT_EOK;
}
//动态创建邮箱控制块, 并通过函数返回值返回
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   申请ram空间给到mb*/
    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;
    /* initialize ipc object 初始化OBJECT链表指针*/
    rt_ipc_object_init(&(mb->parent));
    /* initialize mailbox 将消息池大小赋值进邮箱控制块中 */
    mb->size     = size;
    //申请消息池ram空间
    mb->msg_pool = (rt_ubase_t *)RT_KERNEL_MALLOC(mb->size * sizeof(rt_ubase_t));
    if (mb->msg_pool == RT_NULL)
    {
        /* delete mailbox object */
        rt_object_delete(&(mb->parent.parent));
        return RT_NULL;
    }
    mb->entry      = 0;
    mb->in_offset  = 0;
    mb->out_offset = 0;
    /* initialize an additional list of sender suspend thread 
    初始化发送挂起链表指针*/
    rt_list_init(&(mb->suspend_sender_thread));
    return mb;
}

五、邮件发送

线程或者中断服务程序都可以给邮箱发送邮件,非阻塞方式的邮件发送过程能够安全地应用于中断服务中,中断服务函数、定时器向线程发送消息的有效手段,而阻塞方式的邮件发送只能应用线程中,当发送邮件时,当且仅当邮箱中还没满邮件的时候才能进行发送,如果邮箱已满,可以根据用户设定的等待时间进行等待,当邮箱中的邮件被收取而空出空间来时,等待挂起的发送线程将被唤醒继续发送的过程,当等待时间到了,还没有完成发送邮件,或者未设定等待时间,此时发送邮件失败,发送邮件的线程或者中断程序会收到一个错误码(-RT_EFULL),线程发送邮件可以带阻塞,但在中断中不能采用任何带阻塞的方式发送邮件

rt_err_t rt_mb_send_wait(rt_mailbox_t mb,
                         rt_ubase_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);
    RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);


    tick_delta = 0;
    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)				//邮件满了且超时时间到了
    {
        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)									//超时时间到了  (唤醒线程后如果还mb->entry == mb->size)
        {
            /* enable interrupt */
            rt_hw_interrupt_enable(temp);					//返回错误
            return -RT_EFULL;
        }

        RT_DEBUG_IN_THREAD_CONTEXT;
        /* 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)									//还有时间没到
        {
            /* get the start tick of timer */
            tick_delta = rt_tick_get();						//获取当前时间
            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); 					//设置发送线程唤醒时间为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)						//当timeout唤醒线程后(会重新循环这个while)
        {
            /* 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;

    if(mb->entry < RT_MB_ENTRY_MAX)			//
    {
        /* increase message entry */
        mb->entry ++;						//信件个数++
    }
    else									//执行不到
    {
        rt_hw_interrupt_enable(temp); /* enable interrupt */
        return -RT_EFULL; /* value overflowed */
    }

    /* 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;
}
//没有超时时间, 满件后返回-RT_EFULL;	中断函数中只能使用这个发送信件
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value)
{
    return rt_mb_send_wait(mb, value, 0);
}

六、邮件接收

接收邮件时,根据邮箱控制块中的entry判断队列是否接收邮件,如果邮箱的邮件非空,那么可以根据out_offset找到最先发送的邮箱中的邮件进行接收,在接收时如果邮箱为空,如果用户设置了等待超时时间,系统会将当前线程挂起,当达到设置的超时时间,邮箱依然未收到邮件,那么线程将被唤醒并返回-RT_ETIMEOUT。如果邮箱中存在邮件,那么接收线程将复制邮箱中的4个字节邮件到接收线程中,通常来说,邮件收取过程中可能是阻塞的,这取决于邮箱中是否有邮件,以及收取邮件时设置的超时时间。

//timeout可以是永久接收的.
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_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);
    RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
    /* 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)								//如果邮件中邮件个数为0(没有信件)
    {
        /* reset error number in thread */
        thread->error = RT_EOK;
        /* no waiting, return timeout */
        if (timeout == 0)								//超时(用于判断恢复后的线程超时), 且邮件个数为0
        {
            /* enable interrupt */
            rt_hw_interrupt_enable(temp);
            thread->error = -RT_ETIMEOUT;
            return -RT_ETIMEOUT;						//返回错误
        }

        RT_DEBUG_IN_THREAD_CONTEXT;
        /* suspend current thread */
        rt_ipc_list_suspend(&(mb->parent.suspend_thread),
                            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();
            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)
        {
            tick_delta = rt_tick_get() - tick_delta;
            timeout -= tick_delta;
            if (timeout < 0)
                timeout = 0;
        }
    }

    /* fill ptr 消息池不空*/
    *value = mb->msg_pool[mb->out_offset];		//将消息中的数据给到Value (先进先出)
    /* increase output offset */
    ++ mb->out_offset;
    if (mb->out_offset >= mb->size)				//将消息池环起来
        mb->out_offset = 0;

    /* decrease message entry */
    if(mb->entry > 0)							//消息不为空
    {
        mb->entry --;							//消息个数减一
    }

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

参考资料

RT-Thread邮箱 CSDN : https://blog.csdn.net/qq_43460068/article/details/127040110

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值