RT_thread(七)线程间通信之邮箱


线程间通信

在裸机编程中,通信是使用全局变量,一些程序可能会因为一段操作改变全局变量的值,而另一些程序对这些全局变量进行读取,某些值会导致一些程序进行运行,从而达到多功能之间的协作。

邮箱

邮箱服务是实时操作系统中一种典型的线程间通信方法,比如线程1启动后,线程2要随之启动,就可以用邮箱传递信息

重点

1.create-delete 中有分配内存,释放内存,init-detach 中没有。
与创建邮箱不同的是,静态邮箱对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中,其余的初始化工作与创建邮箱时相同。
2.删除/脱离邮箱,要把其中的等待发送到邮箱线程和等待发送线程返回失败。
3.邮件大小4个字节,缓冲区的字节数是 N,那么邮箱容量应该是 N/4。
4.关中断后的程序有多个路径,记得每个路径最后都要使能中断。

邮箱的工作机制

邮箱用于线程间的通信(异步通信),使用邮箱的优点是时间短,高效。发送的邮件是4个字节,也就是一个指针的大小,邮箱也就是一个信息交换的存储点。
线程或中断服务例程把一则4字节长度(典型的是一个指针)的邮件发送到邮箱中。而一个或多个线程可以从邮箱中接收这些邮件进行处理。

1邮箱控制块

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.邮箱的初始化

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);//邮箱控制块是否存在

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

    /* 确定对象标志位 */
    mb->parent.parent.flag = flag;

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

    /* 邮箱初始化 */
    mb->msg_pool   = msgpool;
    mb->size       = size;
    mb->entry      = 0;
    mb->in_offset  = 0;
    mb->out_offset = 0;

    /* 初始化(当邮箱满了,等待发送信件的挂起线程)的链表 */
    rt_list_init(&(mb->suspend_sender_thread));

    return RT_EOK;
}
RTM_EXPORT(rt_mb_init);

3.邮箱的脱离

rt_err_t rt_mb_detach(rt_mailbox_t mb)
{
    /*核查是否为邮箱 */
    RT_ASSERT(mb != RT_NULL);
    RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
    RT_ASSERT(rt_object_is_systemobject(&mb->parent.parent));

    /* 因为要删除邮箱,恢复所有挂起的线程,这些线程都未 接收 邮件则返回失败 */
    rt_ipc_list_resume_all(&(mb->parent.suspend_thread));
    /*因为要删除邮箱,恢复所有挂起的线程,这些线程都未 发送 邮件则返回失败*/
    rt_ipc_list_resume_all(&(mb->suspend_sender_thread));

    /* 邮箱对象脱离对象链表 */
    rt_object_detach(&(mb->parent.parent));

    return RT_EOK;
}
RTM_EXPORT(rt_mb_detach);

4.邮箱的创建

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;//分配内存不能在关中断中运行,运行时间过久

    /* 对象容器中建立邮箱对象 */
    mb = (rt_mailbox_t)rt_object_allocate(RT_Object_Class_MailBox, name);
    if (mb == RT_NULL)
        return mb;

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

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

    /* 初始化邮箱 */
    mb->size     = size;//设置内存大小即邮件数量最大值
    mb->msg_pool = RT_KERNEL_MALLOC(mb->size * sizeof(rt_uint32_t));//分配内存,返回内存起始地址
    if (mb->msg_pool == RT_NULL)
    {
        /* 没有分配到内存就删除邮箱 */
        rt_object_delete(&(mb->parent.parent));

        return RT_NULL;
    }
    mb->entry      = 0;//当前邮箱中存放的消息条数为0
    mb->in_offset  = 0;//消息存入的偏移量为0
    mb->out_offset = 0;//消息取出的偏移量为0

    /* 初始化等待发送邮件的线程链表 */
    rt_list_init(&(mb->suspend_sender_thread));

    return mb;
}
RTM_EXPORT(rt_mb_create);

5.邮箱的删除

