All of lore.kernel.org
 help / color / mirror / Atom feed
From: David Herrmann <dh.herrmann@gmail.com>
To: linux-kernel@vger.kernel.org
Cc: Andy Lutomirski <luto@amacapital.net>,
	Jiri Kosina <jikos@kernel.org>, Greg KH <greg@kroah.com>,
	Hannes Reinecke <hare@suse.com>,
	Steven Rostedt <rostedt@goodmis.org>,
	Arnd Bergmann <arnd@arndb.de>, Tom Gundersen <teg@jklm.no>,
	David Herrmann <dh.herrmann@gmail.com>,
	Josh Triplett <josh@joshtriplett.org>,
	Linus Torvalds <torvalds@linux-foundation.org>,
	Andrew Morton <akpm@linux-foundation.org>
Subject: [RFC v1 04/14] bus1: util - fixed list utility library
Date: Wed, 26 Oct 2016 21:18:00 +0200	[thread overview]
Message-ID: <20161026191810.12275-5-dh.herrmann@gmail.com> (raw)
In-Reply-To: <20161026191810.12275-1-dh.herrmann@gmail.com>

From: Tom Gundersen <teg@jklm.no>

This implements a fixed-size list called bus1_flist. The size of
the list must be constant over the lifetime of the list. The list
can hold one arbitrary pointer per node.

Fixed lists are a combination of a linked list and a static array.
That is, fixed lists behave like linked lists (no random access, but
arbitrary size), but compare in speed with arrays (consequetive
accesses are fast). Unlike fixed arrays, fixed lists can hold huge
number of elements without requiring vmalloc, but solely relying on
small-size kmalloc allocations.

Internally, fixed lists are a singly-linked list of static arrays.
This guarantees that iterations behave almost like on an array,
except when crossing a batch-border.

Fixed lists can replace fixed-size arrays whenever you need to support
large number of elements, but don't need random access. Fixed lists
have ALMOST the same memory requirements as fixed-size arrays, except
one pointer of state per 'BUS1_FLIST_BATCH' elements. If only a small
size (i.e., it only requires one batch) is stored in a fixed list,
then its memory requirements and iteration time are equivalent to
fixed-size arrays.

Fixed lists will be required by the upcoming bus1 message-transactions.
They must support large auxiliary data transfers, in case users want to
send their entire handle state via the bus.

Signed-off-by: Tom Gundersen <teg@jklm.no>
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
 ipc/bus1/Makefile     |   3 +-
 ipc/bus1/util/flist.c | 116 +++++++++++++++++++++++++++++
 ipc/bus1/util/flist.h | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 320 insertions(+), 1 deletion(-)
 create mode 100644 ipc/bus1/util/flist.c
 create mode 100644 ipc/bus1/util/flist.h

diff --git a/ipc/bus1/Makefile b/ipc/bus1/Makefile
index 9e491691..6db6d13 100644
--- a/ipc/bus1/Makefile
+++ b/ipc/bus1/Makefile
@@ -1,6 +1,7 @@
 bus1-y :=			\
 	main.o			\
-	util/active.o
+	util/active.o		\
+	util/flist.o
 
 obj-$(CONFIG_BUS1) += bus1.o
 
