RTT 消息邮箱

news2024/10/1 23:43:33

1.邮箱概念

邮箱服务是实时操作系统中一种典型的线程间通信方法。举一个简单的例子,有两个线程,线程 1 检测按键状态并发送,线程 2 读取按键状态并根据按键的状态相应地改变 LED 的亮灭。这里就可以使用邮箱的方式进行通信,线程 1 将按键的状态作为邮件发送到邮箱,线程 2 在邮箱中读取邮件获得按键状态并对 LED 执行亮灭操作。

这里的线程 1 也可以扩展为多个线程。例如,共有三个线程,线程 1 检测并发送按键状态,线程 2 检测并发送 ADC 采样信息,线程 3 则根据接收的信息类型不同,执行不同的操作。

2. 邮箱的工作机制

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

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

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

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

邮箱控制块

在 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;      /* 发送线程的挂起等待队列 */
};
typedef struct rt_mailbox* rt_mailbox_t;

rt_mailbox 对象从 rt_ipc_object 中派生,由 IPC 容器所管理。

3. 邮箱的管理方式

邮箱控制块是一个结构体,其中含有事件相关的重要参数,在邮箱的功能实现中起重要的作用。邮箱的相关接口如下图所示,对一个邮箱的操作包含:创建 / 初始化邮箱、发送邮件、接收邮件、删除 / 脱离邮箱。

创建和删除邮箱

动态创建一个邮箱对象可以调用如下的函数接口:

rt_mailbox_t rt_mb_create (const char* name, rt_size_t size, rt_uint8_t flag);

创建邮箱对象时会先从对象管理器中分配一个邮箱对象,然后给邮箱动态分配一块内存空间用来存放邮件,这块内存的大小等于邮件大小(4 字节)与邮箱容量的乘积,接着初始化接收邮件数目和发送邮件在邮箱中的偏移量。下表描述了该函数的输入参数与返回值:

参数描述
name邮箱名称
size邮箱容量
flag邮箱标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
返回——
RT_NULL创建失败
邮箱对象的句柄创建成功

Note

注:RT_IPC_FLAG_FIFO 属于非实时调度方式,除非应用程序非常在意先来后到,并且你清楚地明白所有涉及到该邮箱的线程都将会变为非实时线程,方可使用 RT_IPC_FLAG_FIFO,否则建议采用 RT_IPC_FLAG_PRIO,即确保线程的实时性。

当用 rt_mb_create() 创建的邮箱不再被使用时,应该删除它来释放相应的系统资源,一旦操作完成,邮箱将被永久性的删除。删除邮箱的函数接口如下:

rt_err_t rt_mb_delete (rt_mailbox_t mb);

删除邮箱时,如果有线程被挂起在该邮箱对象上,内核先唤醒挂起在该邮箱上的所有线程(线程返回值是 -RT_ERROR),然后再释放邮箱使用的内存,最后删除邮箱对象。下表描述了该函数的输入参数与返回值:

参数描述
mb邮箱对象的句柄
返回——
RT_EOK成功

初始化和脱离邮箱

初始化邮箱跟创建邮箱类似,只是初始化邮箱用于静态邮箱对象的初始化。与创建邮箱不同的是,静态邮箱对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中,其余的初始化工作与创建邮箱时相同。函数接口如下:

  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缓冲区指针
size邮箱容量
flag邮箱标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
返回——
RT_EOK成功

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

脱离邮箱将把静态初始化的邮箱对象从内核对象管理器中脱离。脱离邮箱使用下面的接口:

rt_err_t rt_mb_detach(rt_mailbox_t mb);

使用该函数接口后,内核先唤醒所有挂在该邮箱上的线程(线程获得返回值是 - RT_ERROR),然后将该邮箱对象从内核对象管理器中脱离。下表描述了该函数的输入参数与返回值:

参数描述
mb邮箱对象的句柄
返回——
RT_EOK成功

