Overview¶
NAME¶
rb_overview - quick overview of librb ring buffer library
SYNOPSIS¶
librb is a ring buffer FIFO queue with POSIX-like API. librb is very versatile library with multiple features to make your life simpler.
classic ring buffer, you don’t have to put elements one by one on a queue, can put multiple elements on queue with 1 call
full multi-thread awareness, with thread blocking, and read/write working simultaneously, non-blocking operations are supported
automatically grow buffer when reaching max buffer, with configurable hard limit on max growable size
malloc() less mode for embedded
claim/commit API allows you to get ring buffers internal buffer to fill it in custom way, or even passing it to POSIX read/write function
dynamic mode, that allows you to put elements with arbitrary sizes
everything is written in one header and c-source file, easy to copy and use in your project
Which features are available are defined by flags you use. Some features can be disable during compile time to save on code size.
librb - library that provides fast, easy to use ring buffer implementation. Its interface is very similar to read/write functions from POSIX. Basic usage can be done with only 4 functions:
function |
description |
|---|---|
create new ring buffer, allocate all needed buffers |
|
write arbitrary number of elements into ring buffer |
|
read arbitrary number of elements from ring buffer |
|
destroy ring buffer once you are done with it |
Flags for rb_new(3) or rb_init(3):
function |
description |
|---|---|
Create a non-blocking ring buffer (for multi-thread buffer only) |
|
Create thread aware ring buffer |
|
Create dynamic buffer, where you can put object of any size on the buffer |
|
Automatically increase size of a buffer, if you use all buffer |
|
Automatically round count passed during creation to next power of two value |
Claim/commit API, useful when you want to pass internal ring buffer’s buffer directly to read(2)/write(2) functions, or work directly on buffer for any reason.
function |
description |
|---|---|
claim buffer for reading |
|
commit data to ring buffer and release buffer |
|
commit data then immediately claim another buffer without unlocking |
|
claim buffer for writing |
|
commit data to ring buffer and release buffer |
|
commit data then immediately claim another buffer without unlocking |
Ring buffer control related functions:
function |
description |
|---|---|
quickly drop all data from ring buffer |
|
quickly discard number of elements from buffer |
|
check how many elements are currently in buffer |
|
check how much space left is there on buffer |
|
for multi-thread, wake all blocked threads and tell them to finish operation |
|
check size of next frame that buffer will return, only when buffer is dynamic |
|
set how much ring buffer can grow when buffer is growable |
Additional functions are provided if classics are not enough and more fine-grained control is needed:
function |
description |
|---|---|
initialize stack allocated ring buffer, must bring your own buffer |
|
cleanup ring buffer |
|
same as rb_write(3) but accepts flags for altering behavior for one call |
|
same as rb_read(3) but accepts flags for altering behavior for one call |
|
claim buffer for reading |
|
commit data to ring buffer and release buffer |
|
commit data then immediately claim another buffer without unlocking |
|
claim buffer for writing |
|
commit data to ring buffer and release buffer |
|
commit data then immediately claim another buffer without unlocking |
|
takes vector of buffers instead of single buffer - performs scatter read |
|
takes vector of buffers instead of single buffer - performs gather write |
DESCRIPTION¶
librb works with “elements” or “objects” not bytes. During ring buffer initialization you specify how big a single object is. That object may be an integer, but it also can be a struct. If you really want to work on bytes object size may just as well be equal to 1. After librb is initialized, you can only write or read objects with that specific size. It’s best to just pick one type and just stick to it for the whole life of the buffer.
You can enable multi-thread mode by passing rb_multithread flag during buffer creation. In this mode, read and write functions will block caller if there is no data or space available on buffer. librb utilizes double locking, one for write and read, so you can read and write simultaneously from a single ring buffer. If you don’t want to have your thread blocked during operation, you can either create buffer with rb_nonblock flag (which will result in all calls to be not blocking), or pass rb_dontwait flag to either rb_send(3) or rb_recv(3) function, for single, non-blocking operation.
EXAMPLE¶
/* ==========================================================================
* Licensed under BSD 2clause license See LICENSE file for more information
* Author: Michał Łyszczek <michal.lyszczek@bofc.pl>
* ========================================================================== */
#include <string.h>
#include <stdio.h>
#include "rb.h"
#include "common.h"
#define STACK_ALLOCATION 1
int main(void)
{
#if STACK_ALLOCATION
struct rb rbs; /* stack allocated ring buffer object */
int buffer[128]; /* buffer to hold 128 integers */
#endif
struct rb *rb; /* pointer to new rb object */
long nwritten; /* return value from rb_write() */
long nread; /* return value from rb_read() */
int data_to_write[256]; /* data to write into rb buffer */
int data_read[256]; /* buffer where we will read from rb */
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* You can use stack or heap allocations. Pick your poison. */
#if STACK_ALLOCATION
/* Initialize stack allocated #rb. Use stack allocated #buffer to
* hold data. Tell #rb array properties that is length and single
* element size */
if (rb_init(&rbs, buffer, rb_array_size(buffer), sizeof(int), 0))
pdie("rb_init()");
rb = &rbs;
#else
/* Initialize ring buffer using heap. Buffer of #rb it allocated
* internally as well */
if ((rb = rb_new(128, sizeof(int), 0)) == NULL)
pdie("rb_init()");
#endif
/* fill data to send with some data */
for (int i = 0; i != rb_array_size(data_to_write); ++i)
data_to_write[i] = i;
/* put data in the buffer, buffer can hold only 127 elements, and we try
* to put 256 integers (elements) there, so rb_write will return 127,
* as this is number of elements copied to rb object. */
nwritten = rb_write(rb, data_to_write, rb_array_size(data_to_write));
printf("number of elements stored to rb: %ld\n", nwritten);
/* buffer is now full, any write to it will result in error */
if (rb_write(rb, data_read, 1) == -1)
perror("rb_write() returned error");
/* now we read maximum of 256 elements from rb to data_read buffer, but
* since we put 127 elements in rb, only 127 elements will be copied
* back to #data_read */
memset(data_read, 0x00, sizeof(data_read));
nread = rb_read(rb, data_read, rb_array_size(data_read));
printf("number of elements read from rb: %ld\n", nread);
/* buffer is now empty, any read from it will result in error */
if (rb_read(rb, data_read, 1) == -1)
perror("rb_write() returned error");
/* check if data read matches what we've just put on buffer */
printf("Checking if data matches... data %s\n",
memcmp(data_read, data_to_write, nread * sizeof(int)) ? "not ok" : "ok");
/* don't forget to cleanup object when done. */
#if STACK_ALLOCATION
rb_cleanup(rb);
#else
rb_destroy(rb);
#endif
return 0;
}