发新帖

RT-Thread学习笔记——邮箱

[复制链接]
2063 0

本文包含源代码、原理图、PCB、封装库、中英文PDF等资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
前言
前面讲了RT-Thread的信号量、互斥量以及事件集这些都是线程间的同步方式。在我们进行实际的项目开发的时候,经常会涉及到一个线程更新某个全局变量值,然后另外一个线程去读取这个全局变量值,根据这个全局变量值的不同而去执行不同的操作,在RT-Thread 中则提供了更多的工具帮助在不同的线程中间传递信息,包括邮箱、消息队列、信号用于线程间的通信方式。本文将RT-Thread的邮箱服务,包括邮箱工作机制、工作管理方式以及应用示例,基于潘多拉开发板进行实验,单片机为STM32L475VET6。

一、邮箱的工作机制
RT-Thread 操作系统的邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针)。典型的邮箱也称作交换消息,如下图所示,线程或中断服务例程把一封 4 字节长度的邮件发送到邮箱中,而一个或多个线程可以从邮箱中接收这些邮件并进行处理。

20190212141013187.png
邮箱工作示意图(来源RT-Thread编程指南)


(1)非阻塞方式的邮件发送过程能够安全的应用于中断服务中,是线程、中断服务、定时器向线程发送消息的有效手段。通常来说,邮件收取过程可能是阻塞的,这取决于邮箱中是否有邮件,以及收取邮件时设置的超时时间。当邮箱中不存在邮件且超时时间不为 0 时,邮件收取过程将变成阻塞方式。在这类情况下,只能由线程进行邮件的收取。

(2)当一个线程向邮箱发送邮件时,如果邮箱没满,将把邮件复制到邮箱中。如果邮箱已经满了,发送线程可以设置超时时间,选择等待挂起或直接返回  RT_EFULL。如果发送线程选择挂起等待,那么当邮箱中的邮件被收取而空出空间来时,等待挂起的发送线程将被唤醒继续发送。

(3)当一个线程从邮箱中接收邮件时,如果邮箱是空的,接收线程可以选择是否等待挂起直到收到新的邮件而唤醒,或可以设置超时时间。当达到设置的超时时间,邮箱依然未收到邮件时,这个选择超时等待的线程将被唤醒并返回 RT_ETIMEOUT。如果邮箱中存在邮件,那么接收线程将复制邮箱中的 4 个字节邮件到接收缓存中。


二、邮箱的相关函数
1、创建动态邮箱函数:创建邮箱对象时会先从对象管理器中分配一个邮箱对象,然后给邮箱动态分配一块内存空间用来存放邮件,这块内存的大小等于邮件大小(4 字节)与邮箱容量的乘积,接着初始化接收邮件数目和发送邮件在邮箱中的偏移量,动态创建一个邮箱对象可以调用如下的函数接口:
  1. rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag);
复制代码
(1)入口参数:

name:邮箱名称。
size:邮箱容量。
flag:邮箱标志,它可以取如下数值:RT_IPC_FLAG_FIFO 或RT_IPC_FLAG_PRIO。

(2)返回值:

RT_NULL:创建失败。
邮箱对象的句柄:创建成功。

2、删除动态邮箱函数:当用 rt_mb_create() 创建的邮箱不再被使用时,应该删除它来释放相应的系统资源,一旦操作完成,邮箱将被永久性的删除。删除邮箱时,如果有线程被挂起在该邮箱对象上,内核先唤醒挂起在该邮箱上的所有线程(线程返回值是 RT_ERROR),然后再释放邮箱使用的内存,最后删除邮箱对象。删除邮箱的函数接口如下:
  1. rt_err_t rt_mb_delete (rt_mailbox_t mb);
复制代码
(1)入口参数:

mb:要删除的邮箱对象的句柄。

(2)返回值:

RT_EOK:成功。



