嵌入式环形队列、消息队列的实现原理

简介: 嵌入式环形队列、消息队列的实现原理

说明

嵌入式环形队列和消息队列是实现数据缓存和通信的常见数据结构,广泛应用于嵌入式系统中的通信协议和领域。

环形队列是一种先进先出(FIFO)的数据结构,其中数据被存储在一个环形缓冲区中。它使用两个指针,分别指向队列的头和尾,以便在读写操作时追踪队列的状态。当队列满时,新数据将覆盖旧数据,以确保队列的长度保持不变。环形队列在实现嵌入式通信协议时特别有用,例如UART,CAN等。

消息队列是一种多个发送者和接收者之间共享数据的通信机制。它允许多个任务或线程向队列发送消息,并允许多个任务或线程从队列中接收消息。每个消息都有一个固定的大小和格式,并可以根据需要进行排队和检索。在嵌入式系统中,消息队列广泛用于处理异步事件,例如中断处理和任务之间的通信。

主要应用于:

  1. 网络通信协议(例如TCP/IP,UDP等)中的数据缓存和队列管理。
  2. 嵌入式操作系统(例如FreeRTOS,uC/OS等)中的任务通信和事件处理。
  3. 汽车电子领域中的CAN和LIN通信协议。
  4. 工业自动化领域中的Modbus,Profibus等通信协议。
  5. 无线通信领域中的蓝牙,Zigbee,LoRa等通信协议。


大致应用

  1. 串口通信中,可以使用环形队列来接收和发送数据。当接收到新的数据时,将其存储到环形队列中,并在需要发送数据时从队列中取出数据发送。这种方式可以减少中断处理的时间,提高系统的响应速度。
  2. 多任务系统中,消息队列用于任务之间的通信。每个任务都可以向消息队列中发送消息,其他任务可以从队列中获取消息并进行相应的处理。这种方式可以实现任务之间的解耦,提高系统的可扩展性和可维护性。
  3. 实时控制系统中,环形队列可以用于缓存传感器数据或控制命令。当传感器或其他设备向系统发送数据时,可以将其存储到环形队列中,然后由控制任务从队列中获取数据并进行相应的处理。这种方式可以减少系统对硬件的依赖性,提高系统的灵活性和可靠性。
  4. 音频处理中,环形队列可以用于实现音频数据的缓存。当音频数据输入时,将其存储到环形队列中,然后由音频处理任务从队列中获取数据并进行处理。这种方式可以实现音频数据的流式处理,提高系统的处理效率和响应速度。


嵌入式环形队列


嵌入式环形队列是一种先进先出(FIFO)的队列,其实现基于环形缓冲区。队列的头尾指针分别指向队列的第一个元素和最后一个元素,当队列满时,新加入的元素将覆盖队列头的元素。嵌入式环形队列的实现过程如下:

  1. 队列初始化:初始化头尾指针为0,表示队列为空。
  2. 入队操作:将元素插入队列尾部,尾指针加1,如果队列满了,则尾指针回到队列开头,覆盖头指针所指向的元素。
  3. 出队操作:将队列头部元素出队,并将头指针加1,如果队列已经空了,则头指针回到队列开头。

嵌入式环形队列的实现可以使用数组或链表来实现。使用数组时,需要考虑队列满时需要覆盖队列头的元素,所以需要额外的逻辑来保证正确性。

嵌入式环形队列操作步骤(大致如此)

1)定义一个固定大小的数组

1#define QUEUE_SIZE 10
2int queue[QUEUE_SIZE];


2)定义两个指针,分别指向队列的起始位置和末尾位置

1int head = 0;  // 队列起始位置
2int tail = 0;  // 队列末尾位置



3)实现入队操作,即将元素添加到队列末尾。如果队列已满,则不能再添加元素

1void enqueue(int data) {
2    if ((tail + 1) % QUEUE_SIZE == head) {
3        // 队列已满
4        return;
5    }
6    queue[tail] = data;
7    tail = (tail + 1) % QUEUE_SIZE;
8}


4)实现出队操作,即将队列中的元素删除并返回。如果队列为空,则不能执行出队操作。

