glip
0.1.0-dev
The Generic Logic Interfacing Project
|
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) |
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.
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.
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.
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.
cbuf contains, in addition to the read/write API, a number of useful utility functions.
|
static |
Calculate a new buffer size
The new size is the next power of two following size_req
, but at most size_max
.
size_req | the requested buffer size |
size_max | the maximum buffer size |
int cbuf_init | ( | struct cbuf ** | buf, |
size_t | size | ||
) |
Initialize the buffer
buf | the buffer structure to be initialized |
size | the size of the buffer in bytes. The size needs to be a power of two. |
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.
buf | the buffer |
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!
buf | the buffer |
hint_max_read_size | a hint on the maximum read 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().
buf | the buffer |
hint_max_write_size | a hint on the maximum write 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.
buf | the buffer |
data | the data to be copied into the buffer |
size | the number of bytes copied from data into the buffer |
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.
[in] | buf | the buffer |
[out] | data | pointer which can be filled |
[in] | size | maximum number of bytes which can be written to data |
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().
buf | the buffer |
data | the data to commit |
size | the number of bytes to commit from data |
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.
buf | the buffer |
data | the data location to copy to |
size | the number of bytes to copy |
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.
[in] | buf | the buffer |
[out] | data | a pointer to the next size bytes of data |
[in] | size | the number of bytes to look ahead |
size
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.
buf | the buffer |
size | the number of bytes to discard |
size
bool cbuf_is_empty | ( | struct cbuf * | buf | ) |
Is the buffer empty?
buf | the buffer |
bool cbuf_is_full | ( | struct cbuf * | buf | ) |
Is the buffer full?
buf | the buffer |
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.
buf | the buffer |
size_t cbuf_free_level | ( | struct cbuf * | buf | ) |
Get the number of available spaces in the buffer
buf | the buffer |
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).
buf | the buffer |
level | known level |
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.).
buf | the buffer |
level | last known level |
abs_timeout | if this absolute time passes, the timeout expires |