当前位置: 首页 > news >正文

RTthread消息队列学习

在 RT-Thread 中,消息队列是一块由操作系统集中管理的、预先分配好的环形内存缓冲区

发送端: 当发送线程调用发送 API 时,操作系统底层会调用类似memcpy()的内存拷贝函数,将你指定的局部变量内存块中的数据,逐字节地复制到消息队列的内部缓冲区中。即使线程调用的函数定义的变量被释放掉,该变量的值已经被拷贝到消息队列中。

接收端: 当接收线程调用接收 API 时,操作系统再次调用拷贝函数,将消息队列内部缓冲区里的数据,复制到接收线程指定的局部变量内存块中。

正因为存在这种“双向拷贝”机制,发送端在调用完send之后,哪怕原有的局部变量立刻被销毁、被覆盖,也绝对不会影响接收端的数据准确性。

例子1:利用消息队列发送字符串数组

#include <rtthread.h> #include <rtdevice.h> #include <board.h> #include <stdlib.h> // 提供 malloc 和 free 函数的声明支持 rt_thread_t send_data_t; rt_thread_t rec_data_t; rt_mq_t mq_t; char name[] ={"asdfghj"}; // 发送线程的入口函数(充当“生产者”角色) void send_data(void *param) { while(1) { rt_mq_send(mq_t,name, 5); rt_thread_mdelay(1000); } } // 接收线程的入口函数(充当“消费者”角色) void rec_data(void *param) { char aa[32]; while(1) { memset(aa, 0, sizeof(aa)); rt_mq_recv(mq_t,aa,2,RT_WAITING_FOREVER); rt_kprintf("recv: %s\n", aa); rt_thread_mdelay(1000); } } int main(void) { mq_t=rt_mq_create("mq_chen", 64,10, RT_IPC_FLAG_FIFO); // 2. 动态创建并启动“发送线程”。栈大小 4096 字节,优先级为 10。 send_data_t=rt_thread_create("send_data", send_data,RT_NULL, 4096, 10, 10); rt_thread_startup(send_data_t); // 3. 动态创建并启动“接收线程”。栈大小 4096 字节,优先级为 10。 rec_data_t=rt_thread_create("rec_data", rec_data,RT_NULL, 4096, 10, 10); rt_thread_startup(rec_data_t); return 0; }

1: mq_t = rt_mq_create("mq_chen", 64, 10, RT_IPC_FLAG_FIFO);

  • "mq_chen":在内核对象管理器中注册的字符串名称,仅用于系统调试时区分不同的队列。

  • 64:单条消息的最大字节数(Message Size)。操作系统以此为基准划分内存块。意味着这个队列里的每一个存储单元,最大可以写入 64 个字节的数据。

  • 10:队列的最大消息容量(Max Messages)。多少条64字节的消息

  • RT_IPC_FLAG_FIFO:线程唤醒的调度算法。FIFO 表示当有多个接收线程阻塞在此队列时,按照它们进入阻塞状态的时间先后顺序,依次唤醒。

  • 底层结果:系统分配了至少640字节的内存空间作为核心缓冲区,并将这块内存的管理权限(句柄)赋值给指针变量mq_t

2:rt_mq_send(mq_t, name, 5);

  • mq_t:指定目标队列的句柄,告诉操作系统数据要写到哪里。

  • name:数据源的内存首地址,它指向包含"asdfghj\0"字符串的内存块。

  • 5:从数据源的内存首地址开始往后拷贝5个字节的数据

  • 底层执行过程:操作系统计算出目标队列中的下一个空闲存储单元。然后,从name所指向的内存地址开始,严格提取 5 个连续的字节(即'a','s','d','f','g'),将它们复制到刚才找到的 64 字节的空闲单元中。该单元剩余的 59 个字节被忽略。

3:rt_mq_recv(mq_t, aa, 2, RT_WAITING_FOREVER);

  • mq_t:指定数据来源的队列句柄。

  • aa:目标接收缓冲区的内存首地址(定义的char aa[32]数组的起始地址)。

  • 2:接收从接收缓冲区的内存首地址开始往后数2个字节的数据

  • RT_WAITING_FOREVER:阻塞指令。如果队列为空,当前线程立即挂起,停止执行,直到队列中有数据写入才被内核唤醒。

4: memset(aa, 0, sizeof(aa));

  • 将数组aa所占用的整块物理内存空间,全部强制擦除并填充为\0

例子2:利用消息队列发送结构体