3、创建静态邮箱函数:这里所说的创建静态邮箱和《RT-Thread编程指南》所讲的初始化邮箱是一样的,跟动态创建邮箱类似,只是初始化邮箱用于静态邮箱对象的初始化。与创建邮箱不同的是,静态邮箱对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中,其余的初始化工作与创建邮箱时相同。初始化邮箱时,该函数接口需要获得用户已经申请获得的邮箱对象控制块,缓冲区的指针,以及邮箱名称和邮箱容量(能够存储的邮件数)。函数接口如下:

  1. rt_err_t rt_mb_init(rt_mailbox_t mb,
  2.                     const char  *name,
  3.                     void        *msgpool,
  4.                     rt_size_t    size,
  5.                     rt_uint8_t   flag);
复制代码
(1)入口参数:

mb:邮箱对象的句柄。
name:邮箱名称。
msgpool:缓冲区指针。
size:邮箱容量。
flag:邮箱标志,它可以取如下数值:RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO。

(2)返回值:

RT_EOK:成功。

注意:这里的 size 参数指定的是邮箱的容量,即如果 msgpool 指向的缓冲区的字节数是 N,那么邮箱容量应该是 N/4。



4、删除静态邮箱函数:这里所说的删除静态邮箱和《RT-Thread编程指南》所讲的脱离邮箱是一样的,脱离邮箱将把静态初始化的邮箱对象从内核对象管理器中脱离,内核先唤醒所有挂在该邮箱上的线程(线程获得返回值是 RT_ERROR),然后将该邮箱对象从内核对象管理器中脱离。脱离邮箱使用下面的接口:
  1. rt_err_t rt_mb_detach(rt_mailbox_t mb);
复制代码
(1)入口参数:

mb:邮箱对象的句柄。

(2)返回值:

RT_EOK:成功。



5、发送邮件函数:线程或者中断服务程序可以通过邮箱给其他线程发送邮件,发送的邮件可以是 32 位任意格式的数据,一个整型值或者一个指向缓冲区的指针。当邮箱中的邮件已经满时,发送邮件的线程或者中断程序会收到 RT_EFULL 的返回值。函数接口如下:
  1. rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value);
复制代码
(1)入口参数:

mb:邮箱对象的句柄。
value:邮件内容。

(2)返回值:

RT_EOK:发送成功。
RT_EFULL:邮箱已经满了。

6、等待方式发送邮件函数:用户也可以通过如下的函数接口向指定邮箱发送邮件:
  1. rt_err_t rt_mb_send_wait(rt_mailbox_t mb,
  2.                          rt_uint32_t  value,
  3.                          rt_int32_t   timeout);
复制代码
rt_mb_send_wait() 与 rt_mb_send() 的区别在于有等待时间,如果邮箱已经满了,那么发送线程将根据设定的 timeout 参数等待邮箱中因为收取邮件而空出空间。如果设置的超时时间到达依然没有空出空间,这时发送线程将被唤醒并返回错误码。

(1)入口参数:

mb:邮箱对象的句柄。
value:邮件内容。
timeout:超时时间。

(2)返回值:

RT_EOK:发送成功。
RT_ETIMEOUT:超时。
RT_ERROR:失败,返回错误。



7、接收邮件函数:接收邮件时,接收者需指定接收邮件的邮箱句柄,并指定接收到的邮件存放位置以及最多能够等待的超时时间。接收邮件函数接口如下:
  1. rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout);
复制代码
(1)入口参数:

  mb:邮箱对象的句柄。
  value:邮件内容。
  timeout:超时时间。

(2)返回值:

  RT_EOK:发送成功。
  RT_ETIMEOUT:超时。
  RT_ERROR:失败,返回错误。

三、基于STM32的邮箱示例
光说不练都是假把式,那么接下来我们进行RT-Thread的邮箱实验,采用RTT&正点原子联合出品潘多拉开发板,基于STM32。创建一个邮箱,两个线程,其中一个线程用于发送邮件,另外一个线程由于接收邮件。通过按下不同按键发送不同的邮件内容,根据读取到右邮件内容执行不同操作。当读取到内容为KEY0按下时点亮RGB红灯,其他熄灭,当读取到内容为KEY1按下时点亮RGB蓝灯,其他熄灭,当读取到内容为KEY0按下时点亮RGB绿灯,其他熄灭。