1int dequeue() {
2    if (head == tail) {
3        // 队列为空
4        return -1;
5    }
6    int data = queue[head];
7    head = (head + 1) % QUEUE_SIZE;
8    return data;
9}



5)实现查询队列大小的函数

1int queue_size() {
2    return (tail - head + QUEUE_SIZE) % QUEUE_SIZE;
3}


完整代码实现

1#define QUEUE_SIZE 10
 2int queue[QUEUE_SIZE];
 3int head = 0;
 4int tail = 0;
 5
 6void enqueue(int data) {
 7    if ((tail + 1) % QUEUE_SIZE == head) {
 8        // 队列已满
 9        return;
10    }
11    queue[tail] = data;
12    tail = (tail + 1) % QUEUE_SIZE;
13}
14
15int dequeue() {
16    if (head == tail) {
17        // 队列为空
18        return -1;
19    }
20    int data = queue[head];
21    head = (head + 1) % QUEUE_SIZE;
22    return data;
23}
24
25int queue_size() {
26    return (tail - head + QUEUE_SIZE) % QUEUE_SIZE;
27}


嵌入式消息队列

嵌入式消息队列的实现原理

嵌入式消息队列通常采用循环缓冲区实现,即使用一个数组作为缓冲区,通过头指针和尾指针来管理队列。消息队列的基本操作包括入队和出队


入队操作

入队操作将一个消息写入队列中,实现方法如下:

1void queue_push(queue_t *queue, void *data, size_t size)
 2{
 3    uint32_t next = (queue->tail + 1) % queue->capacity;
 4
 5    if (next == queue->head) {
 6        // 队列已满,不再添加数据
 7        return;
 8    }
 9
10    queue_item_t *item = &queue->items[queue->tail];
11    item->size = size;
12    item->data = malloc(size);
13    memcpy(item->data, data, size);
14
15    queue->tail = next;
16}


在入队操作中,我们首先检查队列是否已满。如果队列已满,就直接返回,不再添加数据。如果队列未满,则使用尾指针指向的空间来存储新的数据。为了避免新的数据覆盖旧的数据,我们需要动态分配一个新的内存空间,将数据复制到该空间中,并将其地址存储到队列的元素中。最后,我们将尾指针往后移动一个位置。

出队操作

出队操作将一个消息从队列中读取出来,并将其从队列中删除,实现方法如下

1void queue_pop(queue_t *queue, void *data, size_t *size)
 2{
 3    if (queue->head == queue->tail) {
 4        // 队列为空,无法读取数据
 5        return;
 6    }
 7
 8    queue_item_t *item = &queue->items[queue->head];
 9    *size = item->size;
10    memcpy(data, item->data, *size);
11
12    free(item->data);
13    queue->head = (queue->head + 1) % queue->capacity;
14}

在出队操作中,我们首先检查队列是否为空。如果队列为空,就直接返回,无法读取数据。如果队列非空,则使用头指针指向的空间来读取数据。我们将元素中存储的数据复制到指定的地址中,并返回数据的大小。最后,我们释放元素中分配的内存空间,并将头指针往后移动一个位置。

消息示例:

