[RT_Thread]内核中线程间的同步与通信(事件集与邮箱的记录)

目录

 写在前面

本次记录

事件集

邮箱

接受邮件

发送邮件

邮件案例代码

小结


 写在前面

  • 本次写线程间的通信,熟悉与stm32裸机开发时,经常有很多问题,比如:
  • 在多个外设的使用同一的PIN引脚时怎么避免冲突?
  • 突然来一个事件能否及时得到处理?
  • 一系列事件发生需要有一定逻辑条件,如严格的先后顺序,几个事件同时发送引发其他事件发生,如何同步?

        以前我只能做许许多多的标记来进行判断,比如互斥,就得定义一个全局变量,当一个资源使用时修改全局变量的值表示封锁,事件的中断不能访问。这种做标记显得逻辑繁杂且乱。

于是,试着上操作系统看能不能把问题变简洁。

本次记录

事件集

新建的一个事件集可以有8个事件标记,每个事件标记相当于一个信号量,可实现一对多,多对多的事件同步。

三个线程的同步运行 F1->F2->F3

/*
 * Copyright (c) 2006-2022, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-10-14     RT-Thread    first version
 */

#include <rtthread.h>
#include "board.h"



#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "app.h"


rt_thread_t th0;
rt_thread_t cap_th;
rt_thread_t th3;


rt_event_t even1;

#define EVENT_F1 (1<<0)
#define EVENT_F2 (1<<1)
#define EVENT_F3 (1<<2)


void cap_entry(void * param){


   rt_uint32_t e;
    while(1){

        rt_event_recv(even1,
                       EVENT_F2,
                      RT_EVENT_FLAG_AND|RT_EVENT_FLAG_CLEAR,
                       RT_WAITING_FOREVER,
                       &e);
                rt_kprintf("th1\r\n");

               rt_event_send(even1, EVENT_F3);
                rt_thread_mdelay(1000);



    }



}




void th0_entry(void * para){

    rt_uint32_t e;
    static u8 Flag=0;
    while(1){

         rt_event_recv(even1,
                EVENT_F1,
                RT_EVENT_FLAG_AND|RT_EVENT_FLAG_CLEAR,
                RT_WAITING_FOREVER,
                &e);





          if(Flag){
              rt_pin_write(LEDSTATE, PIN_LOW);
              Flag=0;
          }
          else
          {
              rt_pin_write(LEDSTATE, PIN_HIGH);
              Flag=1;
          }

            rt_kprintf(" th2\r\n");

            rt_event_send(even1, EVENT_F2);
            rt_thread_mdelay(1000);
        }





}


void th3_entry(void * param){



while(1){


     rt_event_recv(even1,
                EVENT_F3,
                RT_EVENT_FLAG_AND| RT_EVENT_FLAG_CLEAR,
                RT_WAITING_FOREVER,
                RT_NULL);


                rt_kprintf("th3\r\n");
               rt_event_send(even1, EVENT_F1);
               rt_thread_mdelay(1000);





        }

}
  int main(void)
{




      MY_PIN_INIT();
      th0=rt_thread_create("state", th0_entry, NULL, 1024, 20, 10);
      cap_th=rt_thread_create("cap", cap_entry, NULL, 1012, 20, 10);
      th3=rt_thread_create("th3", th3_entry, NULL, 1024, 20, 10);

      even1 =rt_event_create("even1", RT_IPC_FLAG_FIFO);
      rt_thread_startup(cap_th);
      rt_thread_startup(th3);
      rt_thread_startup(th0);



          rt_event_send(even1, EVENT_F1);

    return RT_EOK;
}


终端运行 

邮箱

rt_mailbox

所定义的句柄 

msg_bool为邮件所存放的位置。

size为邮件的个数,类型为uint32_t,32位4个字节。

动态创建邮箱 rt_mb_create:分配邮箱空间,初始化等待邮箱事件挂起线程链表,初始化邮箱参数(size=邮件个数,entry=当前邮件个数,out_offset=接受(消费邮件位置)in_offset=输入(生产邮件的位置))

见源码:

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

    /* initialize ipc object */
    rt_ipc_object_init(&(mb->parent));

    /* initialize mailbox */
    mb->size     = size;
    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_mb_recv:若邮件中有邮件则读(从out_offset读),没有则把当前线程挂起,若设置了等待时间则开启一个定时器等待,若有线程在被邮箱阻塞则唤醒被阻塞的线程。

