RT-Thread 隐藏的宝藏之ringbuff

9 篇文章 8 订阅
8 篇文章 0 订阅

一,什么是 ringbuff

ringbuff: 翻译成中文就是环形缓冲区。

网上关于 ringbuff 的介绍已经非常多了,我也分享一下我对 ringbuff 的认识。ringbuff 可以理解为学校操场上的跑道,ringbuff 初始化就是新建了一个 400 的环形操场跑道,跑道上有 2 名同学 (writeread),每次写入一个数据,write 往前跑一步,每次读一个数据,read 往前跑一步,read永远不可以超过 write。刚开始起点都是 0 ,如果 write 写的速度慢了,被 read 追上了,那么 ringbuff 缓存数据就为空。 如果 write 写的速度快了,read 读慢了,被 write 跑了一圈又追上了,那么 ringbuff 缓存区就是满。

这里借用一下他人的图,原图地址 , 侵删。

img

二. 怎么使用 ringbuff

在知道怎么使用 ringbuff 前先要看一下,ringbuff 的结构体:

struct rt_ringbuffer
{
    rt_uint8_t *buffer_ptr;

    rt_uint16_t read_mirror : 1;
    rt_uint16_t read_index : 15;
    rt_uint16_t write_mirror : 1;
    rt_uint16_t write_index : 15;
     
    rt_int16_t buffer_size;
};

*buffer_ptr : 缓冲区指针

read_mirror :读取镜像。可以理解为一张白纸,读完了就翻过一页再读

read_index :读数据的位置

write_mirror : 写入镜像。可以理解为一张白纸,写完了就翻过一页再写

write_index : 写入数据的位置

buffer_size : 缓存区大小

这里有一个 C 语言 的小知识点:位域 。 这里使用了一个 u16 存储了 read_mirrorread_index , 所以 index 最大只能到 32767 。这个大小满足绝大数的场景。

1 . 初始化 ringbuff
void rt_ringbuffer_init(struct rt_ringbuffer *rb,
                        rt_uint8_t           *pool,
                        rt_int16_t            size)

*rb : 环形缓冲区句柄

*pool : 缓存区指针

size : 缓存区大小

2 . 往 ringbuff 写入数据
rt_size_t rt_ringbuffer_put(struct rt_ringbuffer *rb,
                            const rt_uint8_t     *ptr,
                            rt_uint16_t           length)

*rb : 环形缓冲区句柄

*ptr : 写入数据的指针

length : 写入数据的长度

3 . 强制往 ringbuff 写入数据
rt_size_t rt_ringbuffer_put_force(struct rt_ringbuffer *rb,
                            const rt_uint8_t     *ptr,
                            rt_uint16_t           length)

*rb : 环形缓冲区句柄

*ptr : 写入数据的指针

length : 写入数据的长度

强制写数据使用场景:ringbuff 缓存区中的数据已经满了,但是还要写入数据。这种情况将会导致前面写入的数据被覆盖,导致数据丢失,请谨慎使用。

4. 从 ringbuff 读取数据
rt_size_t rt_ringbuffer_get(struct rt_ringbuffer *rb,
                            rt_uint8_t           *ptr,
                            rt_uint16_t           length)

*rb : 环形缓冲区句柄

*ptr : 读数据的存放的位置

length : 读取的长度

5. 往 ringbuff 写入一个字节的数据
rt_size_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const rt_uint8_t ch)

*rb : 环形缓冲区句柄

ch : 将要写入的数据

6. 强制往 ringbuff 写入一个字节的数据
rt_size_t rt_ringbuffer_putchar_force(struct rt_ringbuffer *rb, const rt_uint8_t ch)

*rb : 环形缓冲区句柄

ch : 将要写入的数据

注意强制写入将会导致数据被覆盖。

7. 从 ringbuff 读取一个字节的数据
rt_size_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, rt_uint8_t *ch)

*rb : 环形缓冲区句柄

*ch : 存储读取的一个字节的变量

8. 获取 ringbuff 中已经使用的控件
rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb)