1#include <stdio.h>
  2#include <stdlib.h>
  3#include <stdint.h>
  4
  5// 消息队列结构体
  6typedef struct {
  7    uint8_t *buf;    // 指向队列缓存区的指针
  8    uint32_t head;   // 队头指针
  9    uint32_t tail;   // 队尾指针
 10    uint32_t size;   // 队列容量
 11    uint32_t count;  // 当前队列中的消息数量
 12} message_queue_t;
 13
 14// 创建一个消息队列
 15message_queue_t *message_queue_create(uint32_t size) {
 16    message_queue_t *mq = (message_queue_t *)malloc(sizeof(message_queue_t));
 17    if (mq == NULL) {
 18        return NULL;
 19    }
 20
 21    mq->buf = (uint8_t *)malloc(size);
 22    if (mq->buf == NULL) {
 23        free(mq);
 24        return NULL;
 25    }
 26
 27    mq->head = 0;
 28    mq->tail = 0;
 29    mq->size = size;
 30    mq->count = 0;
 31
 32    return mq;
 33}
 34
 35// 销毁一个消息队列
 36void message_queue_destroy(message_queue_t *mq) {
 37    if (mq == NULL) {
 38        return;
 39    }
 40
 41    if (mq->buf != NULL) {
 42        free(mq->buf);
 43    }
 44
 45    free(mq);
 46}
 47
 48// 向消息队列中发送一条消息
 49uint32_t message_queue_send(message_queue_t *mq, uint8_t *buf, uint32_t len) {
 50    if (mq == NULL || buf == NULL || len == 0) {
 51        return 0;
 52    }
 53
 54    // 如果队列已满,则无法发送消息
 55    if (mq->count >= mq->size) {
 56        return 0;
 57    }
 58
 59    // 将消息写入队列缓存区
 60    uint32_t i;
 61    for (i = 0; i < len; i++) {
 62        mq->buf[mq->tail] = buf[i];
 63        mq->tail = (mq->tail + 1) % mq->size;
 64    }
 65
 66    // 更新队列状态
 67    mq->count += len;
 68
 69    return len;
 70}
 71
 72// 从消息队列中接收一条消息
 73uint32_t message_queue_recv(message_queue_t *mq, uint8_t *buf, uint32_t len) {
 74    if (mq == NULL || buf == NULL || len == 0) {
 75        return 0;
 76    }
 77
 78    // 如果队列为空,则无法接收消息
 79    if (mq->count == 0) {
 80        return 0;
 81    }
 82
 83    // 读取队列缓存区中的消息
 84    uint32_t i;
 85    for (i = 0; i < len && i < mq->count; i++) {
 86        buf[i] = mq->buf[mq->head];
 87        mq->head = (mq->head + 1) % mq->size;
 88    }
 89
 90    // 更新队列状态
 91    mq->count -= i;
 92
 93    return i;
 94}
 95
 96// 获取消息队列中的消息数量
 97uint32_t message_queue_count(message_queue_t *mq) {
 98    if (mq == NULL) {
 99        return 0;
100    }
101
102    return mq->count;
103}


消息队列示例说明

上面的示例是一个基于循环队列实现的简单嵌入式消息队列的代码实现,下面详细说明其实现原理:

消息队列结构体

定义一个消息队列结构体,包含队列缓存区指针、队头指针、队尾指针、队列容量和当前队列中的消息数量等信息

1typedef struct {
2    uint8_t *buf;    // 指向队列缓存区的指针
3    uint32_t head;   // 队头指针
4    uint32_t tail;   // 队尾指针
5    uint32_t size;   // 队列容量
6    uint32_t count;  // 当前队列中的消息数量
7} message_queue_t;


创建消息队列

使用 message_queue_create 函数创建一个消息队列,该函数会动态分配一块内存用于存储消息队列结构体和队列缓存区,初始化消息队列的各个参数,并返回一个指向消息队列结构体的指针。

1message_queue_t *message_queue_create(uint32_t size) {
 2    message_queue_t *mq = (message_queue_t *)malloc(sizeof(message_queue_t));
 3    if (mq == NULL) {
 4        return NULL;
 5    }
 6
 7    mq->buf = (uint8_t *)malloc(size);
 8    if (mq->buf == NULL) {
 9        free(mq);
10        return NULL;
11    }
12
13    mq->head = 0;
14    mq->tail = 0;
15    mq->size = size;
16    mq->count = 0;
17
18    return mq;
19}


销毁消息队列

使用 message_queue_destroy 函数销毁一个消息队列,该函数会释放消息队列结构体和队列缓存区所占用的内存。


1void message_queue_destroy(message_queue_t *mq) {
 2    if (mq == NULL) {
 3        return;
 4    }
 5
 6    if (mq->buf != NULL) {
 7        free(mq->buf);
 8    }
 9
10    free(mq);
11}

发送消息

使用 message_queue_send 函数向消息队列中发送一条消息,该函数会将消息写入队列缓存区,并更新队列的状态,返回实际写入队列缓存区的消息长度。