发送邮件

线程或者中断服务程序可以通过邮箱给其他线程发送邮件,发送邮件函数接口如下:

rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value);

发送的邮件可以是 32 位任意格式的数据,一个整型值或者一个指向缓冲区的指针。当邮箱中的邮件已经满时,发送邮件的线程或者中断程序会收到 -RT_EFULL 的返回值。下表描述了该函数的输入参数与返回值:

参数描述
mb邮箱对象的句柄
value邮件内容
返回——
RT_EOK发送成功
-RT_EFULL邮箱已经满了

等待方式发送邮件

用户也可以通过如下的函数接口向指定邮箱发送邮件:

rt_err_t rt_mb_send_wait (rt_mailbox_t mb,
                      rt_uint32_t value,
                      rt_int32_t timeout);

rt_mb_send_wait() 与 rt_mb_send() 的区别在于有等待时间,如果邮箱已经满了,那么发送线程将根据设定的 timeout 参数等待邮箱中因为收取邮件而空出空间。如果设置的超时时间到达依然没有空出空间,这时发送线程将被唤醒并返回错误码。下表描述了该函数的输入参数与返回值:

参数描述
mb邮箱对象的句柄
value邮件内容
timeout超时时间
返回——
RT_EOK发送成功
-RT_ETIMEOUT超时
-RT_ERROR失败,返回错误

发送紧急邮件

发送紧急邮件的过程与发送邮件几乎一样,唯一的不同是,当发送紧急邮件时,邮件被直接插队放入了邮件队首,这样,接收者就能够优先接收到紧急邮件,从而及时进行处理。发送紧急邮件的函数接口如下:

rt_err_t rt_mb_urgent (rt_mailbox_t mb, rt_ubase_t value);

下表描述了该函数的输入参数与返回值:

参数描述
mb邮箱对象的句柄
value邮件内容
返回——
RT_EOK发送成功
-RT_EFULL邮箱已满

接收邮件

只有当接收者接收的邮箱中有邮件时,接收者才能立即取到邮件并返回 RT_EOK 的返回值,否则接收线程会根据超时时间设置,或挂起在邮箱的等待线程队列上,或直接返回。接收邮件函数接口如下:

rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout);

接收邮件时,接收者需指定接收邮件的邮箱句柄,并指定接收到的邮件存放位置以及最多能够等待的超时时间。如果接收时设定了超时,当指定的时间内依然未收到邮件时,将返回 - RT_ETIMEOUT。下表描述了该函数的输入参数与返回值:

参数描述
mb邮箱对象的句柄
value邮件内容
timeout超时时间
返回——
RT_EOK接收成功
-RT_ETIMEOUT超时
-RT_ERROR失败,返回错误

邮箱使用示例

这是一个邮箱的应用例程,初始化 2 个静态线程,一个静态的邮箱对象,其中一个线程往邮箱中发送邮件,一个线程往邮箱中收取邮件。如下代码所示:

邮箱的使用例程

#include <rtthread.h>

#define THREAD_PRIORITY      10
#define THREAD_TIMESLICE     5

/* 邮箱控制块 */
static struct rt_mailbox mb;
/* 用于放邮件的内存池 */
static char mb_pool[128];

static char mb_str1[] = "I'm a mail!";
static char mb_str2[] = "this is another mail!";
static char mb_str3[] = "over";

ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;