*rb : 环形缓冲区句柄

9. 复位 ringbuff
void rt_ringbuffer_reset(struct rt_ringbuffer *rb)

*rb : 环形缓冲区句柄

10. 创建 ringbuff
struct rt_ringbuffer* rt_ringbuffer_create(rt_uint16_t size)

size : 唤醒缓冲区的大小

rt_ringbuffer_create 在函数内部实现了 pool 然后调用了 rt_ringbuffer_init

11. 销毁 ringbuff
void rt_ringbuffer_destroy(struct rt_ringbuffer *rb)

这个只能销毁调用 rt_ringbuffer_create 创建的 ring_buff

12 . 获取 ringbuff 的大小
rt_inline rt_uint16_t rt_ringbuffer_get_size(struct rt_ringbuffer *rb)

三. 原理分析 ringbuff

1. 初始化 ringbuff
void rt_ringbuffer_init(struct rt_ringbuffer *rb,
                        rt_uint8_t           *pool,
                        rt_int16_t            size)
{
    /* initialize read and write index */
    rb->read_mirror = rb->read_index = 0;// 初始化时 读下标设置为 0
    rb->write_mirror = rb->write_index = 0;// 初始化时 写下标设置为 0

    /* set buffer pool and size */
    rb->buffer_ptr = pool;// 设置 ring_buff 的缓存区地址
    rb->buffer_size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);// 保证 size 的大小是对齐的
}
2. 往 ringbuff 写入数据
rt_size_t rt_ringbuffer_put(struct rt_ringbuffer *rb,
                            const rt_uint8_t     *ptr,
                            rt_uint16_t           length)
{
    rt_uint16_t size;

    /* whether has enough space */
    size = rt_ringbuffer_space_len(rb);// 获取 ring_buff 中可用空间的大小

    /* no space */
    if (size == 0)
        return 0; // 如果空间不够 直接返回

    /* drop some data */
    if (size < length) // 如果缓存区的控件不够保存这一次数据, 则把能够写入的这一部分数据写进去
        length = size;

    if (rb->buffer_size - rb->write_index > length)
    {// 这里判断的是数据能够在第一个镜像写完,就直接写入当前的镜像,可以理解为在白纸上写数据,这个时候不需要翻页
        /* read_index - write_index = empty space */
        memcpy(&rb->buffer_ptr[rb->write_index], ptr, length);
        /* this should not cause overflow because there is enough space for
         * length of data in current mirror */
        rb->write_index += length;
        return length; // 返回写入数据的长度
    }

    memcpy(&rb->buffer_ptr[rb->write_index],// 把能够写入第一个镜像的数据线写入第一个镜像的缓存区
           &ptr[0],
           rb->buffer_size - rb->write_index);
    memcpy(&rb->buffer_ptr[0],// 前一个镜像写满了,就要从头写了,这里就体现了循环
           &ptr[rb->buffer_size - rb->write_index],
           length - (rb->buffer_size - rb->write_index));

    /* we are going into the other side of the mirror */
    rb->write_mirror = ~rb->write_mirror;// 完成 镜像 0 和 1 的翻转
    rb->write_index = length - (rb->buffer_size - rb->write_index);// 重新设置写数据的小标

    return length;// 返回写入数据的长度
}
3. 强制往 ringbuff 写入数据
rt_size_t rt_ringbuffer_put_force(struct rt_ringbuffer *rb,
                            const rt_uint8_t     *ptr,
                            rt_uint16_t           length)
{
    rt_uint16_t space_length;

    space_length = rt_ringbuffer_space_len(rb); // 获取可用缓存区大小

    if (length > rb->buffer_size) // 如果长度超过了当前 ring_buff 的最大值
    {
        ptr = &ptr[length - rb->buffer_size];// 获取超出部分的地址
        length = rb->buffer_size;// lenght 设置为 ring_buff 的大小
    }

    if (rb->buffer_size - rb->write_index > length)// 如果足够存储这次将要写入的数据
    {
        /* read_index - write_index = empty space */
        memcpy(&rb->buffer_ptr[rb->write_index], ptr, length);// 拷贝数据
        /* this should not cause overflow because there is enough space for
         * length of data in current mirror */
        rb->write_index += length;// 移动 write index

        if (length > space_length) // 如果长度大于可用缓存区大小
            rb->read_index = rb->write_index;// 因为数据已经被覆盖掉了,所以 read 的 index 必须往前移动

        return length; // 返回写入数据的长度
    }

    memcpy(&rb->buffer_ptr[rb->write_index],// 拷贝数据在当前镜像
           &ptr[0],
           rb->buffer_size - rb->write_index);
    memcpy(&rb->buffer_ptr[0],// 拷贝数据到下一个镜像
           &ptr[rb->buffer_size - rb->write_index],
           length - (rb->buffer_size - rb->write_index));

    /* we are going into the other side of the mirror */
    rb->write_mirror = ~rb->write_mirror;// 镜像翻转
    rb->write_index = length - (rb->buffer_size - rb->write_index); // 写完数据后移动 write index

    if (length > space_length) // 如果长度大于可用空间
    {
        rb->read_mirror = ~rb->read_mirror; // 翻转读镜像
        rb->read_index = rb->write_index; // 移动 read_index 到 write_index 
    }

    return length;
}
4. 从 ringbuff 获取数据
rt_size_t rt_ringbuffer_get(struct rt_ringbuffer *rb,
                            rt_uint8_t           *ptr,
                            rt_uint16_t           length)
{
    rt_size_t size;
    
    /* whether has enough data  */
    size = rt_ringbuffer_data_len(rb);// 检查当前 ringbuff 中是否有足够的空间

    /* no data */
    if (size == 0)
        return 0; // 如果没有数据则直接返回

    /* less data */
    if (size < length)
        length = size;// 如果已经存在的数据小于想要获取的长度,那么就会把长度设置为 size 

    if (rb->buffer_size - rb->read_index > length)// 如果在一个镜像中就能获取到所有的数据
    {
        /* copy all of data */
        memcpy(ptr, &rb->buffer_ptr[rb->read_index], length);// 拷贝数据
        /* this should not cause overflow because there is enough space for
         * length of data in current mirror */
        rb->read_index += length; // 移动读下标
        return length;// 返回读取到的长度
    }
    /* 能执行到这里说明,一个镜像内无法读取到所有的数据 */
    memcpy(&ptr[0],
           &rb->buffer_ptr[rb->read_index],
           rb->buffer_size - rb->read_index);// 拷贝当前镜像的数据
    memcpy(&ptr[rb->buffer_size - rb->read_index],
           &rb->buffer_ptr[0],
           length - (rb->buffer_size - rb->read_index));// 拷贝剩余的数据

    /* we are going into the other side of the mirror */
    rb->read_mirror = ~rb->read_mirror;// 翻转镜像
    rb->read_index = length - (rb->buffer_size - rb->read_index);// 移动读下标

    return length;
}
5. 往 ringbuff 中写入一个字符
rt_size_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const rt_uint8_t ch)
{
    /* whether has enough space */
    if (!rt_ringbuffer_space_len(rb)) // 没有足够的空间就直接返回了
        return 0;

    rb->buffer_ptr[rb->write_index] = ch;// 把这个字符写入到缓冲区的指定位置

    /* flip mirror */
    if (rb->write_index == rb->buffer_size-1)// 检查写入这个字符后,当前镜像是否写满
    {
        rb->write_mirror = ~rb->write_mirror;// 翻转镜像
        rb->write_index = 0;// 设置下标为0
    }
    else
    {
        rb->write_index++; // 下标加1
    }

    return 1; // 写入一个字符,返回 1
}
6. 往 ringbuff 强制写入一个字符
rt_size_t rt_ringbuffer_putchar_force(struct rt_ringbuffer *rb, const rt_uint8_t ch)
{
    enum rt_ringbuffer_state old_state;

    old_state = rt_ringbuffer_status(rb);// 获取状态

    rb->buffer_ptr[rb->write_index] = ch;// 写入数据

    /* flip mirror */
    if (rb->write_index == rb->buffer_size-1) // 检查当前镜像是不是满了
    {
        rb->write_mirror = ~rb->write_mirror; // 翻转写镜像
        rb->write_index = 0;// 翻转之后设置下标为 0
        if (old_state == RT_RINGBUFFER_FULL) // 如果 ringbuff 的状态是满
        {
            rb->read_mirror = ~rb->read_mirror; // 翻转读镜像
            rb->read_index = rb->write_index; // 设置读下标和写下标一致
        }
    }
    else
    {
        rb->write_index++; // 写下标加1
        if (old_state == RT_RINGBUFFER_FULL)
            rb->read_index = rb->write_index;// 如果满,设置读下标等于写下标
    }

    return 1; // 写入一个字符,返回1
}
7. 从 ringbuff 获取一个字符
rt_size_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, rt_uint8_t *ch)
{
    /* ringbuffer is empty */
    if (!rt_ringbuffer_data_len(rb)) // 检查 ringbuff 是否为空
        return 0;

    /* put character */
    *ch = rb->buffer_ptr[rb->read_index];// 获取当前读下标的数据

    if (rb->read_index == rb->buffer_size-1)// 如果当前镜像满了
    {
        rb->read_mirror = ~rb->read_mirror;// 翻转镜像
        rb->read_index = 0; // 设置读数据的下标为0
    }
    else
    {
        rb->read_index++; // 下标加1
    }

    return 1;// 读取一个字节,返回1
}
8. 获取 ringbuff 中数据的长度
rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb)
{
    switch (rt_ringbuffer_status(rb)) // 获取 ringbuff 的状态
    {
    case RT_RINGBUFFER_EMPTY:
        return 0; // 空就返回 0
    case RT_RINGBUFFER_FULL:
        return rb->buffer_size; // 满就返回缓冲区的大小
    case RT_RINGBUFFER_HALFFULL:// 半满
    default:
        if (rb->write_index > rb->read_index) // 如果在同一镜像
            return rb->write_index - rb->read_index;// 返回下标差
        else
            return rb->buffer_size - (rb->read_index - rb->write_index);// 如果不在同一镜像,通过计算获取数据的长度
    };
}
9. 重置 ringbuff
void rt_ringbuffer_reset(struct rt_ringbuffer *rb)
{
    rb->read_mirror = 0;
    rb->read_index = 0;
    rb->write_mirror = 0;
    rb->write_index = 0;
} // 所有的值都设置为 0
10. 创建一个 ringbuff
struct rt_ringbuffer* rt_ringbuffer_create(rt_uint16_t size)
{
    struct rt_ringbuffer *rb;
    rt_uint8_t *pool;

    size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);// 大小做字节对齐

    rb = (struct rt_ringbuffer *)rt_malloc(sizeof(struct rt_ringbuffer));// 申请内存
    if (rb == RT_NULL)
        goto exit;

    pool = (rt_uint8_t *)rt_malloc(size);// 申请数据缓冲区内存
    if (pool == RT_NULL)
    {
        rt_free(rb);
        rb = RT_NULL;
        goto exit;
    }
    rt_ringbuffer_init(rb, pool, size);// 初始化 ringff

exit:
    return rb;
}

这个 API 简化了使用 ringbuff 所需要的 形参。本质还是调用了 rt_ringbuffer_init

11. 摧毁 ringbuff
void rt_ringbuffer_destroy(struct rt_ringbuffer *rb)
{
    rt_free(rb->buffer_ptr);
    rt_free(rb);// 释放申请的内存
}

四. 总结

  1. ringbuff 读写数据互不干扰,比普通的 buff使用更加灵活。
  2. ringbuff 的使用场景非常多,可以解决读写速度不一致的问题,RT-Thread 的组件框架中也使用到了,RT-Thread 已经提供了就不需要我们自己再重复造轮子了,这部分代码也很少,想要数量掌握,还是建议读一下源码。
  • 5
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值