A high-performance, thread-safe circular buffer implementation in C designed for embedded systems and other memory-constrained environments.
For a detailed explanation of the implementation, see One Size Fits All: Linked Ring Instead of a Ring Buffer.
The Linked Ring Buffer provides an efficient way to manage data in a circular buffer with multiple "owners" (data sources or consumers). It's particularly useful for:
- Embedded systems with limited memory
- Communication between different components or threads
- Buffering data streams with multiple producers and consumers
- Real-time data processing applications
- Thread-safe operations with customizable mutex implementation
- Support for multiple data owners/streams in a single buffer
- Efficient memory usage with configurable buffer size
- FIFO (First-In-First-Out) data handling with
lr_put()andlr_get() - LIFO (Last-In-First-Out) data handling with
lr_push()andlr_pop() - String operations for text processing
- Buffer resizing capability
The Linked Ring Buffer provides significant memory savings compared to traditional ring buffers when dealing with multiple producers/consumers:
With traditional ring buffers, each producer-consumer pair typically requires its own dedicated buffer:
N pairs × Buffer Size = Total Memory Required
Example: 5 pairs with 100-element buffers = 500 elements
With the Linked Ring Buffer, a single buffer can be shared among all producers and consumers:
1 buffer + N owner cells = Total Memory Required
Example: 5 pairs with a 100-element shared buffer = 100 + 5 = 105 elements
Memory Savings = (N × Buffer Size) - (Buffer Size + N)
= Buffer Size × (N - 1) - N
Example: With 5 pairs and 100-element buffers
Savings = 100 × (5 - 1) - 5 = 395 elements (79% reduction)
The memory savings increase with:
- More producer-consumer pairs
- Larger buffer sizes
- Uneven usage patterns (some producers are idle while others are active)
For embedded systems with limited memory, these savings can be crucial.
The project uses CMake for building:
mkdir build
cd build
cmake ..
makeThe project includes several test suites to verify functionality:
# Build and run all tests
cd build
make test
ctest#include <lr.h>
// Initialize a buffer
struct lr_cell cells[10];
struct linked_ring buffer;
lr_init(&buffer, 10, cells);
// Add data to the buffer (owner 1)
lr_put(&buffer, 42, 1);
lr_put(&buffer, 43, 1);
// Add data to the buffer (owner 2)
lr_put(&buffer, 100, 2);
// Retrieve data for a specific owner
lr_data_t data;
lr_get(&buffer, &data, 1); // Gets 42 from owner 1
// Add a string to the buffer
lr_put_string(&buffer, (unsigned char*)"Hello", 1);
// Use stack-like operations
lr_push(&buffer, 99, 3); // Add to tail
lr_pop(&buffer, &data, 3); // Get from tail (99)
// Print buffer contents
lr_dump(&buffer);
// Debug circular structure
lr_debug_structure_circular(&buffer, 1);For thread-safe operation, provide a mutex implementation:
// Define mutex functions
lr_result_t my_lock(void *state) {
pthread_mutex_t *mutex = (pthread_mutex_t *)state;
return pthread_mutex_lock(mutex) == 0 ? LR_OK : LR_ERROR_LOCK;
}
lr_result_t my_unlock(void *state) {
pthread_mutex_t *mutex = (pthread_mutex_t *)state;
return pthread_mutex_unlock(mutex) == 0 ? LR_OK : LR_ERROR_UNLOCK;
}
// Set up mutex
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
struct lr_mutex_attr mutex_attr = {
.state = &mutex,
.lock = my_lock,
.unlock = my_unlock
};
// Apply mutex to buffer
lr_set_mutex(&buffer, &mutex_attr);lr_init(lr, size, cells)- Initialize a new buffer - O(n)lr_resize(lr, size, cells)- Resize an existing buffer - O(n)lr_set_mutex(lr, attr)- Set mutex for thread-safe operations - O(1)
lr_put(lr, data, owner)- Add data to head of buffer - O(1)lr_get(lr, &data, owner)- Get data from head of buffer - O(1)lr_read(lr, &data, owner)- Read data without removing it - O(1)
lr_push(lr, data, owner)- Add data to tail of buffer - O(1)lr_pop(lr, &data, owner)- Get data from tail of buffer - O(n)
lr_put_string(lr, data, owner)- Add string to buffer - O(m) where m is string lengthlr_read_string(lr, data, &length, owner)- Read string from buffer - O(m) where m is string length
lr_insert(lr, data, owner, index)- Insert data at specific index - O(n)lr_pull(lr, &data, owner, index)- Remove data from specific index - O(n)
lr_owner_find(lr, owner)- Find owner cell - O(k) where k is number of ownerslr_count_owned(lr, owner)- Count elements for an owner - O(n)lr_count(lr)- Count all elements in buffer - O(n)
lr_dump(lr)- Print buffer contents - O(n)lr_debug_structure_circular(lr, owner)- Verify circular structure - O(n)lr_debug_strucuture_cells(lr)- Debug cell structure - O(n)lr_debug_structure_relinked(lr)- Debug relinked structure - O(n)
This project is available under the MIT License.