/* 线程 1 入口 */
static void thread1_entry(void *parameter)
{
    char *str;

    while (1)
    {
        rt_kprintf("thread1: try to recv a mail\n");

        /* 从邮箱中收取邮件 */
        if (rt_mb_recv(&mb, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK)
        {
            rt_kprintf("thread1: get a mail from mailbox, the content:%s\n", str);
            if (str == mb_str3)
                break;

            /* 延时 100ms */
            rt_thread_mdelay(100);
        }
    }
    /* 执行邮箱对象脱离 */
    rt_mb_detach(&mb);
}

ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;

/* 线程 2 入口 */
static void thread2_entry(void *parameter)
{
    rt_uint8_t count;

    count = 0;
    while (count < 10)
    {
        count ++;
        if (count & 0x1)
        {
            /* 发送 mb_str1 地址到邮箱中 */
            rt_mb_send(&mb, (rt_uint32_t)&mb_str1);
        }
        else
        {
            /* 发送 mb_str2 地址到邮箱中 */
            rt_mb_send(&mb, (rt_uint32_t)&mb_str2);
        }

        /* 延时 200ms */
        rt_thread_mdelay(200);
    }

    /* 发送邮件告诉线程 1,线程 2 已经运行结束 */
    rt_mb_send(&mb, (rt_uint32_t)&mb_str3);
}

int mailbox_sample(void)
{
    rt_err_t result;

    /* 初始化一个 mailbox */
    result = rt_mb_init(&mb,
                        "mbt",                      /* 名称是 mbt */
                        &mb_pool[0],                /* 邮箱用到的内存池是 mb_pool */
                        sizeof(mb_pool) / 4,        /* 邮箱中的邮件数目,因为一封邮件占 4 字节 */
                        RT_IPC_FLAG_FIFO);          /* 采用 FIFO 方式进行线程等待 */
    if (result != RT_EOK)
    {
        rt_kprintf("init mailbox failed.\n");
        return -1;
    }

    rt_thread_init(&thread1,
                   "thread1",
                   thread1_entry,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);

    rt_thread_init(&thread2,
                   "thread2",
                   thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);
    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(mailbox_sample, mailbox sample);

仿真运行结果如下:

 \ | /
- RT -     Thread Operating System
 / | \     3.1.0 build Aug 27 2018
 2006 - 2018 Copyright by rt-thread team
msh >mailbox_sample
thread1: try to recv a mail
thread1: get a mail from mailbox, the content:I'm a mail!
msh >thread1: try to recv a mail
thread1: get a mail from mailbox, the content:this is another mail!
…
thread1: try to recv a mail
thread1: get a mail from mailbox, the content:this is another mail!
thread1: try to recv a mail
thread1: get a mail from mailbox, the content:over复制错误复制成功

例程演示了邮箱的使用方法。线程 2 发送邮件,共发送 11 次;线程 1 接收邮件,共接收到 11 封邮件,将邮件内容打印出来,并判断结束。

邮箱的使用场合

邮箱是一种简单的线程间消息传递方式,特点是开销比较低,效率较高。在 RT-Thread 操作系统的实现中能够一次传递一个 4 字节大小的邮件,并且邮箱具备一定的存储功能,能够缓存一定数量的邮件数 (邮件数由创建、初始化邮箱时指定的容量决定)。邮箱中一封邮件的最大长度是 4 字节,所以邮箱能够用于不超过 4 字节的消息传递。由于在 32 系统上 4 字节的内容恰好可以放置一个指针,因此当需要在线程间传递比较大的消息时,可以把指向一个缓冲区的指针作为邮件发送到邮箱中,即邮箱也可以传递指针,例如:

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_ptr 是一个新分配出来的内存块,所以在接收线程处理完毕后,需要释放相应的内存块:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/348190.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloud保姆级搭建教程五---Redis

首先&#xff0c;这个和微服务没有直接的关系&#xff0c;只是在代码开发当中要使用的一个工具而已&#xff0c;为了提高这个系统的性能&#xff0c;加快查询效率等方面而使用它1、首先&#xff0c;要先安装redis到电脑上&#xff0c;这里依然是在windows上演示&#xff0c;之后…

window11, WSL, Ubuntu 20.04 编译TensorRT源码并且安装 TensorRT

一、安装参考 1.ubuntu18.04TensorRT 配置攻略 ubuntu18.04TensorRT 配置攻略 - 简书卷积网络的量化和部署是重要环节&#xff0c;以前我们训练好模型后直接trace进行调用&#xff0c;参考&#xff08;C windows调用ubuntu训练的PyTorch模型&#xff08;.pt/.pth&#xff09;…

Utkuici:一款功能强大的Nessus自动化任务实现工具

关于Utkuici 今天&#xff0c;随着信息技术系统的普及&#xff0c;网络安全领域的投资已大幅增加。各种规模的组织都需要进行漏洞管理、渗透测试和各种分析&#xff0c;以准确确定各自机构受网络威胁的影响程度。通过借助漏洞管理工具的行业领先者Tenable Nessus&#xff0c;我…

若依ruoyi——手把手教你制作自己的管理系统【三、代码生成】

昨天情人节一(&#xffe3;︶&#xffe3;*)) 送给赛利亚一((*&#xffe3;3&#xffe3;)╭ ********* 专栏略长 爆肝万字 细节狂魔 请准备好一键三连 ********* 修改后的页面&#xff1a; 干干净净贼舒服一Ψ(&#xffe3;∀&#xffe3;)Ψ——Ψ(&#xffe3;∀&#x…

Vue一个项目兼容每个省份的个性化需求

开发环境及打包指令 后拼上省份区划"serve:henan": "yarn && vue-cli-service serve -o --encryptSM2 --zone41","serve:hunan": "yarn && vue-cli-service serve -o --encryptSM2 --zone43","serve:guizhou&quo…

电子技术——多阶放大器

电子技术——多阶放大器 实际上的商用放大器是由多个单阶放大器联级而成的多阶放大器。首先第一阶&#xff08;输入&#xff09;通常提供一个较大的输出阻抗来保证较小的信号衰减。若是差分放大器&#xff0c;还要保证有较大的共模抑制比。中间各阶放大器负责提供电压增益&…

wireshark在流媒体分析中常见操作

Wireshark Wireshark&#xff08;前称Ethereal&#xff09;是一个网络封包分析软件。网络封包分析软件的功能是截取网络封包&#xff0c;并尽可能显示出最为详细的网络封包资料。Wireshark使用WinPCAP作为接口&#xff0c;直接与网卡进行数据报文交换。 在流媒体相关工作中&am…

学英语的优势已来,抓住这个机会

文 / 冰雪&#xff08;微信公众号&#xff1a;王不留&#xff09; ChatGPT大火&#xff0c;国外的商业价值还没找到&#xff0c;咱们这边已经开始变现了。谷雨小姐姐昨天在”一起学英语”微信群发了一张“收割韭菜”的文案截图。 299入社群&#xff0c;服务内容为&#xff1a;免…

卷严重、难度高、激励少,如何适应空投市场新变化

自从空投交互从2020年开始之后&#xff0c;不少人都开始加入到空投交互的行列中&#xff0c;一些项目也因为“格局”的因素&#xff0c;在项目正式上线前都会给早期参与者空投代币&#xff0c;以此吸引大家的关注。但是在越来越多的人加入到撸空投行列之中后&#xff0c;现在整…

javaEE 初阶 — 流量控制与拥塞控制

文章目录1. 流量控制2. 拥塞控制TCP 工作机制&#xff1a;确认应答机制 超时重传机制 连接管理机制 滑动窗口 1. 流量控制 流量控制是一种干扰发送的窗口大小的机制&#xff0c;滑动窗口&#xff0c;窗口越大&#xff0c;传输的效率就越高&#xff08;一份时间&#xff0c;…

Gromacs中的g_mmpbsa计算带电底物与蛋白的结合能不准确

g_mmpbsa计算带电底物与蛋白的结合能总是不准确 TOC 在做的两个项目中&#xff0c;利用g_mmpbsa计算带电底物与蛋白的结合能结果非常不可靠&#xff0c;底物带两个硫酸根离子&#xff0c;g_mmpbsa在计算带电的底物与酶的结合能时总是不准确&#xff0c;因此后续若底物带电&…

三层交换机DHCP中继

关于中继&#xff0c;我们需要有一个对比。正常情况下我们是不是需要配置单臂路由然后开启DHCP地址池&#xff0c;然就设置网段网关以及DNS。这样的话考验 的其实是命令功底。但是为了方便&#xff0c;我们 可以添加服务器&#xff0c;将这个服务给到服务器去配置&#xff0c;这…

为什么sleeping的会话会造成阻塞(2)

背景客户反馈系统突然从11:10开始运行非常缓慢&#xff0c;在SQL专家云中看到大量的产生阻塞的活动会话&#xff0c;KILL掉阻塞的源头马上又出现新的源头&#xff0c;实在没有办法只能重启应用程序断开所有数据库连接才解决&#xff0c;请我们协助分析根本的原因。现象登录SQL专…

Web前端CSS清除浮动的5种方法

在移动端清除浮动布局&#xff0c;常用的5种方法&#xff1a; 使用清除浮动的类&#xff1b;使用overflow属性&#xff1b;使用 flex 布局&#xff1b;使用grid 布局&#xff1b;使用 table 布局。 根据实际情况选择适合的方法&#xff0c;需要注意兼容性和语义性问题。在移动…

Yolo v5 pytorch模型转onnx用c++进行推理

本文介绍如何使用u版的yolov5 模型转成 onnx模型&#xff0c;使用python代码推理onnx模型&#xff0c;然后再使用c代码推理onnx模型,本文使用yolov5 s版本&#xff0c;在m,l,x都试过可行环境配置 :1 u版的yolov5 4.0 版本&#xff0c;其他版本没有试过 https://github.com/ultr…

console调试,chrome调试工具之Console

背景 console作为web调试的一项重要技能大多数开发人员都是console.log()一把梭到底&#xff0c;其实console对象上还有很多可用于调试的方法 控制台打印类别&#xff0c;conso调试面板 第一个是全部消息&#xff0c;第二个是自己在控制台里使用console指令打印的&#xff0…

【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢

HashMap中为什么引入红黑树&#xff0c;而不是AVL树呢1. 概述 开始学习这个知识点之前我们需要知道&#xff0c;在JDK1.8 以及之前&#xff0c;针对HashMap有什么不同。 JDK 1.7的时候&#xff0c;HashMap的底层实现是数组 链表JDK1.8的时候&#xff0c;HashMap的底层实现是数…

Web3中文|Web3再起恐慌:全球第三大稳定币BUSD发行商面临诉讼

俗话说&#xff0c;老大的位置不好当。作为全球最大加密资产交易所BA的CEO&#xff0c;赵长鹏的生活可谓一刻都不能平静。 刚刚结束与SBF的口舌之争&#xff0c;从FTX倒台的熊市中挺过来&#xff0c;赵长鹏又遇到了新的麻烦。 Nansen数据显示&#xff0c;用户在过去24小时内从…

LabVIEW开发的上位机界面在其它电脑分辨率下-界面窗口偏移显示问题解决

目录 问题&#xff1a; 分析&#xff1a; 解决方式 1&#xff09;编辑前面板边界适配对应的分辨率 2&#xff09;编辑前面板窗口-窗口边界 3&#xff09;编辑前面板窗口-保持窗口比例 4&#xff09;设置VI属性--窗口运行时位置居中显示 参考 问题&#xff1a; 在基于La…

图的基本概念

1、图的概念 G(V&#xff0c;E) 图G由节点集合VV(G)和边集合EE(G)组成&#xff0c;其中V为非空有限集合。 集合V中的节点&#xff08;node&#xff09;用红色标出&#xff0c;通过集合E中黑色的边&#xff08;edge&#xff09;连接。 G的边&#xff1a;E中的每个顶点对&#x…