rt_err_t rt_mb_delete(rt_mailbox_t mb)
{
    RT_DEBUG_NOT_IN_INTERRUPT;

    /*核查是否为邮箱 */
    RT_ASSERT(mb != RT_NULL);
    RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);
    RT_ASSERT(rt_object_is_systemobject(&mb->parent.parent) == RT_FALSE);

    /* 唤醒所有挂起的线程,因为要删除邮箱,这些线程都返回失败 */
    rt_ipc_list_resume_all(&(mb->parent.suspend_thread));

    /* 醒所有挂起要发送给邮箱的线程,因为要删除邮箱,这些线程都返回失败 */
    rt_ipc_list_resume_all(&(mb->suspend_sender_thread));

    /* 释放内存 */
    RT_KERNEL_FREE(mb->msg_pool);

    /* 删除邮箱对象 */
    rt_object_delete(&(mb->parent.parent));

    return RT_EOK;
}
RTM_EXPORT(rt_mb_delete);

6.发送邮件(含延时)

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;

    /* 核查类型 */
    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)));

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

    /* 非阻塞呼叫,无内存或无时延*/
    if (mb->entry == mb->size && timeout == 0)//邮箱满了并且线程不等待,返回满了错误码
    {
        rt_hw_interrupt_enable(temp);

        return -RT_EFULL;
    }

    /* 邮箱满了的操作 */
    while (mb->entry == mb->size)
    {
        /* 重置线程中的错误号 */
        thread->error = RT_EOK;

        /* 线程不等待,直接退出返回,错误码返回满了 */
        if (timeout == 0)
        {
            /* 开中断 */
            rt_hw_interrupt_enable(temp);

            return -RT_EFULL;
        }

        RT_DEBUG_IN_THREAD_CONTEXT;//确保在进程上文运行
        /* 挂起当前线程 */
        rt_ipc_list_suspend(&(mb->suspend_sender_thread),
                            thread,
                            mb->parent.parent.flag);

        /* 有等待时间,启动线程计时器 */
        if (timeout > 0)
        {
            /*获取计时器的数值 */
            tick_delta = rt_tick_get();

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

            
            rt_timer_control(&(thread->thread_timer),
                             RT_TIMER_CTRL_SET_TIME,
                             &timeout);//设置线程的定时器
            rt_timer_start(&(thread->thread_timer));//启动定时器
        } 

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

        /* 打开调度器 */
        rt_schedule();

        
        if (thread->error != RT_EOK)//错误码不为RT_EOK,即挂起线程失败
        { 
            /* return error */
            return thread->error;
        }

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

        /* 此判断在while循环中,判断经过的时间是否大于timeout,大于则timeout归零,回到timeout==0的判断中*/
        if (timeout > 0)
        {
            tick_delta = rt_tick_get() - tick_delta;
            timeout -= tick_delta;
            if (timeout < 0)
                timeout = 0;
        }
    }
    
     //上方while循环为邮箱满的操作,下方为未满操作
     
    /* 将value的地址存入邮箱的第in_offset个位置 */
    mb->msg_pool[mb->in_offset] = value;
    ++ mb->in_offset;//存放的偏移量加一
    if (mb->in_offset >= mb->size)//存放的偏移量大于邮箱邮件的最大数,也就是存满了
        mb->in_offset = 0;
    mb->entry ++;//当前邮箱的邮件数加一

    /*判断是否有挂起线程,有则恢复线程 */
    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;
    }

    /* 使能中断 */
    rt_hw_interrupt_enable(temp);

    return RT_EOK;
}
RTM_EXPORT(rt_mb_send_wait);

7.发送邮件(不含延时)

rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value)
{
    return rt_mb_send_wait(mb, value, 0);//延时为0的邮件发送函数
}
RTM_EXPORT(rt_mb_send);

