Compare commits
46 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a6651f05a5 | ||
![]() |
b11edb59f4 | ||
![]() |
16639b24ef | ||
![]() |
ff984caf35 | ||
![]() |
a6c8b5cdc1 | ||
![]() |
da47bcd82f | ||
![]() |
82388142f0 | ||
![]() |
b77d86ae12 | ||
![]() |
b26ceeb8fe | ||
![]() |
2acb3c5bd5 | ||
![]() |
5fd22f005a | ||
![]() |
06a5b53bd9 | ||
![]() |
4e8f158c91 | ||
![]() |
14e8b9e1c0 | ||
![]() |
bfffbb9211 | ||
![]() |
1e76066400 | ||
![]() |
4fab8ca207 | ||
![]() |
259c048bd5 | ||
![]() |
3308e61dc1 | ||
![]() |
20b6e5c2fd | ||
![]() |
4a90d24069 | ||
![]() |
948aeab825 | ||
![]() |
6a4dc46b60 | ||
![]() |
d6a3c95951 | ||
![]() |
7833bf73a7 | ||
![]() |
e5006ed262 | ||
![]() |
2886a81574 | ||
![]() |
a5c5ecd64b | ||
![]() |
85071d3130 | ||
![]() |
06b949bed4 | ||
![]() |
de2465de83 | ||
![]() |
6022bdee92 | ||
![]() |
ea100383d7 | ||
![]() |
eb4b11dda7 | ||
![]() |
c06bb51cdc | ||
![]() |
68af34c428 | ||
![]() |
8e77f810ec | ||
![]() |
8cbca38726 | ||
![]() |
41f817fd5b | ||
![]() |
d19917bba7 | ||
![]() |
db38111b3c | ||
![]() |
c4f1ede6b0 | ||
![]() |
df9cd367a3 | ||
![]() |
07bb60c8ae | ||
![]() |
9c746a10cf | ||
![]() |
4e1254fde1 |
9 changed files with 502 additions and 58 deletions
|
@ -51,7 +51,7 @@ AllowShortEnumsOnASingleLine: true
|
||||||
AllowShortFunctionsOnASingleLine: All
|
AllowShortFunctionsOnASingleLine: All
|
||||||
AllowShortIfStatementsOnASingleLine: Never
|
AllowShortIfStatementsOnASingleLine: Never
|
||||||
AllowShortLambdasOnASingleLine: All
|
AllowShortLambdasOnASingleLine: All
|
||||||
AllowShortLoopsOnASingleLine: false
|
AllowShortLoopsOnASingleLine: true
|
||||||
AlwaysBreakAfterDefinitionReturnType: All
|
AlwaysBreakAfterDefinitionReturnType: All
|
||||||
AlwaysBreakAfterReturnType: AllDefinitions
|
AlwaysBreakAfterReturnType: AllDefinitions
|
||||||
AlwaysBreakBeforeMultilineStrings: false
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,4 +1,9 @@
|
||||||
*.o
|
*.o
|
||||||
*.s
|
*.s
|
||||||
|
*.so
|
||||||
|
*.a
|
||||||
build
|
build
|
||||||
driver
|
driver
|
||||||
|
*.elf
|
||||||
|
*.json
|
||||||
|
.cache
|
||||||
|
|
20
.woodpecker/test.yaml
Normal file
20
.woodpecker/test.yaml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
branch: master
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: debian
|
||||||
|
commands:
|
||||||
|
- apt update
|
||||||
|
- apt install libcmocka-dev -y
|
||||||
|
- apt install gcc -y
|
||||||
|
- apt install make -y
|
||||||
|
- make -j$(nproc) test/test.elf
|
||||||
|
|
||||||
|
- name: run-tests
|
||||||
|
image: debian
|
||||||
|
commands:
|
||||||
|
- apt update
|
||||||
|
- apt install libcmocka0 -y
|
||||||
|
- ./test/test.elf
|
41
Makefile
41
Makefile
|
@ -6,17 +6,19 @@ CFLAGS += -Wno-unused-variable -Wno-unused-function
|
||||||
CFLAGS += -Wno-unused-but-set-variable -Wno-unused-value -Wno-unused-label
|
CFLAGS += -Wno-unused-but-set-variable -Wno-unused-value -Wno-unused-label
|
||||||
CFLAGS += -Wno-unused-result -Wno-unused-const-variable
|
CFLAGS += -Wno-unused-result -Wno-unused-const-variable
|
||||||
|
|
||||||
ifeq ($(RELEASE), 1)
|
CFLAGS += -I.
|
||||||
|
|
||||||
|
ifneq ($(RELEASE), 1)
|
||||||
|
CFLAGS += -g -O0 -std=c99 -march=native -mtune=native
|
||||||
|
CFLAGS += -DDEBUG
|
||||||
|
else
|
||||||
CFLAGS += -fno-exceptions -fno-asynchronous-unwind-tables -fno-ident
|
CFLAGS += -fno-exceptions -fno-asynchronous-unwind-tables -fno-ident
|
||||||
CFLAGS += -fno-unwind-tables -fno-stack-protector -fno-plt -fno-pic
|
CFLAGS += -fno-unwind-tables -fno-stack-protector -fno-plt -fno-pic
|
||||||
CFLAGS += -O3 -std=c99 -march=native -mtune=native -fomit-frame-pointer
|
CFLAGS += -O3 -std=c99 -march=native -mtune=native -fomit-frame-pointer
|
||||||
else
|
CFLAGS += -fshort-enums
|
||||||
CFLAGS += -g -O0 -std=c99 -march=native -mtune=native
|
endif # DEBUG
|
||||||
CFLAGS += -DDEBUG
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Include debug flags
|
LDFLAGS = -lm
|
||||||
CFLAGS += -g -O0 -std=c99 -march=native -mtune=native
|
|
||||||
|
|
||||||
C_SOURCES = $(wildcard *.c)
|
C_SOURCES = $(wildcard *.c)
|
||||||
C_HEADERS = $(wildcard *.h)
|
C_HEADERS = $(wildcard *.h)
|
||||||
|
@ -25,6 +27,9 @@ ASMS = $(C_SOURCES:.c=.s)
|
||||||
|
|
||||||
all: $(OBJECTS)
|
all: $(OBJECTS)
|
||||||
|
|
||||||
|
test: test/test.elf
|
||||||
|
./test/test.elf
|
||||||
|
|
||||||
%.o: %.c $(C_HEADERS)
|
%.o: %.c $(C_HEADERS)
|
||||||
@echo "CC $<"
|
@echo "CC $<"
|
||||||
@$(CC) $(CFLAGS) -c $< -o $@
|
@$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
@ -34,19 +39,35 @@ all: $(OBJECTS)
|
||||||
@$(CC) $(CFLAGS) -S -masm=intel $<
|
@$(CC) $(CFLAGS) -S -masm=intel $<
|
||||||
|
|
||||||
driver: $(OBJECTS)
|
driver: $(OBJECTS)
|
||||||
@$(CC) $(CFLAGS) $^ -o $@
|
@$(CC) $(LDFLAGS) $^ -o $@
|
||||||
|
|
||||||
run: driver
|
run: driver
|
||||||
@./driver
|
@./driver
|
||||||
|
|
||||||
|
test/test.elf: test/test_ringbuf.o ringbuf.o
|
||||||
|
@$(CC) $(CFLAGS) $^ -o $@ -lpthread -lcmocka
|
||||||
|
|
||||||
|
lib: $(OBJECTS)
|
||||||
|
@ar rcs librbuf.a ringbuf.o
|
||||||
|
|
||||||
|
dylib: $(OBJECTS)
|
||||||
|
@$(CC) $(LDLAGS) -fPIC -shared -o librbuf.so ringbuf.o
|
||||||
|
|
||||||
|
install:
|
||||||
|
@cp librbuf.a /usr/local/lib
|
||||||
|
@cp ringbuf.h /usr/local/include
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJECTS) $(ASMS) driver
|
rm -f $(OBJECTS) $(ASMS) driver librbuf.a librbuf.so **/*.o **/*.elf
|
||||||
|
|
||||||
asm: $(ASMS) $(OBJECTS)
|
asm: $(ASMS) $(OBJECTS)
|
||||||
wc -l $(ASMS)
|
wc -l $(ASMS)
|
||||||
size $(OBJECTS)
|
size $(OBJECTS)
|
||||||
|
|
||||||
|
tidy:
|
||||||
|
@clang-tidy $(C_SOURCES) -- $(CFLAGS)
|
||||||
|
|
||||||
format:
|
format:
|
||||||
clang-format -i $(C_SOURCES) $(C_HEADERS)
|
clang-format -i $(C_SOURCES) $(C_HEADERS)
|
||||||
|
|
||||||
.PHONY: all clean format asm
|
.PHONY: all clean format asm test
|
||||||
|
|
44
README.md
Normal file
44
README.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# RingBuf
|
||||||
|
|
||||||
|
RingBuf is an allocator-agnostic, non-overwriting circular/ring buffer
|
||||||
|
implementation in C99.
|
||||||
|
See: [Circular Buffer (Wikipedia)](https://en.wikipedia.org/wiki/Circular_buffer)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Space Efficiency
|
||||||
|
The code is designed to be portable and flexible. The inspiration initially
|
||||||
|
came to me when designing a network driver. When operating in memory
|
||||||
|
constrained environments, every byte is of upmost importance. Traditional
|
||||||
|
metaprogramming such as templating in C++ and template metaprogramming in C,
|
||||||
|
although generic has the side effect of expanding into discrete machine code
|
||||||
|
specific for the specialization applied, essentially scaling (spatially) linear
|
||||||
|
with the amount of different datatypes we specialize on. This implementation
|
||||||
|
circumvents this by treating all data as a void pointer with a length. This
|
||||||
|
implies **no deep copies**.
|
||||||
|
|
||||||
|
- Allocator Agnostic
|
||||||
|
Another common characteristic of memory constrained environments are
|
||||||
|
custom malloc implementations and/or variations.
|
||||||
|
- Signatures
|
||||||
|
- Arena
|
||||||
|
|
||||||
|
## Design considerations
|
||||||
|
- Holding a reference to malloc internally would make a tidier interface
|
||||||
|
- Passing a user-defined function for deep-copies would enable certain applications
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
In essence:
|
||||||
|
```c
|
||||||
|
int value = 42;
|
||||||
|
struct RingBuf rb;
|
||||||
|
rb_init(&rb, 10, malloc, sizeof(int));
|
||||||
|
rb_push_back(&rb, (void *)&data, memcpy);
|
||||||
|
rb_pop_front(&rb, &value, memcpy);
|
||||||
|
```
|
||||||
|
Most of these functions return Enum result types. See:
|
||||||
|
[ringbuf.h](./ringbuf.h).
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
- Trim the code
|
||||||
|
- Reduce boilerplate in tests
|
||||||
|
- Reduce the number of tests in exchange for better test fit
|
87
driver.c
87
driver.c
|
@ -4,25 +4,100 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#define rb_size_t size_t
|
#ifndef DEBUG
|
||||||
|
#define DEBUG
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// #define rb_size_t size_t
|
||||||
|
// #define rb_size_t int
|
||||||
#include "ringbuf.h"
|
#include "ringbuf.h"
|
||||||
|
|
||||||
|
typedef int DATATYPE;
|
||||||
|
|
||||||
int
|
int
|
||||||
main(void)
|
main(void)
|
||||||
{
|
{
|
||||||
struct RingBuf rb;
|
struct RingBuf rb;
|
||||||
rb_init(&rb, 10, malloc, sizeof(int));
|
|
||||||
|
|
||||||
int data = 5;
|
DATATYPE d;
|
||||||
|
rb_init(&rb, 10, malloc, sizeof(DATATYPE));
|
||||||
|
|
||||||
rb_push_back(&rb, &data, memcpy);
|
rb_debug_print(&rb);
|
||||||
|
|
||||||
int d;
|
const DATATYPE arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
|
||||||
rb_pop_front(&rb, &d);
|
int arrlen = (rb_size_t)(sizeof(arr) / sizeof(DATATYPE));
|
||||||
|
|
||||||
|
// Single writes
|
||||||
|
printf("\n=== Single writes ===\n\n");
|
||||||
|
|
||||||
|
rb_debug_print(&rb);
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
while(idx < 5) {
|
||||||
|
if(rb_push_back(&rb, &arr[idx], memcpy) != WriteOk) {
|
||||||
|
printf("Failed to write data to buffer...\n");
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
rb_debug_print(&rb);
|
||||||
|
|
||||||
|
// Pop the last n elements
|
||||||
|
for(int a = 0; a < 10; a++) {
|
||||||
|
if(rb_pop_front(&rb, &d, memcpy) != ReadOk) {
|
||||||
|
printf("Failed to read data from buffer...\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
printf("Data: %d\n", d);
|
printf("Data: %d\n", d);
|
||||||
|
}
|
||||||
|
|
||||||
|
rb_debug_print(&rb);
|
||||||
|
if(rb.read_head == rb.write_head)
|
||||||
|
printf("OK\n");
|
||||||
|
printf("idx: %d\n", idx);
|
||||||
|
|
||||||
|
// Push the rest
|
||||||
|
while(idx < arrlen && rb_push_back(&rb, &arr[idx], memcpy) == WriteOk) {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
rb_debug_print(&rb);
|
||||||
|
|
||||||
|
printf("Data: [");
|
||||||
|
while(rb_pop_front(&rb, &d, memcpy) == ReadOk)
|
||||||
|
printf("%d,", d);
|
||||||
|
printf("\b]\n");
|
||||||
|
|
||||||
|
// Multiple writes
|
||||||
|
printf("\n=== Multiple writes ===\n\n");
|
||||||
|
|
||||||
|
rb_clear(&rb); // Make sure
|
||||||
|
rb_debug_print(&rb);
|
||||||
|
|
||||||
|
int ok = WriteOk; // Assume we can write
|
||||||
|
if(rb_push_many(&rb, arr, memcpy, 8) != WriteOk) {
|
||||||
|
printf("Failed to write data to buffer...\n");
|
||||||
|
}
|
||||||
|
rb_debug_print(&rb);
|
||||||
|
rb_debug_empty(&rb);
|
||||||
|
|
||||||
|
// Test wrap around
|
||||||
|
rb_push_many(&rb, arr, memcpy, 10);
|
||||||
|
rb_debug_print(&rb);
|
||||||
|
rb_debug_empty(&rb);
|
||||||
|
|
||||||
|
// Test clear
|
||||||
|
rb_clear(&rb);
|
||||||
|
if(rb_pop_front(&rb, &d, memcpy) != Empty) {
|
||||||
|
printf("Buffer is not empty after clear...\n");
|
||||||
|
}
|
||||||
|
|
||||||
rb_destroy(&rb, free);
|
rb_destroy(&rb, free);
|
||||||
|
|
||||||
|
enum WriteResult wr = WriteOk;
|
||||||
|
enum ReadResult rr = ReadOk;
|
||||||
|
printf("Size of wr: %lu bytes.\n", sizeof(wr));
|
||||||
|
printf("Size of rr: %lu bytes.\n", sizeof(rr));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
124
ringbuf.c
124
ringbuf.c
|
@ -1,12 +1,14 @@
|
||||||
/* SPDX-License-Identifier: MIT */
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
#include <stdlib.h>
|
// #include <stdlib.h>
|
||||||
#include <string.h>
|
// #include <string.h>
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include "ringbuf.h"
|
#include "ringbuf.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
#define DEBUG_PRINT(fmt, ...) printf(fmt, __VA_ARGS__)
|
#define DEBUG_PRINT(fmt, ...) printf(fmt, __VA_ARGS__)
|
||||||
#else
|
#else
|
||||||
#define DEBUG_PRINT(fmt, ...)
|
#define DEBUG_PRINT(fmt, ...)
|
||||||
|
@ -23,24 +25,31 @@ rb_init(struct RingBuf *rb, rb_size_t capacity, ALLOC_T malloc_fn,
|
||||||
rb->count = 0;
|
rb->count = 0;
|
||||||
rb->write_head = rb->buffer;
|
rb->write_head = rb->buffer;
|
||||||
rb->read_head = rb->buffer;
|
rb->read_head = rb->buffer;
|
||||||
|
|
||||||
// Read from buffer at max position to force a segfault if theres an issue
|
|
||||||
DEBUG_PRINT("Reading from buffer at position %d\n",
|
|
||||||
rb->capacity * rb->struct_size);
|
|
||||||
void *top = rb->buffer + (rb->capacity * rb->struct_size);
|
|
||||||
DEBUG_PRINT("Buffer top successfully read at virtual address: %p\n", &top);
|
|
||||||
|
|
||||||
DEBUG_PRINT(
|
|
||||||
"Initialized ring buffer. Capacit: %d, struct_size: %d, total: %d\n",
|
|
||||||
rb->capacity, rb->struct_size, rb->capacity * rb->struct_size);
|
|
||||||
|
|
||||||
DEBUG_PRINT("Size of RB: %lu\n", sizeof(struct RingBuf));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
rb_destroy(struct RingBuf *rb, void(free)())
|
rb_destroy(struct RingBuf *rb, FREE_T free_fn)
|
||||||
{
|
{
|
||||||
free(rb->buffer);
|
if(rb->buffer) { // Prevent double-free
|
||||||
|
free_fn(rb->buffer);
|
||||||
|
rb->buffer = NULL;
|
||||||
|
rb->buffer_end = NULL;
|
||||||
|
|
||||||
|
rb->struct_size = 0;
|
||||||
|
rb->capacity = 0;
|
||||||
|
rb->count = 0;
|
||||||
|
|
||||||
|
rb->write_head = NULL;
|
||||||
|
rb->read_head = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
rb_clear(struct RingBuf *rb)
|
||||||
|
{
|
||||||
|
rb->count = 0;
|
||||||
|
rb->write_head = rb->buffer;
|
||||||
|
rb->read_head = rb->buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum WriteResult
|
enum WriteResult
|
||||||
|
@ -49,6 +58,8 @@ rb_push_back(struct RingBuf *rb, const void *item, MEMCPY_T memcpy_fn)
|
||||||
if(rb->count == rb->capacity)
|
if(rb->count == rb->capacity)
|
||||||
return Full;
|
return Full;
|
||||||
|
|
||||||
|
assert(rb->buffer != NULL);
|
||||||
|
|
||||||
memcpy_fn(rb->write_head, item, rb->struct_size);
|
memcpy_fn(rb->write_head, item, rb->struct_size);
|
||||||
|
|
||||||
// Advance the write head
|
// Advance the write head
|
||||||
|
@ -61,13 +72,54 @@ rb_push_back(struct RingBuf *rb, const void *item, MEMCPY_T memcpy_fn)
|
||||||
return WriteOk;
|
return WriteOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum WriteResult
|
||||||
|
rb_push_many(struct RingBuf *rb, const void *items, MEMCPY_T memcpy_fn,
|
||||||
|
rb_size_t n)
|
||||||
|
{
|
||||||
|
if(rb->count + n > rb->capacity)
|
||||||
|
return Full; // Perhaps rename to InsufficientSpace
|
||||||
|
|
||||||
|
// If the write head will move past the end of the buffer
|
||||||
|
// we need to to the write in two steps.
|
||||||
|
void *end = (char *)rb->write_head + rb->struct_size * n;
|
||||||
|
|
||||||
|
if(end > rb->buffer_end) {
|
||||||
|
|
||||||
|
// Calculate the number of items that can be written in the first chunk
|
||||||
|
rb_size_t first_chunk = (char *)rb->buffer_end - (char *)rb->write_head;
|
||||||
|
|
||||||
|
DEBUG_PRINT("Multi-chunk write. First chunk: %ld\n", first_chunk);
|
||||||
|
|
||||||
|
// Write the first chunk
|
||||||
|
memcpy_fn(rb->write_head, items, rb->struct_size * first_chunk);
|
||||||
|
|
||||||
|
// Set the write head to the beginning of the buffer
|
||||||
|
rb->write_head = rb->buffer;
|
||||||
|
rb->count += first_chunk;
|
||||||
|
n -= first_chunk;
|
||||||
|
} else {
|
||||||
|
DEBUG_PRINT("Single-chunk write. No need to wrap around.%s\n", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_PRINT("Writing %ld items\n", n);
|
||||||
|
memcpy_fn(rb->write_head, items, rb->struct_size * n);
|
||||||
|
if(rb->write_head == rb->buffer_end)
|
||||||
|
rb->write_head = rb->buffer;
|
||||||
|
|
||||||
|
// Advance the write head
|
||||||
|
rb->write_head = (char *)rb->write_head + rb->struct_size * n;
|
||||||
|
rb->count += n;
|
||||||
|
|
||||||
|
return WriteOk;
|
||||||
|
}
|
||||||
|
|
||||||
enum ReadResult
|
enum ReadResult
|
||||||
rb_pop_front(struct RingBuf *rb, void *item)
|
rb_pop_front(struct RingBuf *rb, void *item, MEMCPY_T memcpy_fn)
|
||||||
{
|
{
|
||||||
if(rb->count == 0)
|
if(rb->count == 0)
|
||||||
return Empty;
|
return Empty;
|
||||||
|
|
||||||
memcpy(item, rb->read_head, rb->struct_size);
|
memcpy_fn(item, rb->read_head, rb->struct_size);
|
||||||
|
|
||||||
// Advance the read head
|
// Advance the read head
|
||||||
rb->read_head = (char *)rb->read_head + rb->struct_size;
|
rb->read_head = (char *)rb->read_head + rb->struct_size;
|
||||||
|
@ -78,3 +130,37 @@ rb_pop_front(struct RingBuf *rb, void *item)
|
||||||
rb->count--;
|
rb->count--;
|
||||||
return ReadOk;
|
return ReadOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
rb_debug_empty(struct RingBuf *rb)
|
||||||
|
{
|
||||||
|
int d;
|
||||||
|
if(rb->count == 0)
|
||||||
|
return;
|
||||||
|
printf("Debug Data: [");
|
||||||
|
while(rb_pop_front(rb, &d, memcpy) == ReadOk)
|
||||||
|
printf("%d,", d);
|
||||||
|
printf("\b]\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
rb_debug_print(struct RingBuf *rb)
|
||||||
|
{
|
||||||
|
DEBUG_PRINT("============%s\n", "");
|
||||||
|
DEBUG_PRINT("Count %lu\n", rb->count);
|
||||||
|
DEBUG_PRINT("Capacity: %ld\n", rb->capacity);
|
||||||
|
DEBUG_PRINT("Left: %ld\n", rb->capacity - rb->count);
|
||||||
|
DEBUG_PRINT("Base addr:\t%p\n", rb->buffer);
|
||||||
|
|
||||||
|
DEBUG_PRINT("Read Head:\t%p (%ld:th position)\n", rb->read_head,
|
||||||
|
((rb->read_head) - (rb->buffer)) / rb->struct_size);
|
||||||
|
DEBUG_PRINT("Write Head:\t%p (%ld:th position)\n", rb->write_head,
|
||||||
|
((rb->write_head) - (rb->buffer)) / rb->struct_size);
|
||||||
|
|
||||||
|
DEBUG_PRINT("============%s\n", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
130
ringbuf.h
130
ringbuf.h
|
@ -2,42 +2,134 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#ifndef rb_size_t
|
#ifndef rb_size_t
|
||||||
#define rb_size_t int
|
#define rb_size_t size_t
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** Signatures of generic functions */
|
/** Signatures of allocators */
|
||||||
typedef void *(*ALLOC_T)(rb_size_t);
|
typedef void *(*ALLOC_T)(rb_size_t);
|
||||||
|
|
||||||
|
/** Signature of memcpy */
|
||||||
typedef void *(*MEMCPY_T)(void *, const void *, rb_size_t);
|
typedef void *(*MEMCPY_T)(void *, const void *, rb_size_t);
|
||||||
|
|
||||||
|
/** Signature of free */
|
||||||
|
typedef void (*FREE_T)(void *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ring buffer, also known as circular buffer.
|
* @brief Ring buffer, also known as circular buffer.
|
||||||
*/
|
*/
|
||||||
struct RingBuf {
|
struct RingBuf {
|
||||||
rb_size_t struct_size; /* Size of the struct */
|
rb_size_t struct_size; /** Size of the struct */
|
||||||
rb_size_t capacity; /* The physical capacity of the entire ringbuf */
|
rb_size_t capacity; /** The physical capacity of the entire ringbuf */
|
||||||
rb_size_t count; /* The number of elements in the buffer */
|
rb_size_t count; /** The number of elements in the buffer */
|
||||||
void *write_head; /* Address of the write head */
|
void *write_head; /** Address of the write head */
|
||||||
void *read_head; /* Address of the read head */
|
void *read_head; /** Address of the read head */
|
||||||
void *buffer; /* The actual data */
|
void *buffer; /** The actual data */
|
||||||
void *buffer_end; /* The end of the buffer */
|
void *buffer_end; /** The end of the buffer */
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
// TODO: Perhaps unify these to RBResult?
|
/*
|
||||||
|
* Considerations: One hot encoding for the enum values
|
||||||
|
* (8 bit each with -fshort-enums)
|
||||||
|
*/
|
||||||
|
|
||||||
enum WriteResult { Full, WriteOk }; /** Result of a write */
|
/** Result of a write */
|
||||||
enum ReadResult { Empty, ReadOk }; /** Result of a read */
|
enum WriteResult {
|
||||||
|
WriteOk, /** The write was successful */
|
||||||
|
Full, /** The buffer is full */
|
||||||
|
InsufficientSpace /** There is not enough space to write */
|
||||||
|
};
|
||||||
|
|
||||||
/** Initialize the ring buffer */
|
/** Result of a read */
|
||||||
void rb_init(struct RingBuf *rb, rb_size_t capacity, void *(*alloc)(rb_size_t),
|
enum ReadResult {
|
||||||
|
Empty, /** The buffer is empty */
|
||||||
|
ReadOk, /** The read was successful */
|
||||||
|
InsufficientData /** There is not enough data to read */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the ring buffer
|
||||||
|
* @param rb The ring buffer to initialize
|
||||||
|
* @param capacity The capacity of the ring buffer
|
||||||
|
* @param alloc The allocator function
|
||||||
|
* @param struct_size The size of the struct
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
void rb_init(struct RingBuf *rb, rb_size_t capacity, ALLOC_T alloc,
|
||||||
rb_size_t struct_size);
|
rb_size_t struct_size);
|
||||||
|
|
||||||
/** Insert data to the ring buffer */
|
/**
|
||||||
|
* @brief Clear the ring buffer
|
||||||
|
* @details This function will reset the read and write heads to the beginning
|
||||||
|
* of the buffer, and set the count to 0. It will not free the buffer.
|
||||||
|
* @param rb The ring buffer
|
||||||
|
*/
|
||||||
|
void rb_clear(struct RingBuf *rb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Insert data to the ring buffer
|
||||||
|
* @param rb The ring buffer
|
||||||
|
* @param item The item to insert
|
||||||
|
* @param memcpy_fn The memcpy function
|
||||||
|
* @return WriteResult
|
||||||
|
*/
|
||||||
enum WriteResult rb_push_back(struct RingBuf *rb, const void *item,
|
enum WriteResult rb_push_back(struct RingBuf *rb, const void *item,
|
||||||
MEMCPY_T memcpy_fn);
|
MEMCPY_T memcpy_fn);
|
||||||
|
|
||||||
/** Read data from the ring buffer */
|
/**
|
||||||
enum ReadResult rb_pop_front(struct RingBuf *rb, void *item);
|
* @brief Insert multiple data to the ring buffer
|
||||||
|
*
|
||||||
|
* @details This function is more efficient than calling rb_push_back multiple
|
||||||
|
* times. It only advances the write head once, and attempts to write all the
|
||||||
|
* memory in one go.
|
||||||
|
*
|
||||||
|
* If n is greater than the capacity, it will return Full.
|
||||||
|
* If the full write will overflow, it will wrap around.
|
||||||
|
*
|
||||||
|
* If the buffer is full, it will return Full and not write
|
||||||
|
* anything.
|
||||||
|
*
|
||||||
|
* @param rb The ring buffer
|
||||||
|
* @param items The items to insert
|
||||||
|
* @param memcpy_fn The memcpy function
|
||||||
|
* @param n The number of items to insert
|
||||||
|
* @return WriteResult
|
||||||
|
*/
|
||||||
|
enum WriteResult rb_push_many(struct RingBuf *rb, const void *items,
|
||||||
|
MEMCPY_T memcpy_fn, rb_size_t n);
|
||||||
|
|
||||||
/** Free the ring buffer */
|
/**
|
||||||
|
* @brief Read data from the ring buffer
|
||||||
|
* @param rb The ring buffer
|
||||||
|
* @param item The item to read into
|
||||||
|
* @return ReadResult
|
||||||
|
*/
|
||||||
|
enum ReadResult rb_pop_front(struct RingBuf *rb, void *item,
|
||||||
|
MEMCPY_T memcpy_fn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Free the ring buffer
|
||||||
|
*
|
||||||
|
* @details This function is idempotent, consecutive calls will not result in a
|
||||||
|
* double free.
|
||||||
|
*
|
||||||
|
* @param rb The ring buffer
|
||||||
|
* @param free The free function
|
||||||
|
*/
|
||||||
void rb_destroy(struct RingBuf *rb, void(free)());
|
void rb_destroy(struct RingBuf *rb, void(free)());
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Debug print
|
||||||
|
*/
|
||||||
|
void rb_debug_print(struct RingBuf *rb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Debug print and empty the ringbuf
|
||||||
|
*/
|
||||||
|
void rb_debug_empty(struct RingBuf *rb);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
101
test/test_ringbuf.c
Normal file
101
test/test_ringbuf.c
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <cmocka.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "ringbuf.h"
|
||||||
|
|
||||||
|
/** Tests initialization */
|
||||||
|
static void
|
||||||
|
test_rb_init_empty(void **state) {
|
||||||
|
struct RingBuf rb;
|
||||||
|
rb_size_t capacity = 10;
|
||||||
|
rb_size_t struct_size = sizeof(int);
|
||||||
|
|
||||||
|
rb_init(&rb, capacity, malloc, struct_size);
|
||||||
|
|
||||||
|
assert_int_equal(rb.capacity, capacity);
|
||||||
|
assert_int_equal(rb.count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tests push_back */
|
||||||
|
static void
|
||||||
|
test_rb_push_back(void **state) {
|
||||||
|
struct RingBuf rb;
|
||||||
|
rb_size_t capacity = 10;
|
||||||
|
rb_size_t struct_size = sizeof(int);
|
||||||
|
|
||||||
|
rb_init(&rb, capacity, malloc, struct_size);
|
||||||
|
|
||||||
|
int data = 10;
|
||||||
|
|
||||||
|
enum WriteResult wr = rb_push_back(&rb, (void *)&data, memcpy);
|
||||||
|
assert_int_equal(wr, WriteOk);
|
||||||
|
|
||||||
|
assert_int_equal(rb.capacity, capacity);
|
||||||
|
assert_int_equal(rb.count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tests the destroy function */
|
||||||
|
static void
|
||||||
|
test_rb_init_destroy(void **state) {
|
||||||
|
struct RingBuf rb = { 0, 0, 0, NULL, NULL, NULL, NULL };
|
||||||
|
rb_size_t capacity = 10;
|
||||||
|
rb_size_t struct_size = sizeof(int);
|
||||||
|
|
||||||
|
rb_init(&rb, capacity, malloc, struct_size);
|
||||||
|
|
||||||
|
int data = 10;
|
||||||
|
|
||||||
|
rb_push_back(&rb, (void *)&data, memcpy);
|
||||||
|
|
||||||
|
assert_int_equal(rb.capacity, capacity);
|
||||||
|
assert_int_equal(rb.count, 1);
|
||||||
|
|
||||||
|
rb_destroy(&rb, free);
|
||||||
|
|
||||||
|
assert_null(rb.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tests fill, but not overflow/wrap-around */
|
||||||
|
static void
|
||||||
|
test_rb_push_back_fill(void **state) {
|
||||||
|
struct RingBuf rb = { 0, 0, 0, NULL, NULL, NULL, NULL };
|
||||||
|
rb_size_t capacity = 10;
|
||||||
|
rb_size_t struct_size = sizeof(int);
|
||||||
|
|
||||||
|
rb_init(&rb, capacity, malloc, struct_size);
|
||||||
|
|
||||||
|
const int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
|
||||||
|
|
||||||
|
// Insert them all sequentially
|
||||||
|
for(int i = 0; rb_push_back(&rb, &arr[i], memcpy) == WriteOk; i++);
|
||||||
|
assert_int_equal(rb.count, 10);
|
||||||
|
assert_int_equal(rb.capacity, capacity);
|
||||||
|
|
||||||
|
// Read them out and assert expected value
|
||||||
|
for(int i = 0, d = __INT_MAX__; rb_pop_front(&rb, &d, memcpy) == ReadOk;
|
||||||
|
i++) {
|
||||||
|
assert_int_equal(d, arr[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_int_equal(rb.capacity, capacity);
|
||||||
|
assert_int_equal(rb.count, 0);
|
||||||
|
|
||||||
|
rb_destroy(&rb, free);
|
||||||
|
|
||||||
|
assert_null(rb.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void) {
|
||||||
|
const struct CMUnitTest tests[] = {
|
||||||
|
cmocka_unit_test(test_rb_init_empty),
|
||||||
|
cmocka_unit_test(test_rb_push_back),
|
||||||
|
cmocka_unit_test(test_rb_init_destroy),
|
||||||
|
cmocka_unit_test(test_rb_push_back_fill),
|
||||||
|
};
|
||||||
|
|
||||||
|
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue