消息队列是内核对象,允许线程和 ISR 异步发送和接收固定大小的数据项。

概念

可以定义任意数量的消息队列(仅受可用 RAM 的限制)。每个消息队列都由其内存地址引用。
消息队列具有以下关键属性:

  • 已发送但尚未接收的数据项的环形缓冲区
  • 数据项大小,以字节为单位。
  • 可以在环形缓冲区中排队的最大数据项数。

消息队列的环形缓冲区必须与N字节边界对齐,其中N是2的幂(即 1、2、4、8、…)。若要确保存储在环形缓冲区中的消息与此边界对齐,数据项大小也必须是N的倍数。
必须先初始化消息队列,然后才能使用它。这会将其环形缓冲区设置为空。
数据项可以通过线程或 ISR 发送到消息队列。如果有等待线程,则发送线程的数据项被复制到等待线程,如果空间可用,则将项目复制到消息队列的环形缓冲区。在任一情况下,要发送的数据区域的大小都必须等于消息队列的数据项大小
如果线程在环形缓冲区已满时尝试发送数据项,则发送线程可能会选择等待空间变为可用。当环形缓冲区已满时,任意数量的发送线程可以同时等待;当空间可用时,它将分配给等待时间最长的优先级最高的发送线程。
线程可以从消息队列接收数据项。数据项被复制到接收线程指定的区域;接收区域的大小必须等于消息队列的数据项大小。
如果线程在环形缓冲区为空时尝试接收数据项,则接收线程可以选择等待发送数据项。当环形缓冲区为空时,任意数量的接收线程可以同时等待;当数据项变为可用时,它将被赋予等待时间最长的最高优先级接收线程。
线程还可以在消息队列的头部查看消息,而无需将其从队列中删除。数据项被复制到接收线程指定的区域;接收区域的大小必须等于消息队列的数据项大小。

定义消息队列

消息队列是使用k_msgq类型的变量定义的。然后必须通过调用k_msgq_init()对其进行初始化。

  1. struct data_item_type {
  2. uint32_t field1;
  3. uint32_t field2;
  4. uint32_t field3;
  5. };
  6. char __aligned(4) my_msgq_buffer[10 * sizeof(struct data_item_type)];
  7. struct k_msgq my_msgq;
  8. k_msgq_init(&my_msgq, my_msgq_buffer, sizeof(struct data_item_type), 10);

也可以通过调用K_MSGQ_DEFINE在编译时定义和初始化消息队列。

  1. K_MSGQ_DEFINE(my_msgq, sizeof(struct data_item_type), 10, 4);

下面的代码演示了在前面的示例代码中定义的结构的对齐实现。 表示每个都将在指定的字节边界上开始。 aligned(4)表示结构与被4整除的地址对齐。

  1. typedef struct {
  2. uint32_t field1;
  3. uint32_t field2;
  4. uint32_t field3;
  5. }__attribute__((aligned(4))) data_item_type;

写入消息队列

通过调用k_msgq_put()将数据项添加到消息队列中。

  1. void producer_thread(void)
  2. {
  3. struct data_item_type data;
  4. while (1) {
  5. /* create data item to send (e.g. measurement, timestamp, ...) */
  6. data = ...
  7. /* send data to consumers */
  8. while (k_msgq_put(&my_msgq, &data, K_NO_WAIT) != 0) {
  9. /* message queue is full: purge old data & try again */
  10. k_msgq_purge(&my_msgq);
  11. }
  12. /* data item was successfully added to message queue */
  13. }
  14. }

从消息队列读取

数据项是通过调用k_msgq_get()从消息队列中获取的。

  1. void consumer_thread(void)
  2. {
  3. struct data_item_type data;
  4. while (1) {
  5. /* get a data item */
  6. k_msgq_get(&my_msgq, &data, K_FOREVER);
  7. /* process data item */
  8. ...
  9. }
  10. }

从消息队列查看数据

通过调用k_msgq_peek()从消息队列中读取数据项,但并不删除数据项。

  1. void consumer_thread(void)
  2. {
  3. struct data_item_type data;
  4. while (1) {
  5. /* read a data item by peeking into the queue */
  6. k_msgq_peek(&my_msgq, &data);
  7. /* process data item */
  8. ...
  9. }
  10. }

消息队列的注意事项

使用消息队列以异步方式在线程之间传输小数据项
如果需要消息队列传输大型数据项。这可能会增加中断延迟,因为在写入或读取数据项时会锁定中断。写入或读取数据项的时间随其大小线性增加,因为该项将全部复制到内存中的缓冲区或从中复制。因此,通常最好通过交换指向数据项的指针而不是数据项本身来传输大型数据项。