From 1f6dee3a9ac79cc2b3c4fffb1b1bfe20fe225165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20O=2E=20Sel=C3=A5sdal?= Date: Sat, 5 Jul 2025 20:03:24 +0200 Subject: [PATCH] Tidy up ringbuf and make tests --- include/ucore/ringbuf.h | 98 +++++++++++++++++++++++++++++++++++++++++ src/ringbuf.c | 93 ++++++++++++++++++++++++++++++++++++++ test/test_ringbuf.c | 48 ++++++++++++++++++++ test/test_runner.c | 10 +++-- 4 files changed, 245 insertions(+), 4 deletions(-) create mode 100644 include/ucore/ringbuf.h create mode 100644 src/ringbuf.c create mode 100644 test/test_ringbuf.c diff --git a/include/ucore/ringbuf.h b/include/ucore/ringbuf.h new file mode 100644 index 0000000..dc9b47f --- /dev/null +++ b/include/ucore/ringbuf.h @@ -0,0 +1,98 @@ +#ifndef UC_RINGBUG_H_ +#define UC_RINGBUG_H_ +#include +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UCRingBuf UCRingBuf; +struct UCRingBuf { + char *base; + uint32_t sz; + uint32_t read_idx; + uint32_t write_idx; +}; + +/** + * Creates a new ringbuffer of at least @sz length. + * The length will be rounded up to the neares page size + * + * The ringbuffer is implementd by mapping the same physical memory + * twice into virtual memory adjacent to eachother, avoiding + * a splitting up read/writes when the ringbuffer wraps around. + * + * This means accessing rb.base[rb.len] up to rb.base[rb.len * 2 - 1] + * is accessing the same memory as rb.base[0] up to rb.base[rb.len -1] + * + * @param rb UCRingBuf to initialize + * @param sz minimum length of the ring buffer + * @returns 0 on success + */ +int uc_ringbuf_init(UCRingBuf *rb, uint32_t sz); + +/** Free resources used by the ring buffer. + * + * @return 0 on success + */ +int uc_ringbuf_destroy(UCRingBuf *rb); + +/** + * Write data to the ringbuffer. The number of bytes to write must fit + * in the free space of the ringbuffer + * + * @param rb UCRingBuf to write to. + * @param data data to write + * @param sz number of bytes to write from data + * @return 0 on success + */ +int uc_ringbuf_write(UCRingBuf *rb, const void *restrict data, uint32_t sz); + +/** Read data from the ringbuffer + * + * @param rb UCRingBuf to write to. + * @param data buffer to copy data into + * @param sz number of bytes to read, must not exceed available data + * in the buffer + * @return 0 on success + */ +int uc_ringbuf_read(UCRingBuf *rb, void *restrict data, uint32_t sz); + + +/** Inspect how much free space is available for writing to the ring buffer + * + * @param rb UCRingBuf to inspect + * @returns number of free bytes + */ +uint32_t uc_ringbuf_available(const UCRingBuf *rb); + +/** + * Length of available data in the ringbuffer + * + * @param rb UCRingBuf get length of + * + * @return length of data available to read + */ +static inline uint32_t uc_ringbuf_len(const UCRingBuf *rb) +{ + return rb->write_idx - rb->read_idx; +} + +/** + * Get a pointer to the first unread byte + * Caller is responsible for reading no more than the available + * data + * + * @param rb UCRingBuf to peek into + * + * @returns pointer to first unread byte +*/ +static inline void *uc_ringbuf_peek(const UCRingBuf *rb) +{ + return &rb->base[rb->read_idx]; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/ringbuf.c b/src/ringbuf.c new file mode 100644 index 0000000..3f47693 --- /dev/null +++ b/src/ringbuf.c @@ -0,0 +1,93 @@ +#ifdef __linux +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include "ucore/ringbuf.h" + + +int uc_ringbuf_init(UCRingBuf *rb, uint32_t sz) +{ + + uint32_t pagesz = getpagesize(); + // Round up to page size + sz = ((sz + pagesz - 1) / pagesz) * pagesz; + /* Reserve contiguous address space*/ + void *base = mmap(NULL, sz * 2, PROT_NONE, MAP_POPULATE|MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + if (base == NULL) { + return -errno; + } + /* MAP_PRIVATE doesn't work */ + base = mmap(base, sz * 1, PROT_READ|PROT_WRITE, MAP_POPULATE|MAP_ANONYMOUS|MAP_FIXED|MAP_SHARED, -1, 0); + if (base == MAP_FAILED) { + return -errno; + } + void *mbase = mremap(base, sz * 1, sz, MREMAP_FIXED|MREMAP_MAYMOVE|MREMAP_DONTUNMAP, base + sz); + if (mbase == MAP_FAILED) { + munmap(base, sz); + return -errno; + + } + + rb->base = base; + rb->sz = sz; + rb->read_idx = 0; + rb->write_idx = 0; + + return 0; +} + +int uc_ringbuf_destroy(UCRingBuf *rb) +{ + int rc = munmap(rb->base, rb->sz); + if (rc != 0) { + return -errno; + } + + rc = munmap(rb->base + rb->sz, rb->sz); + if (rc != 0) { + return -errno; + } + + rb->base = NULL; + + return 0; +} + +uint32_t uc_ringbuf_available(const UCRingBuf *rb) +{ + return rb->sz - rb->write_idx + rb->read_idx; +} + +int uc_ringbuf_write(UCRingBuf *rb, const void *restrict data, uint32_t len) +{ + if (rb->write_idx - rb->read_idx + len > rb->sz) { + return -ENOMEM; + } + memcpy(&rb->base[rb->write_idx], data, len); + rb->write_idx += len; + + return 0; +} + +int uc_ringbuf_read(UCRingBuf *rb, void *restrict data, uint32_t len) +{ + if (uc_ringbuf_len(rb) < len) { + return -ENOMEM; + } + + memcpy(data, &rb->base[rb->read_idx], len); + + rb->read_idx += len; + if (rb->read_idx == rb->write_idx) { + rb->read_idx = rb->write_idx = 0; + } else if (rb->read_idx > rb->sz) { + rb->read_idx -= rb->sz; + rb->write_idx -= rb->sz; + } + return 0; +} + +#endif diff --git a/test/test_ringbuf.c b/test/test_ringbuf.c new file mode 100644 index 0000000..a11b430 --- /dev/null +++ b/test/test_ringbuf.c @@ -0,0 +1,48 @@ +#include +#include +#include "ucore/ringbuf.h" + +START_TEST (test_ringbuf) +{ + + UCRingBuf rb; + int rc; + int pgsize = getpagesize(); + rc = uc_ringbuf_init(&rb, pgsize); + ck_assert_int_eq(rc, 0); + ck_assert_int_eq(uc_ringbuf_available(&rb), pgsize); + + + for (int i = 0 ; i < pgsize; i++) { + unsigned char data = i % 256; + rc = uc_ringbuf_write(&rb, &data, 1); + ck_assert_int_eq(rc, 0); + + } + ck_assert_int_eq(uc_ringbuf_available(&rb), 0); + + + for (int i = 0; i < pgsize; i++) { + unsigned char data = 0; + rc = uc_ringbuf_read(&rb, &data, 1); + ck_assert_int_eq(rc, 0); + ck_assert_int_eq(data, i % 256); + } + ck_assert_int_eq(uc_ringbuf_available(&rb), pgsize); + +} + +Suite *ringbuf_suite(void) +{ + Suite *s = suite_create("ringbuf"); + TCase *tc1 = tcase_create("ringbuf"); + + tcase_add_test(tc1, test_ringbuf); +#ifndef __APPLE__ +#endif + + suite_add_tcase(s, tc1); + + return s; +} + diff --git a/test/test_runner.c b/test/test_runner.c index 4554656..88640ad 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -34,6 +34,7 @@ extern Suite *ticket_lock_suite(void); extern Suite *restart_counter_suite(void); extern Suite *iomux_suite(void); extern Suite *iomux_signal_suite(void); +extern Suite *ringbuf_suite(void); static suite_func suites[] = { bitvec_suite, @@ -62,7 +63,8 @@ static suite_func suites[] = { ticket_lock_suite, restart_counter_suite, iomux_suite, - iomux_signal_suite + iomux_signal_suite, + ringbuf_suite }; @@ -105,7 +107,7 @@ main (int argc, char *argv[]) size_t i; SRunner *sr = srunner_create (NULL); - if (xml_output) + if (xml_output) srunner_set_xml(sr, "result.xml"); //set the environment variable CK_FORK=no which means don't fork(). @@ -131,10 +133,10 @@ main (int argc, char *argv[]) number_run = srunner_ntests_run(sr); number_failed = srunner_ntests_failed (sr); srunner_free (sr); - + if (!number_run) puts("ERROR: no tests were run"); - if (number_failed) + if (number_failed) printf("ERROR: %d tests failed\n", number_failed); return (number_failed != 0) ;