消息队列
本文档根据《嘉楠K230开发手册》V1.0(2024-11-30)整理。正文、表格、示例代码与插图均来自原始手册。
消息队列可以用于"线程到线程"、"线程到中断"、"中断到线程"直接传输信息。
本章涉及如下内容:
- 怎么创建、删除消息队列
- 消息队列中消息如何保存
- 怎么向消息队列发送数据、怎么从消息队列读取数据
- 在消息队列上阻塞是什么意思
消息队列的特性
常规操作
消息队列是一种常用的线程间通讯方式,用来传输数据。
可以应用在多种场合:线程间的消息交换、中断服务程序给线程发送数据。
消息队列的简化操如入下图所示,从此图可知:
- 消息队列可以包含若干个消息
- 每个消息可以容纳的数据大小是一样的
- 创建消息队列时就要指定长度(消息个数)、消息的数据大小
- 数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部读
- 也可以把紧急的数据写到消息队列的头部

更详细的操作如下图所示:


传输数据的两种方法
使用消息队列传输数据时有两种方法:
- 拷贝:把数据、把变量的值复制进消息队列里
- 引用:把数据、把变量的地址复制进消息队 列里
RT-Thread使用拷贝值的方法,这更简单:
- 局部变量的值可以发送到消息队列中,后续即使函数退出、局部变量被回收,也不会影响消息队列中的数据
- 无需分配buffer来保存数据,消息队列中有buffer
- 局部变量可以马上再次使用
- 发送线程、接收线程解耦:接收线程不需要知道这数据是谁的、也不需要发送线程来释放数据
- 如果数据实在太大,你还是可以使用消息队列传输它的地址
- 消息队列的空间有RT-Thread内核分配,无需线程操心
消息队列的阻塞访问
只要知道消息队列的句柄,谁都可以读、写该消息队列。
线程、ISR都可读、写消息队列。可以多个线程读写消息队列。
线程读写消息队列时,如果读写不成功,可以即刻返回错误,也可以阻塞。阻塞时可以指定超时时间。口语化地说,就是可以定个闹钟:如果能读写了就马上进入就绪态,否则就阻塞直到超时。
比如某个线程读消息队列时:
- 如果消息队列中有可用的消息,即刻得到消息并返回
- 如果消息队列中没有可用的消息,线程有两种选择:即刻放回一个错误值,或者阻塞一段时间
- 如果线程阻塞,它何时被唤醒?
- 在指定的时间内,别的线程或者中断服务程序写了队列,会把它唤醒
- 否则,指定的时间到达后超时返回错误。
既然读写消息队列的线程个数没有限制:
- 多个线程都想写队列,但是队列已经满了,这些线程可以进入阻塞状态:它们都在等待队列有空间
- 那么:队列有空间时,把那个线程唤醒?
- 多个线程都想读队列,但是队列已经空了,这些线程可以进入阻塞状态:它们都在等待队列有数据
- 那么:队列有数据时,把那个线程唤醒?
- 唤醒谁?有两种方法。创建队列时,可以指定一个参数flag
- RT_IPC_FLAG_PRIO:表示唤醒优先级最高的等待线程
- RT_IPC_FLAG_FIFO:表示唤醒等待时间最长的等待线程
消息队列函数
使用消息队列的流程:创建/初始化消息队列、发送消息队列、接收消息队列、删除/脱离消息队列。
创建/初始化
消息队列的创建有两种方法:动态分配内存、静态分配内存,
- 动态分配内存:rt_mq_create,消息队列的内存在函数内部动态分配
- 静态分配内存:rt_mq_init,消息队列的内存要事先分配好
**rt_mq_create()**函数原型如下:
rt_mq_t rt_mq_create(const char* name, rt_size_t msg_size, rt_size_t max_msgs, rt_uint8_t flag);
| 参数 | 说明 |
|---|---|
| name | 消息队列的名字 |
| msg_size | 消息队列每个消息的大小:以字节为单位 |
| max_msgs | 消息队列最多能存放多少个消息 |
| flag | 消息队列采用的等待方式: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO |
| 返回值 | 消息队列对象的句柄:成功,返回句柄,以后使用句柄来操作消息队列RT_NULL:失败 |
**rt_mq_init()**函数原型如下:
rt_err_t rt_mq_init(rt_mq_t mq, const char* name, void *msgpool, rt_size_t msg_size, rt_size_t pool_size, rt_uint8_t flag);
| 参数 | 说明 |
|---|---|
| mq | 消息队列对象的句柄 |
| name | 消息队列的名字 |
| msgpool | 指向存放消息的缓冲区的指针 |
| msg_size | 消息队列每个消息的大小:以字节为单位 |
| pool_size | 存放消息的缓冲区大小 |
| flag | 消息队列采用的等待方式: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO |
| 返回值 | 非0:成功,返回句柄,以后使用句柄来操作消息队列NULL:失败,因为pxQueueBuffer为NULL |
删除/脱离
不再使用一个消息队列时:
- 删除它:rt_mq_delete(),只能删除使用**rt_mq_create()**创建的队列
- 脱离它:rt_mq_detach(),只能脱离使用r**t_mq_init()**初始化的队列
删除消息队列的函数为rt_mq_delete(),它会释放内存。原型如下:
rt_err_t rt_mq_delete(rt_mq_t mq);
删除消息队列时,如果有线程在等待该队列,则内核会先唤醒这些线程(线程返回值是 - RT_ERROR),然后再释放消息队列使用的内存,最后删除消息队列对象。
脱离消息队列,就是将消息队列对象被从内核对象管理器中脱离。原型如下:
rt_err_t rt_mq_detach(rt_mq_t mq);
脱离消息队列时,如果有线程在等待该队列,则内核会先唤醒这些线程(线程返回值是 - RT_ERROR)。
发消息
RT-Thread有三个发送消息的函数:
- rt_mq_send() 发送消息
- rt_mq_send_wait() 等待方式发送消息
- rt_mq_urgent() 发送紧急消息
线程或者中断服务程序都可以往消息队列写入消息。
发送消息时,从空闲消息链表取一个空闲消息块,把消息复制到该消息块,然后把消息块挂到消息队列尾部。
使用**rt_mq_send()**发送消息时,队列中有空闲消息块时,才能成功发送消息,否则返回错误码(-RT_EFULL)。
使用**rt_mq_send_wait()**发送消息时,如果队列中没有可用的空闲消息块,会根据timeout参数等待,超时后才返回错误。
使用**rt_mq_urgent()**发送消息时,也要先得到空闲消息块,它会把消息块放在消息队列的头部,以便这个消息能被第1时间读取。
发送消息的函数原型如下:
rt_err_t rt_mq_send (rt_mq_t mq, void* buffer, rt_size_t size);
参数说明如下:
| 参数 | 说明 |
|---|---|
| mq | 消息队列对象的句柄 |
| buffer | 消息内容 |
| size | 消息大小 |
| 返回值 | RT_EOK:发送成功-RT_EFULL:消息队列满-RT_ERROR:发送消息长度大于队列中消息的最大长度 |
等待方式发送消息的函数原型如下:
rt_err_t rt_mq_send_wait(rt_mq_t mq, const void *buffer, rt_size_t size, rt_int32_t timeout);
参数说明如下:
| 参数 | 说明 |
|---|---|
| mq | 消息队列对象的句柄 |
| buffer | 消息内容 |
| size |