glip  0.1.0-dev
The Generic Logic Interfacing Project
Circular Buffer

Data Structures

struct  cbuf
 

Functions

static size_t _cbuf_calc_bufsize (size_t size_req, size_t size_max)
 
int cbuf_init (struct cbuf **buf, size_t size)
 
int cbuf_free (struct cbuf *buf)
 
int cbuf_set_hint_max_read_size (struct cbuf *buf, size_t hint_max_read_size)
 
int cbuf_set_hint_max_write_size (struct cbuf *buf, size_t hint_max_write_size)
 
int cbuf_write (struct cbuf *buf, const uint8_t *data, size_t size)
 
int cbuf_reserve (struct cbuf *buf, uint8_t **data, size_t size)
 
int cbuf_commit (struct cbuf *buf, uint8_t *data, size_t size)
 
int cbuf_read (struct cbuf *buf, uint8_t *data, size_t size)
 
int cbuf_peek (struct cbuf *buf, uint8_t **data, size_t size)
 
int cbuf_discard (struct cbuf *buf, size_t size)
 
bool cbuf_is_empty (struct cbuf *buf)
 
bool cbuf_is_full (struct cbuf *buf)
 
size_t cbuf_fill_level (struct cbuf *buf)
 
size_t cbuf_free_level (struct cbuf *buf)
 
int cbuf_wait_for_level_change (struct cbuf *buf, size_t level)
 
int cbuf_timedwait_for_level_change (struct cbuf *buf, size_t level, const struct timespec *abs_timeout)
 

Detailed Description

Inside GLIP it is often necessary to buffer incoming or outgoing data between the read/write API and a communication thread. This functionality is usually accomplished by a circular buffer, a.k.a. ring buffer or FIFO, and implemented in this class.

cbuf supports two methods for reading and writing data, which can be freely intermixed: a basic mode and an API mode.

Basic Mode

The basic mode is exactly what you would expect from a buffer implementation: cbuf_read() reads data from the buffer, and cbuf_write() allows you to write data to the buffer.

API Mode

In addition to the basic mode, cbuf is optimized to be used together with 3rd-party APIs. Those APIs exhibit a common pattern:

Using such an API together with the basic read/write functions requires you to use temporary buffers, which obviously has a memory overhead and (more importantly) requires data to be needlessly copied around, reducing performance. But don't despair, cbuf's API Mode got you covered!

Now, when reading from the device and thus writing into the circular buffer, you do the following:

Reading from the circular buffer works the same way by using the cbuf_peek() and cbuf_discard() functions.

Note that you will get always a continuous block of memory when calling cbuf_reserve() or cbuf_peek(), as long as the buffer fill level allows for it.

The implementation of cbuf tries to avoid copying data whenever possible (which is actually most of the time). Internally, though, the circular buffer is implemented as block of memory with begin and end pointers. This creates cases where a single write to the buffer causes actually two writes, one to the end of the buffer space, and on to the beginning. In this case a temporary buffer is handed out by cbuf_reserve() and this buffer is later on copied to the appropriate destinations (when calling cbuf_commit()). The same logic applies to the read operation appropriately.

Implementations exist which in all cases avoid copying data, usually with the trade-off of not being able to use the whole buffer space in all cases, i.e. you cannot always fill the circular buffer up to 100 percent. One such implementation is the Bip Buffer by Simon Cooke.

Thread Safety

A circular buffer has two "sides": a read side and a write side. In many cases reading happens from a different thread than writing. This use case is fully supported by cbuf, and read and write side are properly synchronized by appropriate locking mechanisms. Note, however, that cbuf is not fully thread safe. You cannot, for example, write to the buffer from two different threads without implementing appropriate locking mechanisms yourself. This keeps the synchronization overhead minimal while allowing for the most common use case.

Utility Functionality

cbuf contains, in addition to the read/write API, a number of useful utility functions.

Function Documentation

static size_t _cbuf_calc_bufsize ( size_t  size_req,
size_t  size_max 
)
static

Calculate a new buffer size

