summaryrefslogtreecommitdiffstats
path: root/sys/vm/vm_radix.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/vm/vm_radix.c')
-rw-r--r--sys/vm/vm_radix.c435
1 files changed, 435 insertions, 0 deletions
diff --git a/sys/vm/vm_radix.c b/sys/vm/vm_radix.c
new file mode 100644
index 0000000..b8d438d
--- /dev/null
+++ b/sys/vm/vm_radix.c
@@ -0,0 +1,435 @@
+/*
+ * Copyright (c) 2008 Mayur Shardul <mayur.shardul@gmail.com>
+ * Copyright (c) 2011 Jeffrey Roberson <jeff@freebsd.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+
+/*
+ * Radix tree implementation.
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/conf.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/ktr.h>
+#include <vm/vm.h>
+#include <vm/vm_radix.h>
+#include <vm/vm_object.h>
+
+#include <sys/kdb.h>
+
+
+SLIST_HEAD(, vm_radix_node) res_rnodes_head =
+ SLIST_HEAD_INITIALIZER(res_rnodes_head);
+
+struct mtx rnode_lock;
+vm_offset_t rnode_start;
+vm_offset_t rnode_end;
+
+/*
+ * Allocate a radix node. Initializes all elements to 0.
+ */
+static struct vm_radix_node *
+vm_radix_node_get(void)
+{
+ struct vm_radix_node *rnode;
+
+ if (VM_OBJECT_LOCKED(kernel_object) || VM_OBJECT_LOCKED(kmem_object)){
+ mtx_lock_spin(&rnode_lock);
+ if (!SLIST_EMPTY(&res_rnodes_head)) {
+ rnode = SLIST_FIRST(&res_rnodes_head);
+ SLIST_REMOVE_HEAD(&res_rnodes_head, next);
+ mtx_unlock_spin(&rnode_lock);
+ bzero((void *)rnode, sizeof(*rnode));
+ goto out;
+ }
+ mtx_unlock_spin(&rnode_lock);
+ panic("No memory for kernel_object. . .");
+ }
+ rnode = malloc(sizeof(struct vm_radix_node), M_TEMP, M_NOWAIT | M_ZERO);
+ if (rnode == NULL) {
+ panic("vm_radix_node_get: Can not allocate memory\n");
+ return NULL;
+ }
+out:
+
+ return rnode;
+}
+
+/*
+ * Free radix node.
+ */
+static void
+vm_radix_node_put(struct vm_radix_node *rnode)
+{
+
+ KASSERT(rnode->rn_count == 0,
+ ("vm_radix_node_put: Freeing a node with %d children\n",
+ rnode->rn_count));
+ if ((vm_offset_t)rnode >= rnode_start &&
+ (vm_offset_t)rnode < rnode_end) {
+ mtx_lock_spin(&rnode_lock);
+ SLIST_INSERT_HEAD(&res_rnodes_head, rnode, next);
+ mtx_unlock_spin(&rnode_lock);
+ } else
+ free(rnode,M_TEMP);
+}
+
+/*
+ * Return the position in the array for a given level.
+ */
+static inline int
+vm_radix_slot(vm_pindex_t index, int level)
+{
+
+ return ((index >> (level * VM_RADIX_WIDTH)) & VM_RADIX_MASK);
+}
+
+/*
+ * Inserts the key-value pair in to the radix tree. Returns errno.
+ * Panics if the key already exists.
+ */
+int
+vm_radix_insert(struct vm_radix *rtree, vm_pindex_t index, void *val)
+{
+ struct vm_radix_node *rnode;
+ int slot;
+ int level;
+
+ CTR3(KTR_VM,
+ "insert: tree %p, index %p, val %p", rtree, (void *)index, val);
+ if (index == -1)
+ panic("vm_radix_insert: -1 is not a valid index.\n");
+ /*
+ * Increase the height by adding nodes at the root until
+ * there is sufficient space.
+ */
+ while (rtree->rt_height == 0 ||
+ index > VM_RADIX_MAX(rtree->rt_height)) {
+ CTR3(KTR_VM, "insert: expanding %jd > %jd height %d",
+ index, VM_RADIX_MAX(rtree->rt_height), rtree->rt_height);
+ /*
+ * Only allocate tree nodes if they are needed.
+ */
+ if (rtree->rt_root == NULL || rtree->rt_root->rn_count != 0) {
+ rnode = vm_radix_node_get();
+ if (rnode == NULL)
+ return (ENOMEM);
+ if (rtree->rt_root) {
+ rnode->rn_child[0] = rtree->rt_root;
+ rnode->rn_count = 1;
+ }
+ rtree->rt_root = rnode;
+ }
+ rtree->rt_height++;
+ KASSERT(rtree->rt_height <= VM_RADIX_LIMIT,
+ ("vm_radix_insert: Tree %p height %d too tall", rtree,
+ rtree->rt_height));
+ }
+
+ /* Now that the tree is tall enough, fill in the path to the index. */
+ rnode = rtree->rt_root;
+ for (level = rtree->rt_height - 1; level > 0; level--) {
+ slot = vm_radix_slot(index, level);
+ /* Add the required intermidiate nodes. */
+ if (rnode->rn_child[slot] == NULL) {
+ rnode->rn_child[slot] = vm_radix_node_get();
+ if (rnode->rn_child[slot] == NULL)
+ return (ENOMEM);
+ rnode->rn_count++;
+ }
+ CTR5(KTR_VM,
+ "insert: tree %p, index %p, level %d, slot %d, child %p",
+ rtree, (void *)index, level, slot, rnode->rn_child[slot]);
+ rnode = rnode->rn_child[slot];
+ }
+
+ slot = vm_radix_slot(index, level);
+ CTR5(KTR_VM, "insert: tree %p, index %p, level %d, slot %d, child %p",
+ rtree, (void *)index, level, slot, rnode->rn_child[slot]);
+ KASSERT(rnode->rn_child[slot] == NULL,
+ ("vm_radix_insert: Duplicate value %p at index: %lu\n",
+ rnode->rn_child[slot], (u_long)index));
+ rnode->rn_child[slot] = val;
+ rnode->rn_count++;
+
+ return 0;
+}
+
+/*
+ * Returns the value stored at the index. If the index is not present
+ * NULL is returned.
+ */
+void *
+vm_radix_lookup(struct vm_radix *rtree, vm_pindex_t index)
+{
+ struct vm_radix_node *rnode;
+ int slot;
+ int level;
+
+ if (index > VM_RADIX_MAX(rtree->rt_height))
+ return NULL;
+ level = rtree->rt_height - 1;
+ rnode = rtree->rt_root;
+ while (rnode) {
+ slot = vm_radix_slot(index, level);
+ CTR5(KTR_VM,
+ "lookup: tree %p, index %p, level %d, slot %d, child %p",
+ rtree, (void *)index, level, slot, rnode->rn_child[slot]);
+ if (level == 0)
+ return rnode->rn_child[slot];
+ rnode = rnode->rn_child[slot];
+ level--;
+ }
+ CTR2(KTR_VM, "lookup: tree %p, index %p failed", rtree, (void *)index);
+
+ return NULL;
+}
+
+/*
+ * Looks up as many as cnt values between start and end and stores them
+ * in the caller allocated array out. The next index can be used to
+ * restart the scan. This optimizes forward scans in the tree.
+ */
+int
+vm_radix_lookupn(struct vm_radix *rtree, vm_pindex_t start,
+ vm_pindex_t end, void **out, int cnt, vm_pindex_t *next)
+{
+ struct vm_radix_node *rnode;
+ struct vm_radix_node *child;
+ vm_pindex_t max;
+ vm_pindex_t inc;
+ int slot;
+ int level;
+ void *val;
+ int outidx;
+ int loops = 0;
+
+ CTR3(KTR_VM, "lookupn: tree %p, start %p, end %p",
+ rtree, (void *)start, (void *)end);
+ outidx = 0;
+ max = VM_RADIX_MAX(rtree->rt_height);
+ if (start > max)
+ return 0;
+ if (end > max + 1)
+ end = max + 1;
+ if (end == 0)
+ end = -1;
+restart:
+ loops++;
+ if (loops > 1000)
+ panic("vm_radix_lookupn: looping %d\n", loops);
+ /*
+ * Search the tree from the top for any leaf node holding an index
+ * between start and end.
+ */
+ level = rtree->rt_height - 1;
+ rnode = rtree->rt_root;
+ while (rnode) {
+ slot = vm_radix_slot(start, level);
+ CTR5(KTR_VM,
+ "lookupn: tree %p, index %p, level %d, slot %d, child %p",
+ rtree, (void *)start, level, slot, rnode->rn_child[slot]);
+ if (level == 0)
+ break;
+ /*
+ * If we don't have an exact match we must start our search
+ * from the next leaf and adjust our index appropriately.
+ */
+ if ((child = rnode->rn_child[slot]) == NULL) {
+ /*
+ * Calculate how much to increment our index by
+ * based on the tree level. We must truncate the
+ * lower bits to start from the begnning of the next
+ * leaf.
+ */
+ inc = 1LL << (level * VM_RADIX_WIDTH);
+ start &= ~VM_RADIX_MAX(level);
+ start += inc;
+ slot++;
+ CTR5(KTR_VM,
+ "lookupn: start %p end %p inc %d mask 0x%lX slot %d",
+ (void *)start, (void *)end, inc, ~VM_RADIX_MAX(level), slot);
+ for (; slot < VM_RADIX_COUNT && start < end;
+ slot++, start += inc) {
+ child = rnode->rn_child[slot];
+ if (child)
+ break;
+ }
+ }
+ rnode = child;
+ level--;
+ }
+ if (rnode == NULL) {
+ /*
+ * If we still have another range to search, start looking
+ * from the next node. Otherwise, return what we've already
+ * found. The loop above has already adjusted start to the
+ * beginning of the next node.
+ *
+ * Detect start wrapping back to 0 and return in this case.
+ */
+ if (start < end && start != 0)
+ goto restart;
+ goto out;
+ }
+ for (; outidx < cnt && slot < VM_RADIX_COUNT && start < end;
+ slot++, start++) {
+ val = rnode->rn_child[slot];
+ if (val == NULL)
+ continue;
+ out[outidx++] = val;
+ }
+ /*
+ * Go fetch the next page to fill the requested number of pages
+ * otherwise the caller will simply call us again to fulfill the
+ * same request after the structures are pushed out of cache.
+ */
+ if (outidx < cnt && start < end)
+ goto restart;
+
+out:
+ *next = start;
+
+ return (outidx);
+}
+
+/*
+ * Look up any entry at a position greater or equal to index.
+ */
+void *
+vm_radix_lookup_ge(struct vm_radix *rtree, vm_pindex_t index)
+{
+ vm_pindex_t max;
+ void *val;
+ int n;
+
+ max = VM_RADIX_MAX(rtree->rt_height);
+ n = vm_radix_lookupn(rtree, index, max + 1, &val, 1, &max);
+ if (n)
+ return (val);
+ return (NULL);
+}
+
+/*
+ * Look up any entry at a position less than or equal to index.
+ */
+void *
+vm_radix_lookup_le(struct vm_radix *rtree, vm_pindex_t index)
+{
+ return NULL;
+}
+
+/*
+ * Remove the specified index from the tree. If possible the height of the
+ * tree is adjusted after deletion. The value stored at index is returned
+ * panics if the key is not present.
+ */
+void *
+vm_radix_remove(struct vm_radix *rtree, vm_pindex_t index)
+{
+ struct vm_radix_node *stack[VM_RADIX_LIMIT];
+ struct vm_radix_node *rnode;
+ void *val;
+ int level;
+ int slot;
+
+ KASSERT(index <= VM_RADIX_MAX(rtree->rt_height),
+ ("vm_radix_remove: %p index %jd out of range %jd.",
+ rtree, index, VM_RADIX_MAX(rtree->rt_height)));
+ val = NULL;
+ rnode = rtree->rt_root;
+ level = rtree->rt_height - 1;
+ /*
+ * Find the node and record the path in stack.
+ */
+ while (level && rnode) {
+ stack[level] = rnode;
+ slot = vm_radix_slot(index, level);
+ rnode = rnode->rn_child[slot];
+ CTR5(KTR_VM,
+ "remove: tree %p, index %p, level %d, slot %d, child %p",
+ rtree, (void *)index, level, slot, rnode->rn_child[slot]);
+ level--;
+ }
+ slot = vm_radix_slot(index, 0);
+ KASSERT(rnode != NULL && rnode->rn_child[slot] != NULL,
+ ("vm_radix_remove: index %jd not present in the tree.\n", index));
+
+ val = rnode->rn_child[slot];
+ for (;;) {
+ rnode->rn_child[slot] = NULL;
+ rnode->rn_count--;
+ if (rnode->rn_count > 0)
+ break;
+ vm_radix_node_put(rnode);
+ if (rnode == rtree->rt_root) {
+ rtree->rt_root = NULL;
+ rtree->rt_height = 0;
+ break;
+ }
+ rnode = stack[++level];
+ slot = vm_radix_slot(index, level);
+
+ }
+ return (val);
+}
+
+/*
+ * Attempts to reduce the height of the tree.
+ */
+void
+vm_radix_shrink(struct vm_radix *rtree)
+{
+ struct vm_radix_node *tmp;
+
+ if (rtree->rt_root == NULL)
+ return;
+
+ /* Adjust the height of the tree. */
+ while (rtree->rt_root->rn_count == 1 &&
+ rtree->rt_root->rn_child[0] != NULL) {
+ tmp = rtree->rt_root;
+ rtree->rt_root = tmp->rn_child[0];
+ rtree->rt_height--;
+ tmp->rn_count--;
+ vm_radix_node_put(tmp);
+ }
+ /* Finally see if we have an empty tree. */
+ if (rtree->rt_root->rn_count == 0) {
+ vm_radix_node_put(rtree->rt_root);
+ rtree->rt_root = NULL;
+ rtree->rt_height = 0;
+ }
+}
OpenPOWER on IntegriCloud