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

rb_new(3)

create new ring buffer, allocate all needed buffers

rb_write(3)

write arbitrary number of elements into ring buffer

rb_read(3)

read arbitrary number of elements from ring buffer

rb_destroy(3)

destroy ring buffer once you are done with it

Flags for rb_new(3) or rb_init(3):

function

description

rb_nonblock

Create a non-blocking ring buffer (for multi-thread buffer only)

rb_multithread

Create thread aware ring buffer

rb_dynamic

Create dynamic buffer, where you can put object of any size on the buffer

rb_growable

Automatically increase size of a buffer, if you use all buffer

rb_round_count

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

rb_read_claim(3)

claim buffer for reading

rb_read_commit(3)

commit data to ring buffer and release buffer

rb_read_commit_claim(3)

commit data then immediately claim another buffer without unlocking

rb_write_claim(3)

claim buffer for writing

rb_write_commit(3)

commit data to ring buffer and release buffer

rb_write_commit_claim(3)

commit data then immediately claim another buffer without unlocking

Ring buffer control related functions:

function

description

rb_clear(3)

quickly drop all data from ring buffer

rb_discard(3)

quickly discard number of elements from buffer

rb_count(3)

check how many elements are currently in buffer

rb_space(3)

check how much space left is there on buffer

rb_stop(3)

for multi-thread, wake all blocked threads and tell them to finish operation

rb_peek_size(3)

check size of next frame that buffer will return, only when buffer is dynamic

rb_set_hard_max_count(3)

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

rb_init(3)

initialize stack allocated ring buffer, must bring your own buffer

rb_cleanup(3)

cleanup ring buffer

rb_send(3)

same as rb_write(3) but accepts flags for altering behavior for one call

rb_recv(3)

same as rb_read(3) but accepts flags for altering behavior for one call

rb_recv_claim(3)

claim buffer for reading

rb_recv_commit(3)

commit data to ring buffer and release buffer

rb_recv_commit_claim(3)

commit data then immediately claim another buffer without unlocking

rb_send_claim(3)

claim buffer for writing

rb_send_commit(3)

commit data to ring buffer and release buffer

rb_send_commit_claim(3)

commit data then immediately claim another buffer without unlocking

rb_readv(3), rb_recvv(3)

takes vector of buffers instead of single buffer - performs scatter read

rb_writev(3), rb_sendv(3)

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;
}