环形队列的原理介绍及其代码实现

1 环形队列是什么

队列是一种常用的数据结构,这种结构保证了数据是按照“先进先出”的原则进行操作的,即最先进去的元素也是最先出来的元素.环形队列是一种特殊的队列结构,保证了元素也是先进先出的,但与一般队列的区别是,他们是环形的,即队列头部的上个元素是队列尾部,通常是容纳元素数固定的一个闭环。但是实际上还是一个

2环形队列的优缺点

1、 保证元素是先进先出的
2、元素空间可以重复利用
3、为多线程数据通信提供了一种高效的机制。
在最典型的生产者消费者模型中,如果引入环形队列,那么生成者只需要生成“东西”然后放到环形队列中即可,而消费者只需要从环形队列里取“东西”并且消费即可,没有任何锁或者等待,巧妙的高效实现了多线程数据通信。

3环形队列的应用场景

应用于需要高效且频繁进行多线程通信传递数据的场景
dpdk中用的ringbuffer环形队列,给dpdk提供高效的线程间通信,其ringbuffer做的比较高效,因为其环形队列的出队列和进队列只有一个临界值的++操作,这个操作是具有原子性的,底层是汇编指令集实现的++原子操作。

4环形队列的原理分析

环形队列其实质还是一块内存空间,只不过在队尾塞满数据后,就还是从最开始的地方开始处理数据。
在这里插入图片描述
在这里插入图片描述
判断队列为空
font == rear
判断队列满了
(rear+1) % maxSize = font.

下面的代码实现有两个版本的,
1 支持一写一读的生产消费模式
2 支持些多读的生产消费模式

5环形队列的代码分析和实现

版本一


typedef struct ringbuffer_s {
    char * buf;
    unsigned int size; // buffer 的大小
    unsigned int write_pos; // 下一个可写的位置
    unsigned int read_pos; // 下一个可读的位置
} ringbuffer_t;

环形队列结构

初始化

void rb_init(ringbuffer_t *r, unsigned int sz) {
    if (!is_power_of_two(sz)) sz = roundup_power_of_two(sz);
    r->buf = (char *)malloc(sz * sizeof(char));
    r->size = sz;
    r->write_pos = 0;
    r->read_pos = 0;
}

环形队列初始化
1 申请环形队列的存储数据的空间
2 设置环形队列能够存储多大的数据量。
4 设置读和写 的位置索引

读取数据

unsigned int rb_read(ringbuffer_t *r, char* buf, unsigned int sz) {
    if (rb_isempty(r)) {
        // buffer 为空
        return 0;
    }
    unsigned int i;
    sz = min(sz, r->write_pos - r->read_pos);

#ifdef USE_BARRIER
// 确保在开始移动buffer的时候,先采样好 write_pos
    smp_rmb();
#endif

    i = min(sz, r->size - (r->read_pos & (r->size - 1)));
    memcpy(buf, r->buf+(r->read_pos & (r->size - 1)), i);
    memcpy(buf+i, r->buf, sz-i);
    
#ifdef USE_BARRIER
// 确保移动数据在修改 read_pos 之前
    smp_mb();
#endif
    r->read_pos += sz;
    return sz;
}

smp_mb() 设置读内存屏障的汇编指令函数,

将环形队列中的数据取出来然后就,将读的索引加取出来数据的大小。
在这里插入图片描述

写入数据

unsigned int rb_write(ringbuffer_t *r, char* buf, unsigned int sz) {
    if (sz > rb_remain(r)) {
        // 剩余空间不足
        return 0;
    }

#ifdef USE_BARRIER
// 确保在开始移动buffer的时候,先采样好 read_pos
    smp_mb();
#endif
    unsigned int i;
    i = min(sz, r->size - (r->write_pos & (r->size - 1)));

    memcpy(r->buf + (r->write_pos & (r->size - 1)), buf, i);
    memcpy(r->buf, buf+i, sz-i);

#ifdef USE_BARRIER
// 确保移动数据在修改 write_pos 之前
    smp_wmb();
#endif

    r->write_pos += sz;
    return sz;
}

将数据写如环形队列,将write_idx的大小加上size。

这个版本没有用到锁,其代码简洁高效,支持一写一读的场景下。

版本二

环形队列的结构

struct ring_buffer
{
    void      *buffer;     //缓冲区
    uint32_t     size;       //大小
    uint32_t     in;         //入口位置
    uint32_t       out;        //出口位置
    pthread_mutex_t *f_lock;    //互斥锁
};

