1746 lines · 52.6 KB · C
RawDownload
/* Generic associative array implementation. * * See Documentation/assoc_array.txt for information. * * Copyright (C) 2013 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public Licence * as published by the Free Software Foundation; either version * 2 of the Licence, or (at your option) any later version. *///#define DEBUG#include <linux/slab.h>#include <linux/err.h>#include <linux/assoc_array_priv.h> /* * Iterate over an associative array.  The caller must hold the RCU read lock * or better. */static int assoc_array_subtree_iterate(const struct assoc_array_ptr *root,				       const struct assoc_array_ptr *stop,				       int (*iterator)(const void *leaf,						       void *iterator_data),				       void *iterator_data){	const struct assoc_array_shortcut *shortcut;	const struct assoc_array_node *node;	const struct assoc_array_ptr *cursor, *ptr, *parent;	unsigned long has_meta;	int slot, ret; 	cursor = root; begin_node:	if (assoc_array_ptr_is_shortcut(cursor)) {		/* Descend through a shortcut */		shortcut = assoc_array_ptr_to_shortcut(cursor);		smp_read_barrier_depends();		cursor = ACCESS_ONCE(shortcut->next_node);	} 	node = assoc_array_ptr_to_node(cursor);	smp_read_barrier_depends();	slot = 0; 	/* We perform two passes of each node.	 *	 * The first pass does all the leaves in this node.  This means we	 * don't miss any leaves if the node is split up by insertion whilst	 * we're iterating over the branches rooted here (we may, however, see	 * some leaves twice).	 */	has_meta = 0;	for (; slot < ASSOC_ARRAY_FAN_OUT; slot++) {		ptr = ACCESS_ONCE(node->slots[slot]);		has_meta |= (unsigned long)ptr;		if (ptr && assoc_array_ptr_is_leaf(ptr)) {			/* We need a barrier between the read of the pointer			 * and dereferencing the pointer - but only if we are			 * actually going to dereference it.			 */			smp_read_barrier_depends(); 			/* Invoke the callback */			ret = iterator(assoc_array_ptr_to_leaf(ptr),				       iterator_data);			if (ret)				return ret;		}	} 	/* The second pass attends to all the metadata pointers.  If we follow	 * one of these we may find that we don't come back here, but rather go	 * back to a replacement node with the leaves in a different layout.	 *	 * We are guaranteed to make progress, however, as the slot number for	 * a particular portion of the key space cannot change - and we	 * continue at the back pointer + 1.	 */	if (!(has_meta & ASSOC_ARRAY_PTR_META_TYPE))		goto finished_node;	slot = 0; continue_node:	node = assoc_array_ptr_to_node(cursor);	smp_read_barrier_depends(); 	for (; slot < ASSOC_ARRAY_FAN_OUT; slot++) {		ptr = ACCESS_ONCE(node->slots[slot]);		if (assoc_array_ptr_is_meta(ptr)) {			cursor = ptr;			goto begin_node;		}	} finished_node:	/* Move up to the parent (may need to skip back over a shortcut) */	parent = ACCESS_ONCE(node->back_pointer);	slot = node->parent_slot;	if (parent == stop)		return 0; 	if (assoc_array_ptr_is_shortcut(parent)) {		shortcut = assoc_array_ptr_to_shortcut(parent);		smp_read_barrier_depends();		cursor = parent;		parent = ACCESS_ONCE(shortcut->back_pointer);		slot = shortcut->parent_slot;		if (parent == stop)			return 0;	} 	/* Ascend to next slot in parent node */	cursor = parent;	slot++;	goto continue_node;} /** * assoc_array_iterate - Pass all objects in the array to a callback * @array: The array to iterate over. * @iterator: The callback function. * @iterator_data: Private data for the callback function. * * Iterate over all the objects in an associative array.  Each one will be * presented to the iterator function. * * If the array is being modified concurrently with the iteration then it is * possible that some objects in the array will be passed to the iterator * callback more than once - though every object should be passed at least * once.  If this is undesirable then the caller must lock against modification * for the duration of this function. * * The function will return 0 if no objects were in the array or else it will * return the result of the last iterator function called.  Iteration stops * immediately if any call to the iteration function results in a non-zero * return. * * The caller should hold the RCU read lock or better if concurrent * modification is possible. */int assoc_array_iterate(const struct assoc_array *array,			int (*iterator)(const void *object,					void *iterator_data),			void *iterator_data){	struct assoc_array_ptr *root = ACCESS_ONCE(array->root); 	if (!root)		return 0;	return assoc_array_subtree_iterate(root, NULL, iterator, iterator_data);} enum assoc_array_walk_status {	assoc_array_walk_tree_empty,	assoc_array_walk_found_terminal_node,	assoc_array_walk_found_wrong_shortcut,} status; struct assoc_array_walk_result {	struct {		struct assoc_array_node	*node;	/* Node in which leaf might be found */		int		level;		int		slot;	} terminal_node;	struct {		struct assoc_array_shortcut *shortcut;		int		level;		int		sc_level;		unsigned long	sc_segments;		unsigned long	dissimilarity;	} wrong_shortcut;}; /* * Navigate through the internal tree looking for the closest node to the key. */static enum assoc_array_walk_statusassoc_array_walk(const struct assoc_array *array,		 const struct assoc_array_ops *ops,		 const void *index_key,		 struct assoc_array_walk_result *result){	struct assoc_array_shortcut *shortcut;	struct assoc_array_node *node;	struct assoc_array_ptr *cursor, *ptr;	unsigned long sc_segments, dissimilarity;	unsigned long segments;	int level, sc_level, next_sc_level;	int slot; 	pr_devel("-->%s()\n", __func__); 	cursor = ACCESS_ONCE(array->root);	if (!cursor)		return assoc_array_walk_tree_empty; 	level = 0; 	/* Use segments from the key for the new leaf to navigate through the	 * internal tree, skipping through nodes and shortcuts that are on	 * route to the destination.  Eventually we'll come to a slot that is	 * either empty or contains a leaf at which point we've found a node in	 * which the leaf we're looking for might be found or into which it	 * should be inserted.	 */jumped:	segments = ops->get_key_chunk(index_key, level);	pr_devel("segments[%d]: %lx\n", level, segments); 	if (assoc_array_ptr_is_shortcut(cursor))		goto follow_shortcut; consider_node:	node = assoc_array_ptr_to_node(cursor);	smp_read_barrier_depends(); 	slot = segments >> (level & ASSOC_ARRAY_KEY_CHUNK_MASK);	slot &= ASSOC_ARRAY_FAN_MASK;	ptr = ACCESS_ONCE(node->slots[slot]); 	pr_devel("consider slot %x [ix=%d type=%lu]\n",		 slot, level, (unsigned long)ptr & 3); 	if (!assoc_array_ptr_is_meta(ptr)) {		/* The node doesn't have a node/shortcut pointer in the slot		 * corresponding to the index key that we have to follow.		 */		result->terminal_node.node = node;		result->terminal_node.level = level;		result->terminal_node.slot = slot;		pr_devel("<--%s() = terminal_node\n", __func__);		return assoc_array_walk_found_terminal_node;	} 	if (assoc_array_ptr_is_node(ptr)) {		/* There is a pointer to a node in the slot corresponding to		 * this index key segment, so we need to follow it.		 */		cursor = ptr;		level += ASSOC_ARRAY_LEVEL_STEP;		if ((level & ASSOC_ARRAY_KEY_CHUNK_MASK) != 0)			goto consider_node;		goto jumped;	} 	/* There is a shortcut in the slot corresponding to the index key	 * segment.  We follow the shortcut if its partial index key matches	 * this leaf's.  Otherwise we need to split the shortcut.	 */	cursor = ptr;follow_shortcut:	shortcut = assoc_array_ptr_to_shortcut(cursor);	smp_read_barrier_depends();	pr_devel("shortcut to %d\n", shortcut->skip_to_level);	sc_level = level + ASSOC_ARRAY_LEVEL_STEP;	BUG_ON(sc_level > shortcut->skip_to_level); 	do {		/* Check the leaf against the shortcut's index key a word at a		 * time, trimming the final word (the shortcut stores the index		 * key completely from the root to the shortcut's target).		 */		if ((sc_level & ASSOC_ARRAY_KEY_CHUNK_MASK) == 0)			segments = ops->get_key_chunk(index_key, sc_level); 		sc_segments = shortcut->index_key[sc_level >> ASSOC_ARRAY_KEY_CHUNK_SHIFT];		dissimilarity = segments ^ sc_segments; 		if (round_up(sc_level, ASSOC_ARRAY_KEY_CHUNK_SIZE) > shortcut->skip_to_level) {			/* Trim segments that are beyond the shortcut */			int shift = shortcut->skip_to_level & ASSOC_ARRAY_KEY_CHUNK_MASK;			dissimilarity &= ~(ULONG_MAX << shift);			next_sc_level = shortcut->skip_to_level;		} else {			next_sc_level = sc_level + ASSOC_ARRAY_KEY_CHUNK_SIZE;			next_sc_level = round_down(next_sc_level, ASSOC_ARRAY_KEY_CHUNK_SIZE);		} 		if (dissimilarity != 0) {			/* This shortcut points elsewhere */			result->wrong_shortcut.shortcut = shortcut;			result->wrong_shortcut.level = level;			result->wrong_shortcut.sc_level = sc_level;			result->wrong_shortcut.sc_segments = sc_segments;			result->wrong_shortcut.dissimilarity = dissimilarity;			return assoc_array_walk_found_wrong_shortcut;		} 		sc_level = next_sc_level;	} while (sc_level < shortcut->skip_to_level); 	/* The shortcut matches the leaf's index to this point. */	cursor = ACCESS_ONCE(shortcut->next_node);	if (((level ^ sc_level) & ~ASSOC_ARRAY_KEY_CHUNK_MASK) != 0) {		level = sc_level;		goto jumped;	} else {		level = sc_level;		goto consider_node;	}} /** * assoc_array_find - Find an object by index key * @array: The associative array to search. * @ops: The operations to use. * @index_key: The key to the object. * * Find an object in an associative array by walking through the internal tree * to the node that should contain the object and then searching the leaves * there.  NULL is returned if the requested object was not found in the array. * * The caller must hold the RCU read lock or better. */void *assoc_array_find(const struct assoc_array *array,		       const struct assoc_array_ops *ops,		       const void *index_key){	struct assoc_array_walk_result result;	const struct assoc_array_node *node;	const struct assoc_array_ptr *ptr;	const void *leaf;	int slot; 	if (assoc_array_walk(array, ops, index_key, &result) !=	    assoc_array_walk_found_terminal_node)		return NULL; 	node = result.terminal_node.node;	smp_read_barrier_depends(); 	/* If the target key is available to us, it's has to be pointed to by	 * the terminal node.	 */	for (slot = 0; slot < ASSOC_ARRAY_FAN_OUT; slot++) {		ptr = ACCESS_ONCE(node->slots[slot]);		if (ptr && assoc_array_ptr_is_leaf(ptr)) {			/* We need a barrier between the read of the pointer			 * and dereferencing the pointer - but only if we are			 * actually going to dereference it.			 */			leaf = assoc_array_ptr_to_leaf(ptr);			smp_read_barrier_depends();			if (ops->compare_object(leaf, index_key))				return (void *)leaf;		}	} 	return NULL;} /* * Destructively iterate over an associative array.  The caller must prevent * other simultaneous accesses. */static void assoc_array_destroy_subtree(struct assoc_array_ptr *root,					const struct assoc_array_ops *ops){	struct assoc_array_shortcut *shortcut;	struct assoc_array_node *node;	struct assoc_array_ptr *cursor, *parent = NULL;	int slot = -1; 	pr_devel("-->%s()\n", __func__); 	cursor = root;	if (!cursor) {		pr_devel("empty\n");		return;	} move_to_meta:	if (assoc_array_ptr_is_shortcut(cursor)) {		/* Descend through a shortcut */		pr_devel("[%d] shortcut\n", slot);		BUG_ON(!assoc_array_ptr_is_shortcut(cursor));		shortcut = assoc_array_ptr_to_shortcut(cursor);		BUG_ON(shortcut->back_pointer != parent);		BUG_ON(slot != -1 && shortcut->parent_slot != slot);		parent = cursor;		cursor = shortcut->next_node;		slot = -1;		BUG_ON(!assoc_array_ptr_is_node(cursor));	} 	pr_devel("[%d] node\n", slot);	node = assoc_array_ptr_to_node(cursor);	BUG_ON(node->back_pointer != parent);	BUG_ON(slot != -1 && node->parent_slot != slot);	slot = 0; continue_node:	pr_devel("Node %p [back=%p]\n", node, node->back_pointer);	for (; slot < ASSOC_ARRAY_FAN_OUT; slot++) {		struct assoc_array_ptr *ptr = node->slots[slot];		if (!ptr)			continue;		if (assoc_array_ptr_is_meta(ptr)) {			parent = cursor;			cursor = ptr;			goto move_to_meta;		} 		if (ops) {			pr_devel("[%d] free leaf\n", slot);			ops->free_object(assoc_array_ptr_to_leaf(ptr));		}	} 	parent = node->back_pointer;	slot = node->parent_slot;	pr_devel("free node\n");	kfree(node);	if (!parent)		return; /* Done */ 	/* Move back up to the parent (may need to free a shortcut on	 * the way up) */	if (assoc_array_ptr_is_shortcut(parent)) {		shortcut = assoc_array_ptr_to_shortcut(parent);		BUG_ON(shortcut->next_node != cursor);		cursor = parent;		parent = shortcut->back_pointer;		slot = shortcut->parent_slot;		pr_devel("free shortcut\n");		kfree(shortcut);		if (!parent)			return; 		BUG_ON(!assoc_array_ptr_is_node(parent));	} 	/* Ascend to next slot in parent node */	pr_devel("ascend to %p[%d]\n", parent, slot);	cursor = parent;	node = assoc_array_ptr_to_node(cursor);	slot++;	goto continue_node;} /** * assoc_array_destroy - Destroy an associative array * @array: The array to destroy. * @ops: The operations to use. * * Discard all metadata and free all objects in an associative array.  The * array will be empty and ready to use again upon completion.  This function * cannot fail. * * The caller must prevent all other accesses whilst this takes place as no * attempt is made to adjust pointers gracefully to permit RCU readlock-holding * accesses to continue.  On the other hand, no memory allocation is required. */void assoc_array_destroy(struct assoc_array *array,			 const struct assoc_array_ops *ops){	assoc_array_destroy_subtree(array->root, ops);	array->root = NULL;} /* * Handle insertion into an empty tree. */static bool assoc_array_insert_in_empty_tree(struct assoc_array_edit *edit){	struct assoc_array_node *new_n0; 	pr_devel("-->%s()\n", __func__); 	new_n0 = kzalloc(sizeof(struct assoc_array_node), GFP_KERNEL);	if (!new_n0)		return false; 	edit->new_meta[0] = assoc_array_node_to_ptr(new_n0);	edit->leaf_p = &new_n0->slots[0];	edit->adjust_count_on = new_n0;	edit->set[0].ptr = &edit->array->root;	edit->set[0].to = assoc_array_node_to_ptr(new_n0); 	pr_devel("<--%s() = ok [no root]\n", __func__);	return true;} /* * Handle insertion into a terminal node. */static bool assoc_array_insert_into_terminal_node(struct assoc_array_edit *edit,						  const struct assoc_array_ops *ops,						  const void *index_key,						  struct assoc_array_walk_result *result){	struct assoc_array_shortcut *shortcut, *new_s0;	struct assoc_array_node *node, *new_n0, *new_n1, *side;	struct assoc_array_ptr *ptr;	unsigned long dissimilarity, base_seg, blank;	size_t keylen;	bool have_meta;

Lines 1–500 of 1746.