消息队列是解决任务间通信的经典方案,其核心作用如:
任务解耦 - 让采集、处理、发送模块独立演进
流量削峰 - 蓄水池机制应对突发数据
优先级调度 - 让紧急消息插队
数据完整性 - 避免竞态条件导致的脏数据
消息队列的类型大概有如下几种:
- FIFO队列。先进先出队列,典型场景如:日志记录、普通数据流。优先级队列。典型场景如:告警系统、控制指令。环形缓冲区。典型场景如:音视频流、波形采集。邮箱(Mailbox)。典型场景如:简单通知。
本篇文章我们来梳理一个基于FreeRTOS,支持优先级、超时控制、零内存碎片的简易消息队列的实现思路。
1. 静态内存池
在MCU上,每次malloc/free都可能产生外部碎片。假设你的队列里轮流存放16B和64B的消息,运行一段时间后,堆内存会变成"千疮百孔"的状态。内存碎片化,虽然总空闲内存足够,但无法分配连续的大块。
使用静态内存池,消灭碎片化隐患。
静态内存池方案
核心思路:启动时一次性分配所有内存,运行时只做节点复用。
池大小根据业务峰值评估,宁可多分配10%也不要刚好卡边界;MSG_DATA_SIZE设为最大消息体的字节数,避免截断;某些场景下可以用环形数组代替链表,减少指针操作。
2. 互斥锁+信号量
嵌入式RTOS(如FreeRTOS)下,队列操作涉及三种同步原语,构建线程安全屏障。
互斥锁(Mutex):保护队列结构的head/tail指针,防止并发修改
not_full信号量:队列满时阻塞生产者
not_empty信号量:队列空时阻塞消费者
生产者-消费者的信号量协作流程:
实现片段
注意:
-
- 互斥锁不能信号量等待之前,导致生产者拿着锁等待,消费者无法释放空位 → 死锁信号量的初始值很关键:
not_full初始值应为MSG_POOL_SIZE,not_empty初始值为0
3. 固定大小消息
可变vs固定:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 可变大小 | 节省内存 | 需要额外的长度字段,复制开销不确定 | 消息体差异大(如1B和1KB混用) |
| 固定大小 | 实现简单,时间复杂度O(1) | 浪费部分内存 | 消息体大小相近(嵌入式典型场景) |
固定大小消息格式设计
注意:消息头部放类型和优先级,方便路由和调度如果28B不够用,考虑引入
消息片段化:大消息拆成多个定长分片
4. 优先级插入
为什么需要优先级?让紧急消息插队。比如:传感器每100ms发送常规数据,突然检测到异常需要立即告警。如果用FIFO队列,告警消息可能排在100条常规消息之后,消息会延迟到达。
实现:有序链表插入
5. 超时机制
超时机制防止系统挂起,不能因为队列一直为空,消费者永久阻塞。
超时返回错误码
经验值:高频任务(如100ms周期):超时设为500ms低频任务(如秒级):超时设为5000ms关键任务需要配合看门狗,超时后及时喂狗
167