1、实现代码:
  1. #include "rtthread.h"
  2. #include "string.h"
  3. #include "mailbox_app.h"
  4. #include "led.h"
  5. #include "key.h"
  6. /* 线程句柄 */
  7. static rt_thread_t thread1 = RT_NULL;
  8. static rt_thread_t thread2 = RT_NULL;
  9. /* 邮箱句柄 */
  10. static rt_mailbox_t mailbox1 = RT_NULL;
  11. char mailbox_msg_key0_press[] = "mailbox_msg_key0_press";
  12. char mailbox_msg_key1_press[] = "mailbox_msg_key1_press";
  13. char mailbox_msg_key2_press[] = "mailbox_msg_key2_press";
  14. /**************************************************************
  15. 函数名称 : thread1_recv_mailbox_msg
  16. 函数功能 : 线程1入口函数,用于接收邮件
  17. 输入参数 : parameter:入口参数
  18. 返回值           : 无
  19. 备注                 : 无
  20. **************************************************************/
  21. void thread1_recv_mailbox_msg(void *parameter)
  22. {
  23.         char *mb_msg;
  24.        
  25.         while(1)
  26.         {
  27.                 if(rt_mb_recv(mailbox1, (rt_uint32_t *)&mb_msg, RT_WAITING_FOREVER) == RT_EOK)
  28.                 {
  29.                         rt_kprintf("recv mb_msg:%s\r\n", mb_msg);
  30.                         if(0 == strcmp(mb_msg, "mailbox_msg_key0_press"))
  31.                         {
  32.                                 LED_R(0);
  33.                                 LED_B(1);
  34.                                 LED_G(1);
  35.                         }
  36.                         else if(0 == strcmp(mb_msg, "mailbox_msg_key1_press"))
  37.                         {
  38.                                 LED_R(1);
  39.                                 LED_B(0);
  40.                                 LED_G(1);
  41.                         }
  42.                         else if(0 == strcmp(mb_msg, "mailbox_msg_key2_press"))
  43.                         {
  44.                                 LED_R(1);
  45.                                 LED_B(1);
  46.                                 LED_G(0);
  47.                         }
  48.                 }
  49.                 rt_thread_mdelay(1);
  50.         }
  51. }
  52. /**************************************************************
  53. 函数名称 : thread2_send_mailbox_msg
  54. 函数功能 : 线程2入口函数,用于发送邮件
  55. 输入参数 : parameter:入口参数
  56. 返回值           : 无
  57. 备注                 : 无
  58. **************************************************************/
  59. void thread2_send_mailbox_msg(void *parameter)
  60. {
  61.         u8 key;
  62.        
  63.         while(1)
  64.         {
  65.                 key = key_scan(0);
  66.                
  67.                 if(key== KEY0_PRES)
  68.                 {
  69.                         rt_mb_send(mailbox1, (rt_uint32_t)&mailbox_msg_key0_press);
  70.                 }
  71.                 else if(key== KEY1_PRES)
  72.                 {
  73.                         rt_mb_send(mailbox1, (rt_uint32_t)&mailbox_msg_key1_press);
  74.                 }
  75.                 else if(key== KEY2_PRES)
  76.                 {
  77.                         rt_mb_send(mailbox1, (rt_uint32_t)&mailbox_msg_key2_press);
  78.                 }
  79.                
  80.                 rt_thread_mdelay(1);
  81.         }
  82. }
  83. void rtthread_mailbox_test(void)
  84. {
  85.         mailbox1 = rt_mb_create("mailbox1", 12, RT_IPC_FLAG_FIFO);        /* FIFO模式 */
  86.         if(mailbox1 != RT_NULL)
  87.         {
  88.                 rt_kprintf("RT-Thread create mailbox successful\r\n");
  89.         }
  90.         else
  91.         {
  92.                 rt_kprintf("RT-Thread create mailbox failed\r\n");
  93.                 return;
  94.         }
  95.         thread1 = rt_thread_create("thread1",
  96.                                 thread1_recv_mailbox_msg,
  97.                                 NULL,
  98.                                 512,
  99.                                 3,
  100.                                 20);
  101.         if(thread1 != RT_NULL)
  102.         {
  103.                 rt_thread_startup(thread1);;
  104.         }
  105.         else
  106.         {
  107.                 rt_kprintf("create thread1 failed\r\n");
  108.                 return;
  109.         }
  110.         thread2 = rt_thread_create("thread2",
  111.                                 thread2_send_mailbox_msg,
  112.                                 NULL,
  113.                                 1024,
  114.                                 2,
  115.                                 20);
  116.         if(thread2 != RT_NULL)
  117.         {
  118.                 rt_thread_startup(thread2);;
  119.         }
  120.         else
  121.         {
  122.                 rt_kprintf("create thread2 failed\r\n");
  123.                 return;
  124.         }
  125. }