1uint32_t message_queue_send(message_queue_t *mq, uint8_t *buf, uint32_t len) {
 2    if (mq == NULL || buf == NULL || len == 0) {
 3        return 0;
 4    }
 5
 6    // 如果队列已满,则无法发送消息
 7    if (mq->count >= mq->size) {
 8        return 0;
 9    }
10
11    // 将消息写入队列缓存区
12    uint32_t i;
13    for (i = 0; i < len; i++) {
14        mq->buf[mq->tail] = buf[i];
15        mq->tail = (mq->tail + 1) % mq->size;
16    }
17
18    // 更新队列状态
19    mq->count += len;
20
21    return len;
22}


相关实践学习
RocketMQ一站式入门使用
从源码编译、部署broker、部署namesrv,使用java客户端首发消息等一站式入门RocketMQ。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
2月前
|
消息中间件 存储 负载均衡
简单入门:消息队列的概念和应用
在复杂的系统架构中,组件间的通信是至关重要的问题。消息队列作为一种解决方案,能够使组件之间的通信更加高效、可靠。本文将从简单到复杂,逐步向您介绍消息队列的概念、使用场景以及如何实现。
98 3
|
3月前
|
消息中间件 API
接上文,轻量级消息队列的实现
接上文,轻量级消息队列的实现
|
9月前
|
消息中间件 存储 监控
消息队列优点
消息队列优点
|
9月前
|
消息中间件 监控 大数据
高并发设计系列-消息队列篇
高并发设计系列-消息队列篇
|
11月前
|
消息中间件
学习系统编程No.22【消息队列和信号量】
学习系统编程No.22【消息队列和信号量】
|
消息中间件 存储 负载均衡
消息队列是干什么的?底层原理是什么?
消息队列是干什么的?底层原理是什么?
680 0
|
消息中间件 Linux
Linux系统编程-进程间通信(消息队列)
前面文章介绍了Linux下进程的创建,管理,陆续介绍了进程间通信的方式:管道、内存映射、共享内存等。这篇文章继续介绍Linux的进程间通信方式消息队列。
295 0
Linux系统编程-进程间通信(消息队列)
|
消息中间件 存储 Java
消息队列:从选型到原理,一文带你全部掌握(二)
消息队列中间件重要吗?面试必问问题之一,你说重不重要。我有时会问同事,为啥你用RabbitMQ,不用Kafka,或者RocketMQ呢,他给我的回答“因为公司用的就是这个,大家都这么用”,如果你去面试,直接就被Pass,今天这篇文章,告诉你如何回答。 这篇文章纯理论,主要整理网络资料,肝了我整整一个月!文章依然延续上几篇的风格,很长,长到我只整理排版,手都整麻了。全文2.5万字,建议先收藏,后续面试、或者技术选型,再拿出来喵喵,不BB,上思维导图!
412 0
消息队列:从选型到原理,一文带你全部掌握(二)
|
消息中间件 存储 Java
消息队列:从选型到原理,一文带你全部掌握(一)
消息队列中间件重要吗?面试必问问题之一,你说重不重要。我有时会问同事,为啥你用RabbitMQ,不用Kafka,或者RocketMQ呢,他给我的回答“因为公司用的就是这个,大家都这么用”,如果你去面试,直接就被Pass,今天这篇文章,告诉你如何回答。 这篇文章纯理论,主要整理网络资料,肝了我整整一个月!文章依然延续上几篇的风格,很长,长到我只整理排版,手都整麻了。全文2.5万字,建议先收藏,后续面试、或者技术选型,再拿出来喵喵,不BB,上思维导图!
379 0
消息队列:从选型到原理,一文带你全部掌握(一)
|
消息中间件 存储 负载均衡
消息队列:从选型到原理,一文带你全部掌握(三)
消息队列中间件重要吗?面试必问问题之一,你说重不重要。我有时会问同事,为啥你用RabbitMQ,不用Kafka,或者RocketMQ呢,他给我的回答“因为公司用的就是这个,大家都这么用”,如果你去面试,直接就被Pass,今天这篇文章,告诉你如何回答。 这篇文章纯理论,主要整理网络资料,肝了我整整一个月!文章依然延续上几篇的风格,很长,长到我只整理排版,手都整麻了。全文2.5万字,建议先收藏,后续面试、或者技术选型,再拿出来喵喵,不BB,上思维导图!
188 0
消息队列:从选型到原理,一文带你全部掌握(三)