RT-Thread邮箱

目录

邮箱的基本概念和特性

邮箱的基本概念

邮箱的特性

邮箱的运作机制

发送邮件

接收邮件

邮箱控制块

邮箱相关接口

消息队列的本质是链表:

  • 空闲消息链表,往队列里写入消息时,先从空闲链表中得到消息块;从队列读出消息后,把消息块放入空闲链表中
  • 消息块头部链表:消息写入消息块后,该消息块被放到尾部;从队列里读出消息时,从头部读
  • 使用消息队列可以传递各类大小的消息,它使用memcpy的方式写入消息,读出消息,如果我们只是传递很小的数据,比如一些数值,可以使用邮箱;它的效率更高。

邮箱的基本概念和特性

邮箱的基本概念

邮箱在操作系统中是一种常用的IPC通信方式,邮箱可以在线程与线程之间,中断与线程之间进行消息的传递,此外邮箱相比于信号量与消息队列来说,其开销更低,效率更高。所以常用来做线程与线程之间、中断与线程之间的通信。邮箱中的每一封邮件只能容纳固定的4字节的内容(STM32是32位处理系统,一个指针的大小即为4字节,所以一封邮件恰好能够容纳一个指针)。当需要在线程间传递较大的消息时,可以把指向一个缓冲区的指针作为邮件发送到邮箱中。

线程能够从邮箱中读取消息,当邮箱中的邮件是空的时,根据用户自定义的阻塞时间决定是否挂起线程。当邮箱中有新邮件时,挂起的读取线程被唤醒,邮箱也是一种异步通信方式。通过邮箱,线程或中断服务函数可以将一个或多个邮件放入邮箱中,同样,一个或者多个线程可以从邮箱中获得邮件消息,当有多个邮件发送到邮箱时,通常应将先进入邮箱的邮件先传给线程,也就是说,线程先得到的是最先进入邮箱的消息,即先进先出原则(FIFO),同时RT-Thread中的邮箱支持优先级,也就是说在所有等待邮件的线程中优先级最高的会先获得邮件。

RT-Thread中使用邮箱实现异步通信工作,具有如下特性:

  • 邮件支持先进先出方式排队与优先级排队方式,支持异步读写工作方式
  • 发送与接收邮件均支持超时机制
  • 一个线程能够从任意一个消息队列中接收和发送邮件
  • 多个线程能够向同一个邮箱发送邮件和从中接收邮件
  • 邮箱中的每一封邮件只能容纳固定的4字节长度内容(可以存放缓冲区的地址)
  • 当队列使用结束后,需要通过删除邮箱以释放内存。

邮箱与消息队列很相似,消息队列中消息的长度是可以由用户配置的,但邮箱中邮件的大小却只能是固定容纳4字节的内容,所以使用邮箱的开销是很小的,因为传递的只能是4字节以内的内容,所以其效率会更高。

邮箱的特性

邮箱的本质是环形缓冲区,和消息队列的本质是不一样的,虽然说邮箱相当于是消息队列的一种特殊情形。

  • 邮箱的每一封邮件,只能容纳4字节内容,对于32位系统指针刚好为4字节
  • 邮件的 发送通常是非阻塞的,线程中断都可以发送邮件,也可使用阻塞方式发送。
  • 邮件的接收通常是阻塞的,取决于邮箱中是否有邮件
  • 当一个线程向邮箱发送邮件,如果邮箱没满,就把数值写入邮箱中,如果邮箱满了,发送线程可以直接返回-RT_EFULL,也可以挂起一段时间,在挂起期间,别的线程或者中断读了邮箱会唤醒挂起的线程;同理,接收邮件也是如此,可以不等待也可以等待阻塞

邮箱的运作机制

创建邮箱对象时会先创建一个邮箱对象控制块,然后给邮箱分配一块内存空间用来存放邮件,这块内存的大小等于邮件大小(4字节)与邮箱容量的乘积,接着初始化接收邮件和发送邮件在邮箱中的偏移量,然后在初始化消息队列,此时消息队列为空。

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

发送邮件

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

接收邮件

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

当邮箱不再被使用时,应该删除它以释放系统资源,一旦操作完成,邮箱将被永久性的删除。

邮箱的运作机制具体见邮箱的发送和接收示意图

 邮箱的应用场景

RT-Thread操作系统的邮箱可存放固定条数的邮件,邮箱容量在创建/初始化邮箱时设定,每个邮件大小为4字节,当需要在线程间传递比较大的消息时,可以把指向一个缓冲区的指针作为邮件发送到邮箱中。

