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,则截断。若数据长度大于尾部到当前读指针的长度,则需分两次读取操作。