Tidy up ringbuf and make tests

This commit is contained in:
Nils O. Selåsdal
2025-07-05 20:03:24 +02:00
parent 7373a93f9b
commit 1f6dee3a9a
4 changed files with 245 additions and 4 deletions
+98
View File
@@ -0,0 +1,98 @@
#ifndef UC_RINGBUG_H_
#define UC_RINGBUG_H_
#include <stdint.h>
#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
+93
View File
@@ -0,0 +1,93 @@
#ifdef __linux
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#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
+48
View File
@@ -0,0 +1,48 @@
#include <check.h>
#include <unistd.h>
#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;
}
+3 -1
View File
@@ -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
};