与系统其他通信方式相比,邮箱的通信开销更低,效率更高。无论是什么消息,传递的都是4个字节的邮件,所以经常应用在众多领域中,另外其实现的发送/接收阻塞机制,能很好应用于线程与线程、中断与线程之间的通讯。

其实邮箱中,每封邮件的大小为4字节,在32位系统中,刚好能存放一个指针,所以邮箱也特别适合那种仅传地址的情况。

邮箱的应用技巧

其实很简单的,只是一个指针的传递。

struct msg
{
    rt_uint8_t *data_ptr;
    rt_uint32_t data_size;    

};

 对于这样一个结构体,其中包含了指向数据的指针data_ptr和数据块长度的变量data_size。当一个线程需要把这个消息发送给另外一个线程时,可以采用如下的方式进行操作。

struct msg *msg_ptr;
msg_ptr = (struct msg*)rt_malloc(sizeof(struct msg));
msg_ptr->data_ptr = ...; /* 指向相应的数据块地址 */
msg_ptr->data_size = len; /* 数据块的长度 */
/* 发送这个消息指针给 mb 邮箱 */
rt_mb_send(mb, (rt_uint32_t)msg_ptr);

申请结构体大小的内存空间,返回的指针指向了结构体,当结构体中的信息处理完,那么可以将指向结构体的指针作为邮件发送到邮箱中,而在接收邮件的线程中完成对结构体信息的读取操作,在完成操作后应当释放内存,因为收取过来的是指针,而msg_str是一个新分配出来的内存块,所以在接收线程处理完毕之后,需要释放相应的内存块。

struct msg* msg_ptr;
if (rt_mb_recv(mb, (rt_uint32_t*)&msg_ptr) == RT_EOK)
{ 
    /* 在接收线程处理完毕后,需要释放相应的内存块 */
    rt_free(msg_ptr);
 }

邮箱控制块

struct rt_mailbox
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    rt_ubase_t          *msg_pool;                      /**< start address of message buffer */

    rt_uint16_t          size;                          /**< size of message pool */

    rt_uint16_t          entry;                         /**< index of messages in msg_pool */
    rt_uint16_t          in_offset;                     /**< input offset of the message buffer */
    rt_uint16_t          out_offset;                    /**< output offset of the message buffer */

    rt_list_t            suspend_sender_thread;         /**< sender thread suspended on this mailbox */
};

邮箱相关接口

 创建邮箱:邮箱的创建 有两种方法

  • 动态分配内存:rt_mb_create();邮箱的内存在函数内部动态分配,分配的内存大小为邮件大小乘以邮箱容量
  • 静态分配内存:rt_mb_init();邮箱的内存事先分配好,比如可以是数组
 rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag)
参数说明
name邮箱名称
size邮箱容量
flag邮箱采用的等待方式,RT_IPC_FLAG_FIFO或者RT_IPC_FLAG_PRIO
返回值邮箱对象的句柄,成功:返回句柄,以后使用句柄来操作邮箱,RT_NULL:失败
 rt_err_t rt_mb_init(rt_mailbox_t mb,        
                    const char  *name,
                    void        *msgpool,
                    rt_size_t    size,
                    rt_uint8_t   flag);
参数说明
mb邮箱对象的句柄
name邮箱名称
msgpool缓冲区指针
szie邮箱容量
flag邮箱采用的等待方式,RT_IPC_FLAG_FIFO或者RT_IPC_FLAG_PRIO
返回值RT_EOK:成功,RT_NULL:失败

删除邮箱接口:删除和脱离

  • 删除它:rt_mb_delete(),只能删除使用rt_mb_create()创建的邮箱
  • 脱离它:rt_mb_detach(),只能脱离使用rt_mb_init()初始化的邮箱

删除邮箱时,如果有线程在等待该邮箱,则内核先唤醒这些线程(线程返回值是 -RT_ERROR),然后再释放邮箱使用的内存,最后删除邮箱对象。

  rt_err_t rt_mb_detach(rt_mailbox_t mb)
            rt_err_t rt_mb_delete(rt_mailbox_t mb)
脱离消息邮箱时,如果有线程在等待该邮箱,则内核会先唤醒这些线程(线程返回值
- RT_ERROR )。

 (注意:个人觉得二者直接的差异就是一个删除之后,会释放内存,一个只是不能使用但是内存没有释放)

  发送邮件接口:有三个发送邮件的函数(我在源码里只找到两个)

  • rt_mb_send():发送邮件
  • rt_mb_send_wait():等待方式发送邮件
  • rt_mb_urgent():发送紧急邮件(没找到源码)
 rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value)
rt_err_t rt_mb_send_wait(rt_mailbox_t mb,
                         rt_ubase_t   value,
                         rt_int32_t   timeout)

接收邮件接口:

rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout)

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值