diff --git a/ipc/bus1/util/flist.c b/ipc/bus1/util/flist.c
new file mode 100644
index 0000000..b8b0d4e
--- /dev/null
+++ b/ipc/bus1/util/flist.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2013-2016 Red Hat, Inc.
+ *
+ * This program 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include "flist.h"
+
+/**
+ * bus1_flist_populate() - populate an flist
+ * @list:		flist to operate on
+ * @n:			number of elements
+ * @gfp:		GFP to use for allocations
+ *
+ * Populate an flist. This pre-allocates the backing memory for an flist that
+ * was statically initialized via bus1_flist_init(). This is NOT needed if the
+ * list was allocated via bus1_flist_new().
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int bus1_flist_populate(struct bus1_flist *list, size_t n, gfp_t gfp)
+{
+	if (gfp & __GFP_ZERO)
+		memset(list, 0, bus1_flist_inline_size(n));
+
+	if (unlikely(n > BUS1_FLIST_BATCH)) {
+		/* Never populate twice! */
+		WARN_ON(list[BUS1_FLIST_BATCH].next);
+
+		n -= BUS1_FLIST_BATCH;
+		list[BUS1_FLIST_BATCH].next = bus1_flist_new(n, gfp);
+		if (!list[BUS1_FLIST_BATCH].next)
+			return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/**
+ * bus1_flist_new() - allocate new flist
+ * @n:			number of elements
+ * @gfp:		GFP to use for allocations
+ *
+ * This allocates a new flist ready to store @n elements.
+ *
+ * Return: Pointer to flist, NULL if out-of-memory.
+ */
+struct bus1_flist *bus1_flist_new(size_t n, gfp_t gfp)
+{
+	struct bus1_flist list, *e, *slot;
+	size_t remaining;
+
+	list.next = NULL;
+	slot = &list;
+	remaining = n;
+
+	while (remaining >= BUS1_FLIST_BATCH) {
+		e = kmalloc_array(sizeof(*e), BUS1_FLIST_BATCH + 1, gfp);
+		if (!e)
+			return bus1_flist_free(list.next, n);
+
+		slot->next = e;
+		slot = &e[BUS1_FLIST_BATCH];
+		slot->next = NULL;
+
+		remaining -= BUS1_FLIST_BATCH;
+	}
+
+	if (remaining > 0) {
+		slot->next = kmalloc_array(remaining, sizeof(*e), gfp);
+		if (!slot->next)
+			return bus1_flist_free(list.next, n);
+	}
+
+	return list.next;
+}
+
+/**
+ * bus1_flist_free() - free flist
+ * @list:		flist to operate on, or NULL
+ * @n:			number of elements
+ *
+ * This deallocates an flist previously created via bus1_flist_new().
+ *
+ * If NULL is passed, this is a no-op.
+ *
+ * Return: NULL is returned.
+ */
+struct bus1_flist *bus1_flist_free(struct bus1_flist *list, size_t n)
+{
+	struct bus1_flist *e;
+
+	if (list) {
+		/*
+		 * If @list was only partially allocated, then "next" pointers
+		 * might be NULL. So check @list on each iteration.
+		 */
+		while (list && n >= BUS1_FLIST_BATCH) {
+			e = list;
+			list = list[BUS1_FLIST_BATCH].next;
+			kfree(e);
+			n -= BUS1_FLIST_BATCH;
+		}
+
+		kfree(list);
+	}
+
+	return NULL;
+}
diff --git a/ipc/bus1/util/flist.h b/ipc/bus1/util/flist.h
new file mode 100644
index 0000000..e265d5c
--- /dev/null
+++ b/ipc/bus1/util/flist.h
@@ -0,0 +1,202 @@
+#ifndef __BUS1_FLIST_H
+#define __BUS1_FLIST_H
+
+/*
+ * Copyright (C) 2013-2016 Red Hat, Inc.
+ *
+ * This program 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.
+ */
+
+/**
+ * DOC: Fixed Lists
+ *
+ * This implements a fixed-size list called bus1_flist. The size of the list
+ * must be constant over the lifetime of the list. The list can hold one
+ * arbitrary pointer per node.
+ *
+ * Fixed lists are a combination of a linked list and a static array. That is,
+ * fixed lists behave like linked lists (no random access, but arbitrary size),
+ * but compare in speed with arrays (consequetive accesses are fast). Unlike
+ * fixed arrays, fixed lists can hold huge number of elements without requiring
+ * vmalloc(), but solely relying on small-size kmalloc() allocations.
+ *
+ * Internally, fixed lists are a singly-linked list of static arrays. This
+ * guarantees that iterations behave almost like on an array, except when
+ * crossing a batch-border.
+ *
+ * Fixed lists can replace fixed-size arrays whenever you need to support large
+ * number of elements, but don't need random access. Fixed lists have ALMOST
+ * the same memory requirements as fixed-size arrays, except one pointer of
+ * state per 'BUS1_FLIST_BATCH' elements. If only a small size (i.e., it only
+ * requires one batch) is stored in a fixed list, then its memory requirements
+ * and iteration time are equivalent to fixed-size arrays.
+ */
+
+#include <linux/kernel.h>
+
+#define BUS1_FLIST_BATCH (1024)
+
+/**
+ * struct bus1_flist - fixed list
+ * @next:		pointer to next batch
+ * @ptr:		stored entry
+ */
+struct bus1_flist {
+	union {
+		struct bus1_flist *next;
+		void *ptr;
+	};
+};
+
+int bus1_flist_populate(struct bus1_flist *flist, size_t n, gfp_t gfp);
+struct bus1_flist *bus1_flist_new(size_t n, gfp_t gfp);
+struct bus1_flist *bus1_flist_free(struct bus1_flist *list, size_t n);
+
+/**
+ * bus1_flist_inline_size() - calculate required inline size
+ * @n:			number of entries
+ *
+ * When allocating storage for an flist, this calculates the size of the
+ * initial array in bytes. Use bus1_flist_new() directly if you want to
+ * allocate an flist on the heap. This helper is only needed if you embed an
+ * flist into another struct like this:
+ *
+ *     struct foo {
+ *             ...
+ *             struct bus1_flist list[];
+ *     };
+ *
+ * In that case the flist must be the last element, and the size in bytes
+ * required by it is returned by this function.
+ *
+ * The inline-size of an flist is always bound to a fixed maximum. That is,
+ * regardless of @n, this will always return a reasonable number that can be
+ * allocated via kmalloc().
+ *
+ * Return: Size in bytes required for the initial batch of an flist.
+ */
+static inline size_t bus1_flist_inline_size(size_t n)
+{
+	return sizeof(struct bus1_flist) *
+		((likely(n < BUS1_FLIST_BATCH)) ? n : (BUS1_FLIST_BATCH + 1));
+}
+
+/**
+ * bus1_flist_init() - initialize an flist
+ * @list:		flist to initialize
+ * @n:			number of entries
+ *
+ * This initializes an flist of size @n. It does NOT preallocate the memory,
+ * but only initializes @list in a way that bus1_flist_deinit() can be called
+ * on it. Use bus1_flist_populate() to populate the flist.
+ *
+ * This is only needed if your backing memory of @list is shared with another
+ * object. If possible, use bus1_flist_new() to allocate an flist on the heap
+ * and avoid this dance.
+ */
+static inline void bus1_flist_init(struct bus1_flist *list, size_t n)
+{
+	BUILD_BUG_ON(sizeof(struct bus1_flist) != sizeof(void *));
+
+	if (unlikely(n >= BUS1_FLIST_BATCH))
+		list[BUS1_FLIST_BATCH].next = NULL;
+}
+
+/**
+ * bus1_flist_deinit() - deinitialize an flist
+ * @list:		flist to deinitialize
+ * @n:			number of entries
+ *
+ * This deallocates an flist and releases all resources. If already
+ * deinitialized, this is a no-op. This is only needed if you called
+ * bus1_flist_populate().
+ */
+static inline void bus1_flist_deinit(struct bus1_flist *list, size_t n)
+{
+	if (unlikely(n >= BUS1_FLIST_BATCH)) {
+		bus1_flist_free(list[BUS1_FLIST_BATCH].next,
+				n - BUS1_FLIST_BATCH);
+		list[BUS1_FLIST_BATCH].next = NULL;
+	}
+}
+
+/**
+ * bus1_flist_next() - flist iterator
+ * @iter:		iterator
+ * @pos:		current position
+ *
+ * This advances an flist iterator by one position. @iter must point to the
+ * current position, and the new position is returned by this function. @pos
+ * must point to a variable that contains the current index position. That is,
+ * @pos must be initialized to 0 and @iter to the flist head.
+ *
+ * Neither @pos nor @iter must be modified by anyone but this helper. In the
+ * loop body you can use @iter->ptr to access the current element.
+ *
+ * This iterator is normally used like this:
+ *
+ *     size_t pos, n = 128;
+ *     struct bus1_flist *e, *list = bus1_flist_new(n);
+ *
+ *     ...
+ *
+ *     for (pos = 0, e = list; pos < n; e = bus1_flist_next(e, &pos)) {
+ *             ... access e->ptr ...
+ *     }
+ *
+ * Return: Next iterator position.
+ */
+static inline struct bus1_flist *bus1_flist_next(struct bus1_flist *iter,
+						 size_t *pos)
+{
+	return (++*pos % BUS1_FLIST_BATCH) ? (iter + 1) : (iter + 1)->next;
+}
+
+/**
+ * bus1_flist_walk() - walk flist in batches
+ * @list:		list to walk
+ * @n:			number of entries
+ * @iter:		iterator
+ * @pos:		current position
+ *
+ * This walks an flist in batches of size up to BUS1_FLIST_BATCH. It is
+ * normally used like this:
+ *
+ *     size_t pos, z, n = 65536;
+ *     struct bus1_flist *e, *list = bus1_flist_new(n);
+ *
+ *     ...
+ *
+ *     pos = 0;
+ *     while ((z = bus1_flist_walk(list, n, &e, &pos)) > 0) {
+ *             ... access e[0...z]->ptr
+ *             ... invariant: z <= BUS1_FLIST_BATCH
+ *             ... invariant: e[i]->ptr == (&e->ptr)[i]
+ *     }
+ *
+ * Return: Size of batch at @iter.
+ */
+static inline size_t bus1_flist_walk(struct bus1_flist *list,
+				     size_t n,
+				     struct bus1_flist **iter,
+				     size_t *pos)
+{
+	if (*pos < n) {
+		n = n - *pos;
+		if (unlikely(n > BUS1_FLIST_BATCH))
+			n = BUS1_FLIST_BATCH;
+		if (likely(*pos == 0))
+			*iter = list;
+		else
+			*iter = (*iter)[BUS1_FLIST_BATCH].next;
+		*pos += n;
+	} else {
+		n = 0;
+	}
+	return n;
+}
+
+#endif /* __BUS1_FLIST_H */
-- 
2.10.1

  parent reply	other threads:[~2016-10-26 19:21 UTC|newest]