复制代码
2、观察FInSH和执行效果:

(1)输入list_mailbox,可以看到由哪些邮箱,邮箱的容量以及当前挂起等待邮箱内容的线程。

20190212164344518.png

(2)按下KEY0,打印如下邮件的内容,同时RGB红灯亮。

20190212164534866.png

(3)按下KEY1,打印如下邮件内容,同时RGB蓝灯亮。

20190212164643827.png

(4)按下KEY2,打印如下邮件内容,同时RGB绿灯亮。

20190212164735790.png

四、邮箱的使用场合及技巧
邮箱是一种简单的线程间消息传递方式,特点是开销比较低,效率较高。在 RT-Thread 操作系统的实现中能够一次传递一个 4 字节大小的邮件,并且邮箱具备一定的存储功能,能够缓存一定数量的邮件数 (邮件数由创建、初始化邮箱时指定的容量决定)。邮箱中一封邮件的最大长度是 4 字节,所以邮箱能够用于不超过 4 字节的消息传递。

由于在 32 系统上 4 字节的内容恰好可以放置一个指针,因此当需要在线程间传递比较大的消息时,可以把指向一个缓冲区的指针作为邮件发送到邮箱中,即邮箱也可以传递指针,例如:
  1. struct msg
  2. {
  3.     rt_uint8_t *data_ptr;
  4.     rt_uint32_t data_size;
  5. };
复制代码
对于这样一个消息结构体,其中包含了指向数据的指针 data_ptr 和数据块长度的变量 data_size。当一个线程需要把这个消息发送给另外一个线程时,可以采用如下的操作:
  1. struct msg* msg_ptr;
  2. msg_ptr = (struct msg*)rt_malloc(sizeof(struct msg));
  3. msg_ptr->data_ptr = ...; /* 指 向 相 应 的 数 据 块 地 址 */
  4. msg_ptr->data_size = len; /* 数 据 块 的 长 度 */
  5. /* 发 送 这 个 消 息 指 针 给 mb 邮 箱 */
  6. rt_mb_send(mb, (rt_uint32_t)msg_ptr);
复制代码
申请结构体大小的内存空间,返回的指针指向了结构体,当结构体中的信息处理完,那么可以将指向结构体的指针作为邮件发送到邮箱中,而在接收邮件的线程中完成对结构体信息的读取操作,在完成操作后应当释放内存,因为收取过来的是指针,而 msg_ptr 是一个新分配出来的内存块,所以在接收线程处理完毕后,需要释放相应的内存块:
  1. struct msg* msg_ptr;
  2. if (rt_mb_recv(mb, (rt_uint32_t*)&msg_ptr) == RT_EOK)
  3. {
  4.     /* 在 接 收 线 程 处 理 完 毕 后, 需 要 释 放 相 应 的 内 存 块 */
  5.     rt_free(msg_ptr);
  6. }
复制代码
参考文献:
1、[野火®]《RT-Thread 内核实现与应用开发实战—基于STM32》

2、《RT-THREAD 编程指南》

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

更多

客服中心

微信扫描二维码 服务时间:周一至周日 8:30-22:00
快速回复 返回顶部 返回列表