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

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

说明

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

环形队列是一种先进先出(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版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
消息中间件 Go 网络协议
剖析nsq消息队列(一) 简介及去中心化实现原理
分布式消息队列nsq,简单易用,去中心化的设计使nsq更健壮,nsq充分利用了go语言的goroutine和channel来实现的消息处理,代码量也不大,读不了多久就没了。后期的文章我会把nsq的源码分析给大家看。
1685 0
|
4月前
|
消息中间件 网络性能优化 开发工具
消息队列 MQ使用问题之如何确保消息的唯一性
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
4月前
|
消息中间件 C语言 RocketMQ
消息队列 MQ操作报错合集之出现"Connection reset by peer"的错误,该如何处理
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
4月前
|
消息中间件 Java C语言
消息队列 MQ使用问题之在使用C++客户端和GBase的ESQL进行编译时出现core dump,该怎么办
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
18天前
|
消息中间件 存储 Kafka
MQ 消息队列核心原理,12 条最全面总结!
本文总结了消息队列的12个核心原理,涵盖消息顺序性、ACK机制、持久化及高可用性等内容。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
2月前
|
消息中间件
手撸MQ消息队列——循环数组
队列是一种常用的数据结构,类似于栈,但采用先进先出(FIFO)的原则。生活中常见的排队场景就是队列的应用实例。在数据结构中,队列通常用数组实现,包括入队(队尾插入元素)和出队(队头移除元素)两种基本操作。本文介绍了如何用数组实现队列,包括定义数组长度、维护队头和队尾下标(front 和 tail),并通过取模运算解决下标越界问题。此外,还讨论了队列的空与满状态判断,以及并发和等待机制的实现。通过示例代码展示了队列的基本操作及优化方法,确保多线程环境下的正确性和高效性。
38 0
手撸MQ消息队列——循环数组
|
3月前
|
消息中间件 存储 缓存
一个用过消息队列的人,竟不知为何要用 MQ?
一个用过消息队列的人,竟不知为何要用 MQ?
159 1
|
4月前
|
消息中间件 开发工具 RocketMQ
消息队列 MQ使用问题之一直连接master失败,是什么原因
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
4月前
|
消息中间件 Prometheus 监控
消息队列 MQ使用问题之如何将旧集群的store目录迁移到新集群
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
4月前
|
消息中间件 安全 PHP
消息队列 MQ使用问题之如何获取PHP客户端代码
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。