The new size is the next power of two following size_req, but at most size_max.

Parameters
size_reqthe requested buffer size
size_maxthe maximum buffer size
Returns
the calculated buffer size
int cbuf_init ( struct cbuf **  buf,
size_t  size 
)

Initialize the buffer

Parameters
bufthe buffer structure to be initialized
sizethe size of the buffer in bytes. The size needs to be a power of two.
Returns
0 on success
-ENOMEM if the memory allocation for the data buffer failed
any other value indicates an error
int cbuf_free ( struct cbuf buf)

Free all resources of the buffer

This function frees all resources inside the buffer, including the buffer struct buf itself.

Parameters
bufthe buffer
Returns
0 on success
any other value indicates an error
int cbuf_set_hint_max_read_size ( struct cbuf buf,
size_t  hint_max_read_size 
)

Give a hint on the maximum read size

Reading from the circular buffer requires under some circumstances the use of a temporary buffer. This buffer needs to be as large as a read size and automatically adjusts its size. If you know already how large your maximum read will be (e.g. your maximum packet size), you can set this value as a hint and this way prevent unnecessary memory allocations.

Note that this hint is strictly an optimization, it's not necessary for the correct functioning of this buffer!

Parameters
bufthe buffer
hint_max_read_sizea hint on the maximum read size
Returns
0 on success
any other value indicates an error
See also
cbuf_set_hint_max_write_size()
int cbuf_set_hint_max_write_size ( struct cbuf buf,
size_t  hint_max_write_size 
)

Give a hint on the maximum write size

This function sets a hint on the maximum write size, similar to the maximum read size set in cbuf_set_hint_max_read_size().

Parameters
bufthe buffer
hint_max_write_sizea hint on the maximum write size
Returns
0 on success
any other value indicates an error
See also
cbuf_set_hint_max_read_size()
int cbuf_write ( struct cbuf buf,
const uint8_t *  data,
size_t  size 
)

Copy data into the buffer

size bytes of data are taken from data and copied into the buffer. You need to check if the buffer has enough space available by calling cbuf_free_level() before; this function call fails with a return value of -ENOMEM if not enough space is available.

Parameters
bufthe buffer
datathe data to be copied into the buffer
sizethe number of bytes copied from data into the buffer
Returns
0 on success
-EINVAL if the buffer does not have enough space available. No data has been copied.
any other value indicates an error
See also
cbuf_reserve()
int cbuf_reserve ( struct cbuf buf,
uint8_t **  data,
size_t  size 
)

Reserve space in the buffer and return a pointer to it

This function reserves size bytes of memory inside the buffer and returns the pointer data to it. This pointer can then be used to write up to size bytes of data to it. After the data has been written, the data needs to be committed by calling cbuf_commit(). You may commit less bytes than you reserved (or even nothing at all).

You need to check if enough space is available by calling cbuf_free_level() beforehand, otherwise this function will fail with a -ENOMEM return value.

The main use case for this API are other APIs: many libraries provide functions which can be passed a pointer to a preallocated chunk of memory and a maximum size and then fill this memory. Using the cbuf_write() function would require another memory copy (once from the API to a temporary buffer, then from the temporary buffer to this buffer), which can be avoided by using the reserve/commit API.

Parameters
[in]bufthe buffer
[out]datapointer which can be filled
[in]sizemaximum number of bytes which can be written to data
Returns
0 on success
-EINVAL if the buffer does not have enough space available, the reservation is not possible.
-ENOMEM the allocation of a temporary buffer failed
any other value indicates an error
See also
cbuf_commit()
cbuf_write()
int cbuf_commit ( struct cbuf buf,
uint8_t *  data,
size_t  size 
)

Commit data written into the buffer

After you have reserved space in the buffer by calling cbuf_reserve() and written data to it, use this function to commit the size actually written bytes to the buffer. data must be a pointer returned by cbuf_reserve(), size must be equal or less than the size you reserved in cbuf_reserve().

