消息队列是内核对象,允许线程和 ISR 异步发送和接收固定大小
的数据项。
概念
可以定义任意数量的消息队列(仅受可用 RAM 的限制)。每个消息队列都由其内存地址引用。
消息队列具有以下关键属性:
- 已发送但尚未接收的数据项的
环形缓冲区
。 - 数据项大小,以字节为单位。
- 可以在环形缓冲区中排队的最大数据项数。
消息队列的环形缓冲区必须与N字节边界对齐,其中N是2的幂(即 1、2、4、8、…)。若要确保存储在环形缓冲区中的消息与此边界对齐,数据项大小也必须是N的倍数。
必须先初始化消息队列,然后才能使用它。这会将其环形缓冲区设置为空。
数据项可以通过线程或 ISR 发送到消息队列。如果有等待线程,则发送线程的数据项被复制
到等待线程,如果空间可用,则将项目复制
到消息队列的环形缓冲区。在任一情况下,要发送的数据区域的大小
都必须等于消息队列的数据项大小
。
如果线程在环形缓冲区已满时尝试发送数据项,则发送线程可能会选择等待空间变为可用。当环形缓冲区已满时,任意数量的发送线程可以同时等待;当空间可用时,它将分配给等待时间最长的优先级最高的发送线程。
线程可以从消息队列接收数据项。数据项被复制
到接收线程指定的区域;接收区域的大小必须等于消息队列的数据项大小。
如果线程在环形缓冲区为空时尝试接收数据项,则接收线程可以选择等待发送数据项。当环形缓冲区为空时,任意数量的接收线程可以同时等待;当数据项变为可用时,它将被赋予等待时间最长的最高优先级接收线程。
线程还可以在消息队列的头部查看消息,而无需将其从队列中删除。数据项被复制到接收线程指定的区域;接收区域的大小必须等于消息队列的数据项大小。
定义消息队列
消息队列是使用k_msgq
类型的变量定义的。然后必须通过调用k_msgq_init()
对其进行初始化。
struct data_item_type {
uint32_t field1;
uint32_t field2;
uint32_t field3;
};
char __aligned(4) my_msgq_buffer[10 * sizeof(struct data_item_type)];
struct k_msgq my_msgq;
k_msgq_init(&my_msgq, my_msgq_buffer, sizeof(struct data_item_type), 10);
也可以通过调用K_MSGQ_DEFINE
在编译时定义和初始化消息队列。
K_MSGQ_DEFINE(my_msgq, sizeof(struct data_item_type), 10, 4);
下面的代码演示了在前面的示例代码中定义的结构的对齐实现。 表示每个都将在指定的字节边界上开始。 aligned(4)
表示结构与被4整除的地址对齐。
typedef struct {
uint32_t field1;
uint32_t field2;
uint32_t field3;
}__attribute__((aligned(4))) data_item_type;
写入消息队列
通过调用k_msgq_put()
将数据项添加到消息队列中。
void producer_thread(void)
{
struct data_item_type data;
while (1) {
/* create data item to send (e.g. measurement, timestamp, ...) */
data = ...
/* send data to consumers */
while (k_msgq_put(&my_msgq, &data, K_NO_WAIT) != 0) {
/* message queue is full: purge old data & try again */
k_msgq_purge(&my_msgq);
}
/* data item was successfully added to message queue */
}
}
从消息队列读取
数据项是通过调用k_msgq_get()
从消息队列中获取的。
void consumer_thread(void)
{
struct data_item_type data;
while (1) {
/* get a data item */
k_msgq_get(&my_msgq, &data, K_FOREVER);
/* process data item */
...
}
}
从消息队列查看数据
通过调用k_msgq_peek()
从消息队列中读取数据项,但并不删除数据项。
void consumer_thread(void)
{
struct data_item_type data;
while (1) {
/* read a data item by peeking into the queue */
k_msgq_peek(&my_msgq, &data);
/* process data item */
...
}
}
消息队列的注意事项
使用消息队列以异步方式在线程之间传输小数据项
。
如果需要消息队列传输大型数据项。这可能会增加中断延迟,因为在写入或读取数据项时会锁定中断。写入或读取数据项的时间随其大小线性增加
,因为该项将全部复制到内存中的缓冲区或从中复制。因此,通常最好通过交换指向数据项的指针
而不是数据项本身来传输大型数据项。