头文件
#ifndef __RING_BUFFER__
#define __RING_BUFFER__
#include <stdint.h>
#include <stddef.h>
#include <pthread.h>
#include <unistd.h>
struct ringbuffer
{
uint8_t *buffer_ptr;
uint16_t read_mirror : 1;
uint16_t read_index : 15;
uint16_t write_mirror : 1;
uint16_t write_index : 15;
int16_t buffer_size;
pthread_mutex_t mutex_lock;
};
enum ringbuffer_state
{
RINGBUFFER_EMPTY,
RINGBUFFER_FULL,
RINGBUFFER_HALFFULL,
RINGBUFFER_INVALID,
};
void ringbuffer_init(struct ringbuffer *rb, uint8_t *pool, int16_t size);
void ringbuffer_reset(struct ringbuffer *rb);
int16_t ringbuffer_put(struct ringbuffer *rb, const uint8_t *ptr, int16_t length);
int16_t ringbuffer_get(struct ringbuffer *rb, uint8_t *ptr, int16_t length);
int16_t ringbuffer_data_len(struct ringbuffer *rb);
struct ringbuffer* ringbuffer_create(int16_t length);
void ringbuffer_destroy(struct ringbuffer *rb);
static inline int16_t ringbuffer_get_size(struct ringbuffer *rb)
{
return rb->buffer_size;
}
#define ringbuffer_space_len(rb) ((rb)->buffer_size - ringbuffer_data_len(rb))
#endif
实现源码:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "ringbuffer.h"
void ringbuffer_init(struct ringbuffer *rb, uint8_t *pool, int16_t size)
{
if(rb == NULL)
{
return;
}
rb->read_mirror = rb->read_index = 0;
rb->write_mirror = rb->write_index = 0;
rb->buffer_ptr = pool;
rb->buffer_size = size;
pthread_mutex_init(&rb->mutex_lock,NULL);
return;
}
void ringbuffer_reset(struct ringbuffer *rb)
{
if(rb == NULL)
{
return;
}
rb->read_mirror = 0;
rb->read_index = 0;
rb->write_mirror = 0;
rb->write_index = 0;
return;
}
int16_t ringbuffer_put(struct ringbuffer *rb, const uint8_t *ptr, int16_t length)
{
int16_t size;
if(rb == NULL || length == 0)
{
return 0;
}
pthread_mutex_lock(&rb->mutex_lock);
size = ringbuffer_space_len(rb);
pthread_mutex_unlock(&rb->mutex_lock);
if(size == 0)
return 0;
if (size < length)
{
length = size;
}
if (rb->buffer_size - rb->write_index > length)
{
memcpy(&rb->buffer_ptr[rb->write_index], ptr, length);
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));
pthread_mutex_lock(&rb->mutex_lock);
rb->write_mirror = ~rb->write_mirror;
rb->write_index = length - (rb->buffer_size - rb->write_index);
pthread_mutex_unlock(&rb->mutex_lock);
return length;
}
int16_t ringbuffer_get(struct ringbuffer *rb, uint8_t *ptr, int16_t length)
{
if(rb == NULL || length == 0)
{
return 0;
}
int16_t size;
pthread_mutex_lock(&rb->mutex_lock);
size = ringbuffer_data_len(rb);
pthread_mutex_unlock(&rb->mutex_lock);
if (size == 0) return 0;
if (size < length)
{
length = size;
}
if (rb->buffer_size - rb->read_index > length)
{
memcpy(ptr, &rb->buffer_ptr[rb->read_index], length);
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));
pthread_mutex_lock(&rb->mutex_lock);
rb->read_mirror = ~rb->read_mirror;
rb->read_index = length - (rb->buffer_size - rb->read_index);
pthread_mutex_unlock(&rb->mutex_lock);
return length;
}
enum ringbuffer_state ringbuffer_status(struct ringbuffer *rb)
{
if (rb->read_index == rb->write_index)
{
if (rb->read_mirror == rb->write_mirror)
{
return RINGBUFFER_EMPTY;
}
else
{
return RINGBUFFER_FULL;
}
}
return RINGBUFFER_HALFFULL;
}
int16_t ringbuffer_data_len(struct ringbuffer *rb)
{
switch (ringbuffer_status(rb))
{
case RINGBUFFER_EMPTY:
return 0;
case RINGBUFFER_FULL:
return rb->buffer_size;
case 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);
}
}
测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include "ringbuffer.h"
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
struct ringbuffer *rb;
void *writer_thread(void *args)
{
uint8_t w = 0;
while(1)
{
/*pthread_mutex_lock(&mutex);*/
if(ringbuffer_put(rb, &w, 1) == 1)
w ++;
/*pthread_mutex_unlock(&mutex);*/
}
return NULL;
}
void *reader_thread(void *args)
{
uint8_t r;
while(1)
{
/*pthread_mutex_lock(&mutex);*/
if (ringbuffer_get(rb, &r, 1) == 1)
printf("---%x---\n", r);
/*pthread_mutex_unlock(&mutex);*/
}
return NULL;
}
int main(void)
{
unsigned int rc;
#if 1
uint8_t *buffer = (uint8_t *)malloc(1024 + sizeof(struct ringbuffer) + 16);
if(buffer == NULL) return -1;
if((unsigned long)buffer & 7 != 0)
{
printf("%s line %d not aligned.\n", __func__, __LINE__);
return -1;
}
rb=(struct ringbuffer *)buffer;
if(rb == NULL) return -1;
ringbuffer_init(rb, buffer + sizeof(struct ringbuffer), 1024);
printf("buffer = %p, sizeof(rb) = %ld.\n", buffer, sizeof(struct ringbuffer));
#else
static struct ringbuffer rbb;
uint8_t *buffer = (uint8_t *)malloc(1024);
if(buffer == NULL) return -1;
rb = &rbb;
memset(rb, 0x00,sizeof(struct ringbuffer));
memset(buffer, 0x00,1024);
ringbuffer_init(rb, buffer, 1024);
#endif
pthread_t writer;
rc = pthread_create(&writer, NULL, writer_thread, NULL);
if (rc)
{
printf("ERROR; return code is %d\n", rc);
return EXIT_FAILURE;
}
pthread_t reader;
rc = pthread_create(&reader, NULL, reader_thread, NULL);
if (rc)
{
printf("ERROR; return code is %d\n", rc);
return EXIT_FAILURE;
}
pthread_join(reader, NULL);
pthread_join(writer, NULL);
return EXIT_SUCCESS;
}
Makefile
all:
gcc main.c ringbuffer.c -o main -lpthread
测试:
另外,Linux内核也有一个不错的实现,参考:
./linux-5.4.99/drivers/media/dvb-core/dvb_ringbuffer.c
如上图所说,严蔚敏数据结构中的循环队列实现,少用一个空间的方法,约定队列头指针在尾指针的下一位上作为队列满状态的标志,而本文开头的循环队列实现,则是使用read_mirror_index, write_mirror_index两个标志位来判定是否发生了回绕。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXQSIZE 100
typedef struct {
unsigned long *base;
int front;
int rear;
} user_queue_t;
static user_queue_t uqueue;
int init_queue(user_queue_t *q)
{
q->base = (unsigned long *)malloc(MAXQSIZE * sizeof(unsigned long));
if (!q->base) {
printf("%s line %d, malloc queue failure.\n",
__func__, __LINE__);
exit(-1);
}
memset(q->base, 0x00, sizeof(unsigned long) * MAXQSIZE);
q->front = q->rear = 0;
return 0;
}
int queue_length(user_queue_t *q)
{
return (q->rear - q->front + MAXQSIZE) % MAXQSIZE;
}
int enqueue(user_queue_t *q, unsigned long e)
{
if ((q->rear + 1) % MAXQSIZE == q->front) {
printf("%s line %d, queue full.\n",
__func__, __LINE__);
return -1;
}
q->base[q->rear] = e;
q->rear = (q->rear + 1) % MAXQSIZE;
return 0;
}
unsigned long dequeue(user_queue_t *q)
{
unsigned long e;
if (q->front == q->rear) {
printf("%s line %d, queue is null.\n",
__func__, __LINE__);
return -1;
}
e = q->base[q->front];
q->front = (q->front + 1) % MAXQSIZE;
return e;
}
int main(void)
{
int i;
unsigned long e;
init_queue(&uqueue);
printf("%s line %d, queue_length %d.\n", __func__, __LINE__, queue_length(&uqueue));
enqueue(&uqueue, 100);
printf("%s line %d, queue_length %d.\n", __func__, __LINE__, queue_length(&uqueue));
e = dequeue(&uqueue);
printf("%s line %d, queue_length %d, e %ld.\n", __func__, __LINE__, queue_length(&uqueue), e);
for (i = 0; i < MAXQSIZE; i ++) {
enqueue(&uqueue, i);
printf("%s line %d, queue_length %d.\n", __func__, __LINE__, queue_length(&uqueue));
}
for (i = 0; i < MAXQSIZE; i ++) {
e = dequeue(&uqueue);
printf("%s line %d, queue_length %d, e %ld.\n", __func__, __LINE__, queue_length(&uqueue), e);
}
return 0;
}
除了上面使用额外的mirror标志来提高循环队列的利用率之外,还有另外一种方法,就是使用单调递增的read index/write index,而不让其回绕,代价就是在需要获取读写位置的时候,需要对read/write index进行取模运算,参考如下修改:https://gitee.com/tugouxp/ringbuf/blob/master/ringbufnew.c
一个实际的使用例子是在corundum网卡实现发送队列中,参考如下项目中的实现:
git clone https://github.com/corundum/corundum
doorbell 机制
中断是硬件给软件的doorbell, doorbell是软件给硬件的中断,下面是用C实现的一个doorbell的MODEL, 基于ring buffer,可以参考:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#define MAXQSIZE 1024
typedef struct {
long *base;
int front;
int rear;
} user_queue_t;
typedef struct {
long *base;
int front;
int rear;
} hwrb_queue_t;
static user_queue_t sw_queue;
static hwrb_queue_t hw_queue;
static pthread_mutex_t rbmutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t dbmutex = PTHREAD_MUTEX_INITIALIZER;
int init_queue_sw(user_queue_t *q, void *qbuf)
{
q->base = qbuf;
q->front = q->rear = 0;
return 0;
}
int init_queue_hw(hwrb_queue_t *q, void *qbuf)
{
q->base = qbuf;
q->front = q->rear = 0;
return 0;
}
int queue_length_sw(user_queue_t *q)
{
int ret;
pthread_mutex_lock(&rbmutex);
ret = (q->rear - q->front + MAXQSIZE) % MAXQSIZE;
pthread_mutex_unlock(&rbmutex);
return ret;
}
int queue_length_hw(hwrb_queue_t *q)
{
int ret;
pthread_mutex_lock(&rbmutex);
ret = (q->rear - q->front + MAXQSIZE) % MAXQSIZE;
pthread_mutex_unlock(&rbmutex);
return ret;
}
int enqueue(user_queue_t *q, long e)
{
pthread_mutex_lock(&rbmutex);
if ((q->rear + 1) % MAXQSIZE == q->front) {
printf("%s line %d, queue full.\n",
__func__, __LINE__);
pthread_mutex_unlock(&rbmutex);
return -1;
}
q->base[q->rear] = e;
q->rear = (q->rear + 1) % MAXQSIZE;
pthread_mutex_unlock(&rbmutex);
return 0;
}
long dequeue(user_queue_t *q)
{
long e;
pthread_mutex_lock(&rbmutex);
if (q->front == q->rear) {
printf("%s line %d, queue is null.\n",
__func__, __LINE__);
pthread_mutex_unlock(&rbmutex);
return -1;
}
e = q->base[q->front];
q->front = (q->front + 1) % MAXQSIZE;
pthread_mutex_unlock(&rbmutex);
return e;
}
static void ring_doorbell(unsigned long dbval)
{
pthread_mutex_lock(&dbmutex);
hw_queue.rear = dbval;
pthread_mutex_unlock(&dbmutex);
}
static void send_interupt(unsigned long itval)
{
pthread_mutex_lock(&dbmutex);
sw_queue.front = itval;
pthread_mutex_unlock(&dbmutex);
}
static void *hw_reader_thread(void *args)
{
int ret;
long e = 0, next = 0;
hwrb_queue_t *hwqueue = (hwrb_queue_t *)args;
while (1) {
ret = 0;
pthread_mutex_lock(&rbmutex);
if (hwqueue->front == hwqueue->rear) {
printf("%s line %d, queue is empty.\n",
__func__, __LINE__);
ret = -1;
} else {
e = hwqueue->base[hwqueue->front];
hwqueue->front = (hwqueue->front + 1) % MAXQSIZE;
send_interupt(hwqueue->front);
}
pthread_mutex_unlock(&rbmutex);
if (ret == 0) {
printf("%s line %d, queue_length %d, e %ld.\n",
__func__, __LINE__, queue_length_hw(hwqueue), e);
if (next != 0) {
if (e != next) {
printf("%s line %d, val is not continue %ld, %ld.\n",
__func__, __LINE__, e, next);
while (1);
}
}
next = e + 1;
}
}
return NULL;
}
void *sw_writer_thread(void *args)
{
int ret;
long e = 0;
user_queue_t *swqueue = (user_queue_t *)args;
while (1) {
ret = 0;
pthread_mutex_lock(&rbmutex);
if ((swqueue->rear + 1) % MAXQSIZE == swqueue->front) {
printf("%s line %d, queue is full.\n",
__func__, __LINE__);
ret = -1;
} else {
swqueue->base[swqueue->rear] = e;
swqueue->rear = (swqueue->rear + 1) % MAXQSIZE;
ring_doorbell(swqueue->rear);
}
pthread_mutex_unlock(&rbmutex);
if (ret == 0) {
printf("%s line %d, queue_length %d enqueue %ld.\n",
__func__, __LINE__, queue_length_sw(swqueue), e);
e ++;
#if 0
// test continue of hw read.
if (e == 100)
e = 101;
#endif
}
}
return NULL;
}
int main(void)
{
int ret;
void *qbuf;
pthread_t hw_thread;
pthread_t sw_thread;
qbuf = malloc(MAXQSIZE * sizeof(unsigned long));
if (!qbuf) {
printf("%s line %d, malloc queue failure.\n",
__func__, __LINE__);
exit(-1);
}
memset(qbuf, 0x00, sizeof(unsigned long) * MAXQSIZE);
init_queue_sw(&sw_queue, qbuf);
init_queue_hw(&hw_queue, qbuf);
ret = pthread_create(&hw_thread, NULL, hw_reader_thread, &hw_queue);
if (ret) {
printf("error, return code is %d\n", ret);
return -1;
}
ret = pthread_create(&sw_thread, NULL, sw_writer_thread, &sw_queue);
if (ret) {
printf("error, return code is %d\n", ret);
return -1;
}
pthread_join(hw_thread, NULL);
pthread_join(sw_thread, NULL);
return 0;
}