#include <rtthread.h> #include <rtdevice.h> #include <board.h> // #include <stdlib.h> // 传递局部变量结构体不再需要 malloc/free,可以注释掉 rt_thread_t send_data_t; rt_thread_t rec_data_t; rt_mq_t mq_t; // 1. 定义一个用于传递的结构体类型 struct sensor_data { int temp; // 温度 int hum; // 湿度 }; // 发送线程的入口函数(生产者) void send_data(void *param) { // 定义一个局部变量,用于存放要发送的数据 struct sensor_data tx_data; while(1) { // 给结构体赋值 tx_data.temp = 25; tx_data.hum = 60; // 2. 发送结构体: // 参数2:传入结构体变量的地址 &tx_data // 参数3:传入结构体的实际大小 sizeof(struct sensor_data) rt_mq_send(mq_t, &tx_data, sizeof(struct sensor_data)); rt_thread_mdelay(1000); } } // 接收线程的入口函数(消费者) void rec_data(void *param) { // 定义一个空的局部变量,作为接收数据的“容器” struct sensor_data rx_data; while(1) { // 不需要再用 memset 清零了,因为接下来的 recv 会完整覆盖这个结构体 // 3. 接收结构体: // 参数2:传入接收容器的地址 &rx_data // 参数3:传入结构体的实际大小 sizeof(struct sensor_data) rt_mq_recv(mq_t, &rx_data, sizeof(struct sensor_data), RT_WAITING_FOREVER); // 直接通过结构体成员打印数据 rt_kprintf("recv: temp=%d, hum=%d\n", rx_data.temp, rx_data.hum); // 因为没有涉及到延时必须严格对齐的问题,这里的延时可以删掉, // 接收线程会因为 RT_WAITING_FOREVER 自动挂起,直到发送端发来新数据。 } } int main(void) { // 4. 创建消息队列时,单条消息的最大长度也必须改成结构体的大小 mq_t = rt_mq_create("mq_chen", sizeof(struct sensor_data), 10, RT_IPC_FLAG_FIFO); send_data_t = rt_thread_create("send_data", send_data, RT_NULL, 4096, 10, 10); rt_thread_startup(send_data_t); rec_data_t = rt_thread_create("rec_data", rec_data, RT_NULL, 4096, 10, 10); rt_thread_startup(rec_data_t); return 0; }

struct sensor_data tx_data单片机会在栈空间划分8字节的物理内存,将温湿度数据存储。

当调用rt_mq_send时,CPU 执行严格的按字节复制,把这块栈内存里的 8 个字节,原封不动地克隆了一份,并写入到操作系统预先分配好的消息队列内部缓冲区内核空间中。

执行while循环后,这块内存立马被填入了下一次的新数据。

rt_mq_recv:把消息队列里面的内容复制给接收端

http://www.cnnetsun.cn/news/1488675.html

相关文章:

  • springboot基于学生兴趣的学习资源推荐系统 的设计与实现
  • 哨兵2号影像实战:5分钟搞定10种盐分指数的GDAL批量计算(附完整脚本)
  • nli-distilroberta-base实操手册:日志监控、请求限流与异常熔断配置
  • springboot的旅游商城问卷答疑网站的设计与实现
  • TMSpeech:Windows本地实时语音转文字终极指南,三步打造高效办公助手
  • 从部署到对话:Qwen2.5-0.5B-Instruct完整使用流程详解
  • 别卷了!这个在线PS网页版让我把装软件的硬盘空间省下来存小姐姐照片
  • QMCDecode终极指南:三步解锁QQ音乐加密文件的完整教程
  • 避坑指南:在Windows上用YOLOv5检测B站视频,我踩过的那些环境配置的‘雷’
  • SAP固定资产报废BAPI_ASSET_RETIREMENT_POST
  • Granite TimeSeries FlowState R1模型Docker容器化部署与运维手册
  • OpenClaw Harness设计全解(5张图详解),从入门到精通,收藏这一篇就够了!
  • TranslucentTB任务栏透明功能修复指南:Windows 11兼容性问题全解决方案
  • apk应用管理系统系统源码 网址打包app iOS免签打包 搭建教程
  • CLIP模型微调层实战:从零构建高效跨模态检索系统
  • ActiveReports for .NET 20.0 AIで进化する帐票开発环境
  • OpenClaw自动化邮件分类:GLM-4.7-Flash智能收件箱管理
  • 操作系统开发实战:如何用MMU权限检查实现内存保护机制?
  • 漏洞管理进入智能时代!OC社区发布国内首个AI Agent增强的漏洞动态分级标准题
  • 深耕工业连接20余年,西赛姆科技如何用高可靠定制化方案赋能智能制造?
  • Unity URP 深度解析:利用Stencil与RenderFeature实现高效遮挡高亮
  • NaViL-9B作品分享:100+张真实场景测试图的图文理解准确率与响应时间
  • 昇腾CANN架构入门:核心组件与数据处理流程详解
  • Deepseek公式复制到Word乱码?轻松解决Word公式排版问题
  • 开源阅读鸿蒙版:重新定义你的个性化数字阅读体验
  • 高并发接口防护:Sentinel 限流实战案例
  • 51单片机毕设题目大全:从实战选题到系统实现的完整指南
  • 协作网盘有哪些?分享国内企业常用的7款
  • 别再为版本头疼了!手把手教你搞定Vivado 2018.3与ModelSim 10.6c的完美联调
  • 纹理工作流革新:Tacent View如何重塑游戏开发者的图像处理体验