/**
 * 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_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)
    {
        rt_hw_interrupt_enable(temp);

        return -RT_ETIMEOUT;
    }

    /* mailbox is empty */
    while (mb->entry == 0)
    {
        /* 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;
        }

        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];

    /* 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_mb_send()

rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value)
{
    return rt_mb_send_wait(mb, value, 0);
}

rt_mb_send_wait()

超时时间指若邮箱已经满,若在超时时间内继续等待还没有空间剩余则返回。

发送邮件,在邮箱未满时:offset:邮件数自加1,从in_offset位置输入发送邮件的数据,随即in_offset指向下一个生产位置

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

邮件案例代码

官方的案例用的是发送二级指针的形式:这种好处在于可以得到不定长的数据,指针的类型在32位系统中恰恰是4个字节,拿到指针就意味着拿到了数据引用,前提是数据所在内存没有被释放,否则就是非法地址引用了,一般还是在发送时动态开辟内存,在接受时释放指针所指向内存,若指针所指向内存又放了指针(比如邮件传送一个结构体指针,结构体指针里定义了一个数组的地址),则注意从下级往上级逐级释放内存,否则将会一直占用内存。

我的案例,简单的发送一个4个字节的整形,主线程发送,3个子线程接受,表现出了三个子线程从邮件中接受数据是不同步的。

/*
 * Copyright (c) 2006-2022, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-10-14     RT-Thread    first version
 */

#include <rtthread.h>
#include "board.h"



#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "app.h"


rt_thread_t th0;
rt_thread_t th1;
rt_thread_t th2;

rt_mailbox_t mb1;



rt_err_t ret;
void th0_entry(void * param){


  u16  str;
    while(1){
   if(rt_mb_recv(mb1, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK)rt_kprintf("th0:%d\r\n",str);
              if(str ==  0)  rt_kprintf("th0 -- 0\r\n");
              rt_thread_mdelay(1000);
    }

}




void th1_entry(void * para){


   u16  str;
    static u8 Flag=0;
    while(1){


        if( rt_mb_recv(mb1, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK)rt_kprintf("th1:%d\r\n",str);
        if(str == 1) rt_kprintf(" th1 --1\r\n");

          if(Flag){
              rt_pin_write(LEDSTATE, PIN_LOW);
              Flag=0;
          }
          else
          {
              rt_pin_write(LEDSTATE, PIN_HIGH);
              Flag=1;
          }



            rt_thread_mdelay(800);
        }

}


void th2_entry(void * param){


     u16 str;
while(1){


   if( rt_mb_recv(mb1, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK)rt_kprintf("th2:%d\r\n",str);
  if(str == 2)  rt_kprintf("th2--2\r\n");

    rt_thread_mdelay(500);

        }

}
  int main(void)
{
      //初始化GPIO引脚
      MY_PIN_INIT();
      th0=rt_thread_create("th0", th0_entry, NULL, 1024, 20, 10);
      th1=rt_thread_create("th1", th1_entry, NULL, 1012, 20, 10);
      th2=rt_thread_create("th2", th2_entry, NULL, 1024, 20, 10);

      //开启三个线程
      rt_thread_startup(th0);
      rt_thread_startup(th1);
      rt_thread_startup(th2);

      //创建邮箱
      mb1=rt_mb_create("mb1",20,RT_IPC_FLAG_FIFO);

      u8 t=0;
      while(1){
          if(t%3 == 0){
          ret=    rt_mb_send(mb1, (rt_uint32_t)0);
//          if(ret == RT_EOK)
//          rt_kprintf("send th0 success\r\n");
          }
          else if(t%3 == 1){
          ret=    rt_mb_send(mb1, (rt_uint32_t)1);
//          if(ret == RT_EOK)
//                  rt_kprintf("send th1 success\r\n");
          }
          else if(t%3 == 2){
           ret=   rt_mb_send(mb1, (rt_uint32_t)2);
//           if(ret == RT_EOK)
//                          rt_kprintf("send th2 success\r\n");

          }

          t++;

          rt_kprintf("mb1 inoffset:%d\r\n",mb1->in_offset);
          rt_kprintf("mb1 outoffset:%d\r\n",mb1->out_offset);
          rt_thread_mdelay(500);

      }

    return RT_EOK;
}


运行结果: 

小结

         邮件的最大邮件数是size,当前邮件数是entry,当前生产邮件的位置是in_offset,当前接(消耗)邮件数是out_offset,邮件的存放缓存  rt_ubase_t          *msg_pool,是一个循环的环型数组。

posted @   昊月光华  阅读(30)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示