RingBuffer - 原理与实现

1. 原理

参考http://blog.chinaunix.net/uid-9407839-id-3954445.html

      环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区中可写的缓冲区。通过移动读指针和写指针就可以实现缓冲区的数据读取和写入。在通常情况下,环形缓冲区的读用户仅仅会影响读指针,而写用户仅仅会影响写指针。如果仅仅有一个读用户和一个写用户,那么不需要添加互斥保护机制就可以保证数据的正确性。如果有多个读写用户访问环形缓冲区,那么必须添加互斥保护机制来确保多个用户互斥访问环形缓冲区。

图1、图2和图3是一个环形缓冲区的运行示意图。图1是环形缓冲区的初始状态,可以看到读指针和写指针都指向第一个缓冲区处;图2是向环形缓冲区中添加了一个数据后的情况,可以看到写指针已经移动到数据块2的位置,而读指针没有移动;图3是环形缓冲区进行了读取和添加后的状态,可以看到环形缓冲区中已经添加了两个数据,已经读取了一个数据。

 

2. 代码实现(以RT-Thread源代码为例)

结构体定义

struct rt_ringbuffer
{
    rt_uint16_t read_index, write_index;
    rt_uint8_t *buffer_ptr;
    rt_uint16_t buffer_size;
};

 

初始化函数

void rt_ringbuffer_init(struct rt_ringbuffer *rb,
                        rt_uint8_t           *pool,
                        rt_uint16_t           size)
{
    RT_ASSERT(rb != RT_NULL);

    /* initialize read and write index */
    rb->read_index = rb->write_index = 0;

    /* set buffer pool and size */
    rb->buffer_ptr = pool;
    rb->buffer_size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);
}

写入函数

rt_size_t rt_ringbuffer_put(struct rt_ringbuffer *rb,
                            const rt_uint8_t     *ptr,
                            rt_uint16_t           length)
{
    rt_uint16_t size;
    rt_uint16_t mask;
    rt_uint16_t write_position;
    
    RT_ASSERT(rb != RT_NULL);

    mask = rb->buffer_size - 1;
    /* whether has enough space */
    size = rb->buffer_size - (rb->write_index - rb->read_index);

    /* no space */
    if (size == 0)
        return 0;
    /* drop some data */
    if (size < length)
        length = size;

    write_position = (rb->write_index & mask);
    if (rb->buffer_size - write_position> length)
    {
        /* read_index - write_index = empty space */
        memcpy(&rb->buffer_ptr[write_position], ptr, length);
    }
    else
    {
        memcpy(&rb->buffer_ptr[write_position], ptr,
               rb->buffer_size - write_position);
        memcpy(&rb->buffer_ptr[0], &ptr[rb->buffer_size - write_position],
               length - (rb->buffer_size - write_position));
    }
    rb->write_index += length;

    return length;
}

  其中

mask = rb->buffer_size - 1;
read_position = rb->read_index & mask;

 是可以实现写指针绕回到缓冲区头部后归0,注意该操作需要在获得size后才能执行。size为缓冲区空余的空间,若需要写入的数据长度超出size,则截断。若数据长度大于缓冲区尾部到当前写指针的长度,则需分两次写入操作。

 读出函数

rt_size_t rt_ringbuffer_get(struct rt_ringbuffer *rb,
                            rt_uint8_t           *ptr,
                            rt_uint16_t           length)
{
    rt_size_t size;
    rt_uint16_t mask;
    rt_uint16_t read_position;

    RT_ASSERT(rb != RT_NULL);
    /* whether has enough data  */
    mask = rb->buffer_size - 1;
    size = rb->write_index - rb->read_index;

    /* no data */
    if (size == 0)
        return 0;
    /* less data */
    if (size < length)
        length = size;

    read_position = rb->read_index & mask;
    if (rb->buffer_size - read_position >= length)
    {
        /* copy all of data */
        memcpy(ptr, &rb->buffer_ptr[read_position], length);
    }
    else
    {
        /* copy first and second */
        memcpy(ptr, &rb->buffer_ptr[read_position],
               rb->buffer_size - read_position);
        memcpy(&ptr[rb->buffer_size - read_position], &rb->buffer_ptr[0],
               length - rb->buffer_size + read_position);
    }
    rb->read_index += length;

    return length;
}

其中size为当前缓冲区中含有数据的长度,若需要读出的数据长度超出size,则截断。若数据长度大于尾部到当前读指针的长度,则需分两次读取操作。

猜你喜欢

转载自lanxinyuchs.iteye.com/blog/2085557
今日推荐