Parameters
bufthe buffer
datathe data to commit
sizethe number of bytes to commit from data
Returns
0 on success
any other value indicates an error
See also
cbuf_reserve()
int cbuf_read ( struct cbuf buf,
uint8_t *  data,
size_t  size 
)

Read data from the buffer

This copies size bytes of data from the buffer into data. data needs to be large enough to hold size bytes of data.

You need to check the buffer fill level if size bytes of data are available by calling cbuf_fill_level() beforehand; otherwise this function will fail with a -EINVAL return value.

Parameters
bufthe buffer
datathe data location to copy to
sizethe number of bytes to copy
Returns
0 on success
-EINVAL if not enough data is available for reading
any other value indicates an error
See also
cbuf_peek()
int cbuf_peek ( struct cbuf buf,
uint8_t **  data,
size_t  size 
)

Return a pointer to data without reading it yet

This function returns a pointer to the buffer from which up to size bytes of data can be read. The data is not yet removed from the buffer, you need to call cbuf_discard() for that.

The buffer needs to have size bytes of data available, check this requirement by calling cbuf_fill_level() beforehand; otherwise -EINVAL is returned.

The main reason for this the peek/discard API is compatibility with 3rd-party APIs. Call cbuf_peek() to get a pointer to some data, call another API's write function for example and pass this pointer, and finally call cbuf_discard() with the number of bytes actually written by the 3rd-party API.

Parameters
[in]bufthe buffer
[out]dataa pointer to the next size bytes of data
[in]sizethe number of bytes to look ahead
Returns
0 on success
-EINVAL not enough data is available to be read, check size
-ENOMEM the allocation of a temporary buffer failed
any other value indicates an error
int cbuf_discard ( struct cbuf buf,
size_t  size 
)

Discard data in the buffer

This function removes (discards) size bytes of data from the buffer. See cbuf_peek() for further information on how to use this API.

Parameters
bufthe buffer
sizethe number of bytes to discard
Returns
0 on success
-EINVAL not enough data is available to discard, check size
any other value indicates an error
See also
cbuf_peek()
bool cbuf_is_empty ( struct cbuf buf)

Is the buffer empty?

Parameters
bufthe buffer
Returns
true if the buffer is empty, false otherwise
See also
cbuf_is_full()
cbuf_free_level()
bool cbuf_is_full ( struct cbuf buf)

Is the buffer full?

Parameters
bufthe buffer
Returns
true if the buffer is full, false otherwise
See also
cbuf_is_empty()
cbuf_fill_level()
size_t cbuf_fill_level ( struct cbuf buf)

Get the fill level of the buffer

The fill level is equal to the number of bytes of valid data stored in the buffer.

Parameters
bufthe buffer
Returns
the number of valid bytes in the buffer
See also
cbuf_free_level()
cbuf_is_full()
size_t cbuf_free_level ( struct cbuf buf)

Get the number of available spaces in the buffer

Parameters
bufthe buffer
Returns
the number of bytes that can still be written into the buffer
See also
cbuf_fill_level()
cbuf_is_empty()
int cbuf_wait_for_level_change ( struct cbuf buf,
size_t  level 
)

Wait until the buffer fill level changes

This function is called with the last known level. The functions returns immediately if the level has changed. Otherwise it waits for another thread to write to the buffer (which triggers a level change).

Parameters
bufthe buffer
levelknown level
Returns
0 on success
any other value indicates an error
See also
cbuf_timedwait_for_level_change()
int cbuf_timedwait_for_level_change ( struct cbuf buf,
size_t  level,
const struct timespec *  abs_timeout 
)

Wait until the buffer fill level changes with a timeout

This function is called with the last known level. The functions returns immediately if the level has changed. Otherwise it waits for another thread to write to the buffer (which triggers a level change).

The timeout expires when the absolute time in abs_timeout passes, or if the absolute time specified in abs_timeout has already passed. This behavior is identical to the timed pthread functions (pthread_mutex_timedlock(), etc.).

Parameters
bufthe buffer
levellast known level
abs_timeoutif this absolute time passes, the timeout expires
Returns
0 on success
any other value indicates an error
See also
cbuf_wait_for_level_change()