8.接收邮件

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;

    /*核查是否邮箱 */
    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_trytake_hook, (&(mb->parent.parent)));

    /* 关中断1*/
    temp = rt_hw_interrupt_disable();

    /* 邮箱中没有信件并无延时(没有延时表示不会再收到信件) */
    if (mb->entry == 0 && timeout == 0)
    {
        rt_hw_interrupt_enable(temp);

        return -RT_ETIMEOUT;
    }

    /* 邮箱为空的运行 */
    while (mb->entry == 0)
    {
        /* 重置线程中的错误号*/
        thread->error = RT_EOK;

        /* 不会再收到信件,返回线程错误码 */
        if (timeout == 0)
        {
            /* 开中断1 */
            rt_hw_interrupt_enable(temp);

            thread->error = -RT_ETIMEOUT;

            return -RT_ETIMEOUT;
        }

        /* 令当前线程不能使用中断服务 */
        RT_DEBUG_IN_THREAD_CONTEXT;//确保在进程上文运行
        /* 没有邮件接收,挂起当前线程 */
        rt_ipc_list_suspend(&(mb->parent.suspend_thread),
                            thread,
                            mb->parent.parent.flag);

        /* 若设置了等待时间,设置线程定时器 */
        if (timeout > 0)
        {
            /*获得当前时钟 */
            tick_delta = rt_tick_get();

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

            /* 重置线程计时器的超时并启动它 */
            rt_timer_control(&(thread->thread_timer),
                             RT_TIMER_CTRL_SET_TIME,
                             &timeout);//设置线程定时器
            rt_timer_start(&(thread->thread_timer));//启动线程定时器
        }

        /* 开中断1 */
        rt_hw_interrupt_enable(temp);

        /* 启动调度器 */
        rt_schedule();

        /* 线程状态检测,挂起线程没成功就返回错误码 */
        if (thread->error != RT_EOK)
        {
            /* 不为EOK返回当前状态*/
            return thread->error;
        }

        /* 关中断2 */
        temp = rt_hw_interrupt_disable();

        /* 此判断在while循环中,判断经过的时间是否大于timeout,大于则timeout归零,回到timeout==0的判断中 */
        if (timeout > 0)
        {
            tick_delta = rt_tick_get() - tick_delta;
            timeout -= tick_delta;
            if (timeout < 0)
                timeout = 0;
        }
    }

    /* 取出邮箱信件 */
    *value = mb->msg_pool[mb->out_offset];

    /* 取出偏移量加一*/
    ++ mb->out_offset;
    if (mb->out_offset >= mb->size)//取出偏移量大于邮箱大小就回到邮箱起点(环形,首尾相连)
        mb->out_offset = 0;
    /*当前邮箱邮件数减一 */
    mb->entry --;

    /* 当前线程接收完邮箱,轮到下一个线程,存在下一个线程就唤醒 */
    if (!rt_list_isempty(&(mb->suspend_sender_thread)))
    {
        rt_ipc_list_resume(&(mb->suspend_sender_thread));

        /* 开中断2*/
        rt_hw_interrupt_enable(temp);
 /*设置线程获取内核对象钩子函数 该函数将设置一个钩子函数,当线程获取到内核对象后将调用该钩子函数。*/
        RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mb->parent.parent)));

        rt_schedule();

        return RT_EOK;
    }

    /* 开中断2 */
    rt_hw_interrupt_enable(temp);

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

    return RT_EOK;
}
RTM_EXPORT(rt_mb_recv);

9.对邮箱进行参数设置

rt_err_t rt_mb_control(rt_mailbox_t mb, int cmd, void *arg)
{
    rt_ubase_t level;

    /* 核查类型*/
    RT_ASSERT(mb != RT_NULL);
    RT_ASSERT(rt_object_get_type(&mb->parent.parent) == RT_Object_Class_MailBox);

    if (cmd == RT_IPC_CMD_RESET)
    {
        /* 关中断1 */
        level = rt_hw_interrupt_disable();

        /* 唤醒所有等待线程,使其返回失败 */
        rt_ipc_list_resume_all(&(mb->parent.suspend_thread));
       /* 唤醒所有等待发送给邮箱的线程,使其返回失败 */
        rt_ipc_list_resume_all(&(mb->suspend_sender_thread));

        /* 重置邮箱 */
        mb->entry      = 0;
        mb->in_offset  = 0;
        mb->out_offset = 0;

        /*开中断1 */
        rt_hw_interrupt_enable(level);

        rt_schedule();

        return RT_EOK;
    }

    return -RT_ERROR;
}
RTM_EXPORT(rt_mb_control);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值