[systemd-devel] [RFC 02/12] ring: add basic ring-buffer helper

David Herrmann dh.herrmann at gmail.com
Wed Nov 27 10:48:37 PST 2013


This adds a very straightforward ring-buffer implementation to
libsystemd-shared. Ring-buffers allow pushing data to, and pulling data
from, both ends of a buffer without modifying existing/remaining buffer
content. This is very useful for network buffers and asynchronous writes.

This implementation only contains the most basic functions to push data
onto the end of the buffer and pull data from the front. More helpers may
be added once needed.
---
 Makefile.am       |   2 +
 src/shared/ring.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/shared/ring.h |  54 ++++++++++++++
 3 files changed, 269 insertions(+)
 create mode 100644 src/shared/ring.c
 create mode 100644 src/shared/ring.h

diff --git a/Makefile.am b/Makefile.am
index 0119751..4aa2bdf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -768,6 +768,8 @@ libsystemd_shared_la_SOURCES = \
 	src/shared/net-util.h \
 	src/shared/errno-list.c \
 	src/shared/errno-list.h \
+	src/shared/ring.h \
+	src/shared/ring.c \
 	src/shared/syscall-list.c \
 	src/shared/syscall-list.h
 
diff --git a/src/shared/ring.c b/src/shared/ring.c
new file mode 100644
index 0000000..f60f098
--- /dev/null
+++ b/src/shared/ring.c
@@ -0,0 +1,213 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 David Herrmann <dh.herrmann at gmail.com>
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+
+#include "ring.h"
+#include "util.h"
+
+#define RING_MASK(_r, _v) ((_v) & ((_r)->size - 1))
+
+void ring_flush(Ring *r) {
+        r->start = 0;
+        r->end = 0;
+}
+
+void ring_clear(Ring *r) {
+        free(r->buf);
+        memset(r, 0, sizeof(*r));
+}
+
+/*
+ * Resize ring-buffer to size @nsize. @nsize must be a power-of-2, otherwise
+ * ring operations will behave incorrectly.
+ */
+static int ring_resize(Ring *r, size_t nsize) {
+        char *buf;
+
+        buf = malloc(nsize);
+        if (!buf)
+                return -ENOMEM;
+
+        if (r->end == r->start) {
+                r->end = 0;
+                r->start = 0;
+        } else if (r->end > r->start) {
+                memcpy(buf, &r->buf[r->start], r->end - r->start);
+
+                r->end -= r->start;
+                r->start = 0;
+        } else {
+                memcpy(buf, &r->buf[r->start], r->size - r->start);
+                memcpy(&buf[r->size - r->start], r->buf, r->end);
+
+                r->end += r->size - r->start;
+                r->start = 0;
+        }
+
+        free(r->buf);
+        r->buf = buf;
+        r->size = nsize;
+
+        return 0;
+}
+
+/* Compute next higher power-of-2 of @v. Returns 4096 in case v is 0. */
+static size_t ring_pow2(size_t v) {
+        size_t i;
+
+        if (!v)
+                return 4096;
+
+        --v;
+
+        for (i = 1; i < 8 * sizeof(size_t); i *= 2)
+                v |= v >> i;
+
+        return ++v;
+}
+
+/*
+ * Resize ring-buffer to provide enough room for @add bytes of new data. This
+ * resizes the buffer if it is too small. It returns -ENOMEM on OOM and 0 on
+ * success.
+ */
+static int ring_grow(Ring *r, size_t add) {
+        size_t len;
+
+        /*
+         * Note that "end == start" means "empty buffer". Hence, we can never
+         * fill the last byte of a buffer. That means, we must account for an
+         * additional byte here ("end == start"-byte).
+         */
+
+        if (r->end < r->start)
+                len = r->start - r->end;
+        else
+                len = r->start + r->size - r->end;
+
+        /* don't use ">=" as "end == start" would be ambigious */
+        if (len > add)
+                return 0;
+
+        /* +1 for additional "end == start" byte */
+        len = r->size + add - len + 1;
+        len = ring_pow2(len);
+
+        if (len <= r->size)
+                return -ENOMEM;
+
+        return ring_resize(r, len);
+}
+
+/*
+ * Push @len bytes from @u8 into the ring buffer. The buffer is resized if it
+ * is too small. -ENOMEM is returned on OOM, 0 on success.
+ */
+int ring_push(Ring *r, const char *u8, size_t len) {
+        int err;
+        size_t l;
+
+        err = ring_grow(r, len);
+        if (err < 0)
+                return err;
+
+        if (r->start <= r->end) {
+                l = r->size - r->end;
+                if (l > len)
+                        l = len;
+
+                memcpy(&r->buf[r->end], u8, l);
+                r->end = RING_MASK(r, r->end + l);
+
+                len -= l;
+                u8 += l;
+        }
+
+        if (!len)
+                return 0;
+
+        memcpy(&r->buf[r->end], u8, len);
+        r->end = RING_MASK(r, r->end + len);
+
+        return 0;
+}
+
+/*
+ * Remove @len bytes from the start of the ring-buffer. Note that we protect
+ * against overflows so removing more bytes than available is safe.
+ */
+void ring_pull(Ring *r, size_t len) {
+        size_t l;
+
+        if (r->start > r->end) {
+                l = r->size - r->start;
+                if (l > len)
+                        l = len;
+
+                r->start = RING_MASK(r, r->start + l);
+                len -= l;
+        }
+
+        if (!len)
+                return;
+
+        l = r->end - r->start;
+        if (l > len)
+                l = len;
+
+        r->start = RING_MASK(r, r->start + l);
+}
+
+/*
+ * Get data pointers for current ring-buffer data. @vec must be an array of 2
+ * iovec objects. They are filled according to the data available in the
+ * ring-buffer. 0, 1 or 2 is returned according to the number of iovec objects
+ * that were filled (0 meaning buffer is empty).
+ *
+ * Hint: "struct iovec" is defined in <sys/uio.h> and looks like this:
+ *     struct iovec {
+ *         void *iov_base;
+ *         size_t iov_len;
+ *     };
+ */
+size_t ring_peek(Ring *r, struct iovec *vec) {
+        if (r->end > r->start) {
+                if (vec) {
+                        vec[0].iov_base = &r->buf[r->start];
+                        vec[0].iov_len = r->end - r->start;
+                }
+                return 1;
+        } else if (r->end < r->start) {
+                if (vec) {
+                        vec[0].iov_base = &r->buf[r->start];
+                        vec[0].iov_len = r->size - r->start;
+                        vec[1].iov_base = r->buf;
+                        vec[1].iov_len = r->end;
+                }
+                return r->end ? 2 : 1;
+        } else {
+                return 0;
+        }
+}
diff --git a/src/shared/ring.h b/src/shared/ring.h
new file mode 100644
index 0000000..d2cd016
--- /dev/null
+++ b/src/shared/ring.h
@@ -0,0 +1,54 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 David Herrmann <dh.herrmann at gmail.com>
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+/* Pretty straightforward ring-buffer implementation. */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include "util.h"
+
+typedef struct Ring Ring;
+
+struct Ring {
+        char *buf;
+        size_t size;
+        size_t start;
+        size_t end;
+};
+
+void ring_flush(Ring *r);
+void ring_clear(Ring *r);
+
+int ring_push(Ring *r, const char *u8, size_t len);
+void ring_pull(Ring *r, size_t len);
+size_t ring_peek(Ring *r, struct iovec *vec);
+
+static inline size_t ring_length(Ring *r) {
+        if (r->end > r->start)
+                return r->end - r->start;
+        else if (r->end < r->start)
+                return (r->size - r->start) + r->end;
+        else
+                return 0;
+}
-- 
1.8.4.2



More information about the systemd-devel mailing list