相比于版本一多了一个互斥锁,可以用于多线程读写。其性能和代码简度比第一个版本差一些。反正说各有特色把。

环形队列的初始化


//初始化缓冲区
struct ring_buffer* ring_buffer_init(void *buffer, uint32_t size, pthread_mutex_t *f_lock)
{
    assert(buffer);
    struct ring_buffer *ring_buf = NULL;
    #if 0
    if (!is_power_of_2(size))
    {
    fprintf(stderr,"size must be power of 2.\n");
        return ring_buf;
    }
    #endif
    ring_buf = (struct ring_buffer *)malloc(sizeof(struct ring_buffer));
    if (!ring_buf)
    {
        Console_Error("Failed to malloc memory,errno:%u,reason:%s",
            errno, strerror(errno));
        return ring_buf;
    }
    memset(ring_buf, 0, sizeof(struct ring_buffer));
    ring_buf->buffer = buffer;
    ring_buf->size = size;
    ring_buf->in = 0;
    ring_buf->out = 0;
    ring_buf->f_lock = f_lock;
    return ring_buf;
}

读取环形队列数据

uint32_t __ring_buffer_get(struct ring_buffer *ring_buf, void * buffer, uint32_t size)
{
    assert(ring_buf || buffer);
    uint32_t len = 0;
    size  = min(size, ring_buf->in - ring_buf->out);
    uint32_t RingbufOutRemainder = (ring_buf->out) % (ring_buf->size);
    /* first get the data from fifo->out until the end of the buffer */
    len = min(size, ring_buf->size - RingbufOutRemainder);
    memcpy(buffer, ring_buf->buffer + RingbufOutRemainder, len);
    /* then get the rest (if any) from the beginning of the buffer */
    memcpy(buffer + len, ring_buf->buffer, size - len);
    ring_buf->out += size;
    return size;
}

写入环形队列

uint32_t __ring_buffer_put(struct ring_buffer *ring_buf, void *buffer, uint32_t size)
{
    assert(ring_buf || buffer);
    uint32_t len = 0;
    //其实不用下面两句判断环形队列满
    // if(__ring_buffer_len(ring_buf) >= ring_buf->size)
    //     return 0;//环形队列已满
    size = min(size, ring_buf->size - ring_buf->in + ring_buf->out);//size变成环形缓存区最大可以字节数。
    uint32_t RingbufInRemainder = (ring_buf->in) % (ring_buf->size);

    /* first put the data starting from fifo->in to buffer end */
    len  = min(size, ring_buf->size - RingbufInRemainder);
    memcpy(ring_buf->buffer + RingbufInRemainder, buffer, len);
    /* then put the rest (if any) at the beginning of the buffer */
    memcpy(ring_buf->buffer, buffer + len, size - len);
    ring_buf->in += size;
    return size;
}

源码地址

版本一二地址:
https://github.com/xiaoyeyihao/xioayeyihao.github.io/tree/master/circular_queue

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
环形队列在单片机中的应用非常广泛,特别是在实时数据处理和缓冲区管理方面。以下是一些单片机应用中常见的环形队列使用场景: 1. 串口通信:在串口通信过程中,接收和发送数据往往需要使用环形队列来进行缓冲和管理。通过环形队列,可以实现接收数据的缓存、数据的解析以及发送数据的排队等功能。 2. 数据采集与处理:在数据采集系统中,传感器产生的数据需要进行实时采集和处理。环形队列可以用于缓存采集到的数据,使得数据不会丢失,并且可以提供给后续的处理模块进行实时处理。 3. 多任务数据传递:在多任务系统中,不同任务之间可能需要进行数据传递。通过使用环形队列,可以实现任务之间的数据传递和通信,保证数据的安全性和可靠性。 4. 实时操作系统(RTOS):在RTOS中,任务调度是基于时间片或优先级的。环形队列可用于任务之间的消息传递和通信,例如任务间的事件通知、资源共享等。 5. 缓存和循环存储:在一些特定的应用场景中,需要使用环形队列实现缓存和循环存储的功能。例如,在音频处理中,可以使用环形队列作为缓存来存储音频数据,以便进行实时处理。 总之,环形队列在单片机应用中是一种非常有效的数据结构,可以帮助处理实时数据、任务间通信和缓冲区管理等问题。通过合理地使用环形队列,可以提高系统的稳定性、实时性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值