Thread overview: 55+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-10-26 19:17 [RFC v1 00/14] Bus1 Kernel Message Bus David Herrmann
2016-10-26 19:17 ` [RFC v1 01/14] bus1: add bus1(7) man-page David Herrmann
2016-10-27 23:12   ` Kirill A. Shutemov
2016-10-26 19:17 ` [RFC v1 02/14] bus1: provide stub cdev /dev/bus1 David Herrmann
2016-10-26 23:19   ` Andy Lutomirski
2016-10-26 23:54     ` Tom Gundersen
2016-10-27  9:11       ` Arnd Bergmann
2016-10-27 15:25         ` Tom Gundersen
2016-10-27 16:37           ` Linus Torvalds
2016-10-27 16:39             ` Tom Gundersen
2016-10-29 22:13           ` Arnd Bergmann
2016-10-26 19:17 ` [RFC v1 03/14] bus1: util - active reference utility library David Herrmann
2016-10-26 19:18 ` David Herrmann [this message]
2016-10-27 12:37   ` [RFC v1 04/14] bus1: util - fixed list " Peter Zijlstra
2016-10-27 12:48     ` David Herrmann
2016-10-27 12:56       ` Arnd Bergmann
2016-10-27 13:31         ` David Herrmann
2016-10-26 19:18 ` [RFC v1 05/14] bus1: util - pool " David Herrmann
2016-10-27 12:54   ` Peter Zijlstra
2016-10-27 12:59   ` Peter Zijlstra
2016-10-27 15:00     ` Peter Zijlstra
2016-10-27 15:14   ` Peter Zijlstra
2016-10-26 19:18 ` [RFC v1 06/14] bus1: util - queue " David Herrmann
2016-10-27 15:27   ` Peter Zijlstra
2016-10-27 16:43   ` Peter Zijlstra
2016-10-28 11:33     ` Tom Gundersen
2016-10-28 13:33       ` Peter Zijlstra
2016-10-28 13:47         ` Tom Gundersen
2016-10-28 13:58           ` Peter Zijlstra
2016-10-28 14:33             ` Tom Gundersen
2016-10-28 16:49               ` Peter Zijlstra
2016-10-26 19:18 ` [RFC v1 07/14] bus1: tracking user contexts David Herrmann
2016-10-26 19:18 ` [RFC v1 08/14] bus1: implement peer management context David Herrmann
2016-10-28 12:06   ` Richard Weinberger
2016-10-28 13:18     ` Tom Gundersen
2016-10-28 13:21       ` Richard Weinberger
2016-10-28 13:05   ` Richard Weinberger
2016-10-28 13:23     ` Tom Gundersen
2016-10-28 13:54       ` Richard Weinberger
2016-10-26 19:18 ` [RFC v1 09/14] bus1: provide transaction context for multicasts David Herrmann
2016-10-28 14:37   ` Peter Zijlstra
2016-10-26 19:18 ` [RFC v1 10/14] bus1: add handle management David Herrmann
2016-10-26 19:18 ` [RFC v1 11/14] bus1: implement message transmission David Herrmann
2016-10-26 19:18 ` [RFC v1 12/14] bus1: hook up file-operations David Herrmann
2016-10-26 19:18 ` [RFC v1 13/14] bus1: limit and protect resources David Herrmann
2016-10-26 19:18 ` [RFC v1 14/14] bus1: basic user-space kselftests David Herrmann
2016-10-26 19:39 ` [RFC v1 00/14] Bus1 Kernel Message Bus Linus Torvalds
2016-10-26 20:34   ` David Herrmann
2016-10-27  0:45     ` Kirill A. Shutemov
2016-10-29 21:04       ` Josh Triplett
2016-11-02 14:45       ` David Herrmann
2017-01-30 22:11     ` Pavel Machek
2016-10-27 11:10 ` Michael Kerrisk
2016-10-28 13:11 ` Richard Weinberger
2016-10-28 13:37   ` Tom Gundersen

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20161026191810.12275-5-dh.herrmann@gmail.com \
    --to=dh.herrmann@gmail.com \
    --cc=akpm@linux-foundation.org \
    --cc=arnd@arndb.de \
    --cc=greg@kroah.com \
    --cc=hare@suse.com \
    --cc=jikos@kernel.org \
    --cc=josh@joshtriplett.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=luto@amacapital.net \
    --cc=rostedt@goodmis.org \
    --cc=teg@jklm.no \
    --cc=torvalds@linux-foundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.