目录
本学笔记基于zephyr 工程版本 2.2.99,主机环境为ubuntu18.04,开发平台 nrf52840dk_nrf52840
摘要
消息队列是一个内核对象,简单的实现了消息队列的功能。允许线程和ISR异步的发送和接收固定大小的数据项。
1 概念
任意多个消息队列可以被定义。引用消息队列时使用的是它的地址。
一个消息队列有如下几个关键属性:
ring buffer:它存储已经被发送但是没有被接收的数据项。
data item size:数据项的大小(字节)。
maximum quqntity:可以被入队在环形缓冲区中数据项的最大个数。
消息队列的ring buffer内存必须是N个字节对齐,N是2的幂数,比如(1,2,4,8...)。并且确保存储到队列的消息也是按照这种方式进行对齐。
消息队列使用之前必须被初始化,初始化会清空环形缓冲区。
一个数据项可以被县城或者ISR发送,如果有等待消息的线程存在,那么这个数据项直接拷贝到这个线程(注意不是把数据项地址给这个等待的线程,而是直接把数据项的内容到复制到接收线程指定的地址)。其他情况,如果ring buffer有空间,数据项会拷贝到消息队列的ring buffer。前面说的FIFO是存储传递的是地址,而消息队列是内容,这需要与之前的讲的FIFO进行区分。无论什么时候,发送的数据的大小,必须和定义是消息队列的数据项大小一致。
如果发送线程向一个已满的消息队列的ring buffer发送数据,那么线程将被阻塞,知道ring buffer有空闲的存储空间。这个动作可以任意多个线程同时执行。也就是多可线程可以同时向这个已满的队列中写数据,但是都会被阻塞。当队列有空间时,那个优先级最高等待时间最长的队列,可以被唤醒,然后向队列中写数据。
一个数据项可以被线程接收。这个数据项被拷贝到线程指定的内存地址(就是前面提到的,是传值,而不是址)。这个指定的接收区域的空间,必须等于数据项大小。
接收时也是类似,如果队列是空,也就是没有数据项时,那么这个接收线程,将被阻塞。任意多个线程可以同时接收同一个消息,没有消息时都被阻塞,当有消息时优先级最高等待时间最长的线程将被唤醒接收数据。
收受线程还可以查看消息队列中的head消息,换句话说,就是读取消息队列的数据项之后,不把这个数据项从队列中删除。
内核可以允许ISR去接收一个消息队列,但是不要尝试去等待一个空的消息队列。
2 实现
2.1 定义消息队列
可以使用struct k_msgq类型定义一个消息队列,定义之后必须使用k_msgq_init()进行初始化。
下面的示例代码,定义一个有10个12个字节长的数据项,并初始化为空。
struct data_item_type {
u32_t field1;
u32_t field2;
u32_t field3;
};
char __aligned(4) my_msgq_buffer[10 * sizeof(data_item_type)];
struct k_msgq my_msgq;
k_msgq_init(&my_msgq, my_msgq_buffer, sizeof(data_item_type), 10);
或者,使用K_MSGQ_DEFINE在编译时定义定初始化。和上面的代码效果相同:
K_MSGQ_DEFINE(my_msgq, sizeof(data_item_type), 10, 4);
2.2 写数据到消息队列
可以使用k_msgq_put()添加一个数据项到消息队列。
下面的示例代码,从一个生产者传输数据到多个消费者。如果数据没有被消费者及时取出,那么生产者会清空队列,继续存入新数据。
void producer_thread(void)
{
struct data_item_t 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 */
}
}
2.3 从消息队列读一个数项
可以使用k_msgq_get()从队列中读取一个数据项。
作为消费者去读取数据项:
void consumer_thread(void)
{
struct data_item_t data;
while (1) {
/* get a data item */
k_msgq_get(&my_msgq, &data, K_FOREVER);
/* process data item */
...
}
}
2.4 查看消息队列的头数项
可以使用k_msgq_peek()函数去看看一个数据项。
下面的示例代码,查看head数据项:
void consumer_thread(void)
{
struct data_item_t data;
while (1) {
/* read a data item by peeking into the queue */
k_msgq_peek(&my_msgq, &data);
/* process data item */
...
}
}
3 参考链接:
https://docs.zephyrproject.org/latest/reference/kernel/data_passing/message_queues.html