【RT-Thread】消息队列
消息队列提供了线程间异步通信机制。
·
RT-Thread 消息队列机制
1. 消息队列(Message Queue)基本概念
- 消息队列:线程间通信的一种机制,用于异步传递固定大小的数据块。
- 典型特点:发送方和接收方松耦合,不需要直接同步等待对方。
在RT-Thread中,消息队列适用于频繁产生数据但处理时间较长的应用场景,例如:传感器采样、任务通知。
2. 消息队列的核心结构
-
消息池(pool):固定数量的消息块组成的循环缓冲区,用来存放具体消息内容。
-
链表1 - 空闲消息块链表:
- 管理当前未使用的消息块。
- 发送消息时,从空闲链表取出一个块,写入数据。
-
链表2 - 已用消息块链表:
- 管理已经发送但尚未被接收的消息块。
- 接收线程从此链表中读取消息。
简化示意:
[ 空闲消息块链表 ] ← 发送 → [ 已用消息块链表 ] ← 接收
3. 消息队列操作详解
3.1 创建消息队列
rt_mq_t mq = rt_mq_create("mq1", // 名称
32, // 每条消息32字节
10, // 最多10条消息
RT_IPC_FLAG_FIFO); // 先进先出调度
- 消息池大小 = 32字节 × 10条 = 320字节。
- RT_IPC_FLAG_FIFO:发送顺序 = 接收顺序(也可选优先级调度)。
3.2 发送消息
char data[32] = "hello";
rt_mq_send(mq, data, sizeof(data));
- 内部流程:
- 尝试从空闲链表取出一个空块。
- 将
data复制到空块中。 - 将消息块挂入已用链表尾部。
- 如果有线程在等待接收,唤醒一个接收线程。
关于"76次直接写入"
在发送消息时,RT-Thread内部会最多尝试76次寻找空闲消息块:
- 如果找到空块,成功写入消息。
- 如果循环76次仍然找不到,认为队列已满,发送失败,返回
-RT_EFULL。
🔹 小总结:
| 项目 | 描述 |
|---|---|
| 76次扫描 | 最多循环76次找空闲消息块 |
| 成功情况 | 立即找到空块,不需要76次 |
| 队列满了 | 76次后认定失败,返回错误 |
| 为什么是76 | 折中考虑(效率+资源) |
3.3 接收消息
char recv_buf[32];
rt_mq_recv(mq, recv_buf, sizeof(recv_buf), RT_WAITING_FOREVER);
- 内部流程:
- 检查已用链表是否有消息块。
- 如果有,从链表头取出一条消息,复制到
recv_buf中。 - 释放消息块,放回空闲链表。
- 如果没有消息,根据超时时间等待。
3.4 删除消息队列
rt_mq_delete(mq);
- 释放内存资源,清空消息池,删除链表管理。
4. 消息队列内部链表管理流程图
消息池(msg_pool,大内存块,分成一块块小的消息单元)
┌───────────────┬───────────────┬───────────────┬───────────────┐
│ 消息块1(32字节) │ 消息块2(32字节) │ 消息块3(32字节) │ ... │
└───────────────┴───────────────┴───────────────┴───────────────┘
每个消息块的状态用两个链表来管理:
空闲链表(msg_free) 已用链表(msg_queue)
┌──────────┐ ┌──────────┐
│ 消息块2 │───>(空) │ 消息块1 │───> 消息块3 →(空)
└──────────┘ └──────────┘
【发送消息时】
1. 从空闲链表取一个空的消息块(比如 消息块2)。
2. 把要发送的数据写入消息块2。
3. 把消息块2插入到已用链表末尾。
【接收消息时】
1. 从已用链表取出一个有数据的消息块(比如 消息块1)。
2. 读取数据。
3. 把消息块1放回空闲链表头部。
✏️ 更口语化理解 :
-
消息池是所有真实存放数据的地方。
-
空闲链表就像是"空盒子队列",告诉系统哪里有空位可以放数据。
-
已用链表就像是"满盒子队列",告诉系统哪里已经装了数据等着被拿走。
-
发送就是把数据放到空盒子里,并送到满盒子队列。
接收就是从满盒子拿数据,并把盒子重新送回空盒子队列。
5. 消息队列中的线程休眠、唤醒与定时器机制
5.1 线程状态变化
-
发送消息:
- 队列未满 → 直接发送
- 队列已满 → 发送线程休眠(可设置超时)
-
接收消息:
- 队列有数据 → 直接接收
- 队列为空 → 接收线程休眠(可设置超时)
5.2 定时器的引入
- 如果指定超时时间,休眠时会启动一个内核定时器(rt_timer)。
- 超时后如果线程未被唤醒,则由定时器回调将线程唤醒并返回超时。
5.3 整体休眠/唤醒流程
发送线程或接收线程休眠时
├── 检查是否设置超时
│ ├── 是:启动定时器
│ └── 否:一直等待
├── 有资源可用时被唤醒(正常)
└── 超时定时器到期被唤醒(异常)
示例代码
发送线程(带超时保护)
void sender_thread(void *parameter)
{
char msg[] = "sensor data";
while (1)
{
if (rt_mq_send_wait(mq, msg, sizeof(msg), 200) != RT_EOK)
{
rt_kprintf("Send timeout or error!\n");
}
rt_thread_mdelay(500);
}
}
接收线程(带超时保护)
void receiver_thread(void *parameter)
{
char buffer[32];
while (1)
{
if (rt_mq_recv(mq, buffer, sizeof(buffer), 100) == RT_EOK)
{
rt_kprintf("Received: %s\n", buffer);
}
else
{
rt_kprintf("Receive timeout!\n");
}
}
}
6. 总结
- 消息队列提供了线程间异步通信机制。
- 通过空闲链表和已用链表管理消息块。
- 发送时最多扫描76次寻找空位。
- 接收时从已用链表取出消息,并归还空闲链表。
- 涉及到线程休眠/唤醒,必要时还涉及定时器保护超时场景。
📚 RT-Thread的消息队列机制高效而简洁,非常适合需要轻量化、高实时通信的场合。
更多推荐



所有评论(0)