Linux -IDR

场景

  在某些场景我们需要将一个指针映射到一个数字,然后使用这个数字进行相关业务的执行,这就是IDR。

  方案

   IDR的实现方案基于XArray数据结构,但同时使用radix_tree进行了一定封装。

数据结构

#define radix_tree_root		xarray
#define radix_tree_node		xa_node

struct idr {
	struct radix_tree_root	idr_rt;
    //IDR 绑定数值的起始值
	unsigned int		idr_base;
	unsigned int		idr_next;
};


/**
 * struct radix_tree_iter - radix tree iterator state
 *
 * @index:	index of current slot
 * @next_index:	one beyond the last index for this chunk
 * @tags:	bit-mask for tag-iterating
 * @node:	node that contains current slot
 *
 * This radix tree iterator works in terms of "chunks" of slots.  A chunk is a
 * subinterval of slots contained within one radix tree leaf node.  It is
 * described by a pointer to its first slot and a struct radix_tree_iter
 * which holds the chunk's position in the tree and its size.  For tagged
 * iteration radix_tree_iter also holds the slots' bit-mask for one chosen
 * radix tree tag.
 */
//迭代器
struct radix_tree_iter {
	unsigned long	index;
	unsigned long	next_index;
	unsigned long	tags;
	struct radix_tree_node *node;
};

代码逻辑

初始化

define RADIX_TREE_INIT(name, mask)	XARRAY_INIT(name, mask)

#define IDR_INIT_BASE(name, base) {					\
	.idr_rt = RADIX_TREE_INIT(name, IDR_RT_MARKER),			\
	.idr_base = (base),						\
	.idr_next = 0,							\
}

/**
 * radix_tree_iter_init - initialize radix tree iterator
 *
 * @iter:	pointer to iterator state
 * @start:	iteration starting index
 * Returns:	NULL
 */
static __always_inline void __rcu **
radix_tree_iter_init(struct radix_tree_iter *iter, unsigned long start)
{
	/*
	 * Leave iter->tags uninitialized. radix_tree_next_chunk() will fill it
	 * in the case of a successful tagged chunk lookup.  If the lookup was
	 * unsuccessful or non-tagged then nobody cares about ->tags.
	 *
	 * Set index to zero to bypass next_index overflow protection.
	 * See the comment in radix_tree_next_chunk() for details.
	 */
	iter->index = 0;
	iter->next_index = start;
	return NULL;
}

分配ID

ID的分配通过在树中寻找到一个有效位置,然后将指针替换进对应的slot,同时修改对应位置上的属性

/**
 * idr_alloc_u32() - Allocate an ID.
 * @idr: IDR handle.
 * @ptr: Pointer to be associated with the new ID.
 * @nextid: Pointer to an ID.
 * @max: The maximum ID to allocate (inclusive).
 * @gfp: Memory allocation flags.
 *
 * Allocates an unused ID in the range specified by @nextid and @max.
 * Note that @max is inclusive whereas the @end parameter to idr_alloc()
 * is exclusive.  The new ID is assigned to @nextid before the pointer
 * is inserted into the IDR, so if @nextid points into the object pointed
 * to by @ptr, a concurrent lookup will not find an uninitialised ID.
 *
 * The caller should provide their own locking to ensure that two
 * concurrent modifications to the IDR are not possible.  Read-only
 * accesses to the IDR may be done under the RCU read lock or may
 * exclude simultaneous writers.
 *
 * Return: 0 if an ID was allocated, -ENOMEM if memory allocation failed,
 * or -ENOSPC if no free IDs could be found.  If an error occurred,
 * @nextid is unchanged.
 */
int idr_alloc_u32(struct idr *idr, void *ptr, u32 *nextid,
			unsigned long max, gfp_t gfp)
{
	struct radix_tree_iter iter;
	void __rcu **slot;
    //获取idr的基数,以及收索的起始索引
	unsigned int base = idr->idr_base;
	unsigned int id = *nextid;

	if (WARN_ON_ONCE(!(idr->idr_rt.xa_flags & ROOT_IS_IDR)))
		idr->idr_rt.xa_flags |= IDR_RT_MARKER;
    //通过基数调整起始搜索索引并初始化迭代器
	id = (id < base) ? 0 : id - base;
	radix_tree_iter_init(&iter, id);
    //从idr中找到一个满足条件的空slot,若失败返回错误码
	slot = idr_get_free(&idr->idr_rt, &iter, gfp, max - base);
	if (IS_ERR(slot))
		return PTR_ERR(slot);
     //将找到的符合的位置加上基数转换为调用着看到的值
	*nextid = iter.index + base;
	/* there is a memory barrier inside radix_tree_iter_replace() */
    //将指针放置到对应的slot上并修改属性
	radix_tree_iter_replace(&idr->idr_rt, &iter, slot, ptr);
	radix_tree_iter_tag_clear(&idr->idr_rt, &iter, IDR_FREE);

	return 0;
}
EXPORT_SYMBOL_GPL(idr_alloc_u32);

查找空闲位置

查找空闲位置的时候若当前idr的覆盖范围不足以需要查找的范围先通过radix_tree_extend,然后再从根部往下寻找

void __rcu **idr_get_free(struct radix_tree_root *root,
			      struct radix_tree_iter *iter, gfp_t gfp,
			      unsigned long max)
{
	struct radix_tree_node *node = NULL, *child;
	void __rcu **slot = (void __rcu **)&root->xa_head;
	unsigned long maxindex, start = iter->next_index;
	unsigned int shift, offset = 0;

 grow:
    //获取树的根部指针,最大索引以及shift信息
	shift = radix_tree_load_root(root, &child, &maxindex);
    //若树没有设置free属性,则需要调整起始搜索位置
	if (!radix_tree_tagged(root, IDR_FREE))
		start = max(start, maxindex + 1);
    //若调整后的位置大于最大值则没有符合条件的位置
	if (start > max)
		return ERR_PTR(-ENOSPC);
    
	if (start > maxindex) {
 //当前起始搜索位置以及超过树的最大覆盖范围则需要扩展树
		int error = radix_tree_extend(root, gfp, start, shift);
		if (error < 0)
			return ERR_PTR(error);
        //设置搜索的起始条件,树的最大shift以及根节点指针
		shift = error;
		child = rcu_dereference_raw(root->xa_head);
	}
	if (start == 0 && shift == 0)
		shift = RADIX_TREE_MAP_SHIFT;
   //从根部往下搜索
	while (shift) {
		shift -= RADIX_TREE_MAP_SHIFT;
        //若child为空则意味着需要建立一个新的节点
		if (child == NULL) {
			/* Have to add a child node.  */
			child = radix_tree_node_alloc(gfp, node, root, shift,
							offset, 0, 0);
			if (!child)
				return ERR_PTR(-ENOMEM);
             //设置新的节点的属性。并将其放置在对应slot中,
			all_tag_set(child, IDR_FREE);
			rcu_assign_pointer(*slot, node_to_entry(child));
			//若这是一个有效节点,则由于上面添加至slot中其有效个数加1
            if (node)
				node->count++;
		} else if (!radix_tree_is_internal_node(child))
			break;
        //将child转换为node结构,并下沉获取新的偏移量
		node = entry_to_node(child);
		offset = radix_tree_descend(node, &child, start);
        //若当前node下对应偏移量位置不空,则需要向前搜索一个空的slot
		if (!tag_get(node, IDR_FREE, offset)) {
        //向前搜索一个空的slot
			offset = radix_tree_find_next_bit(node, IDR_FREE,
							offset + 1);
            //依据最新的offset获取最新的搜索位置
			start = next_index(start, node, offset);
            //查看是否越界
			if (start > max || start == 0)
				return ERR_PTR(-ENOSPC);
           //若偏移量已经到达slot的最大值。那么需要从父节点的下一个位置开始继续寻找
			while (offset == RADIX_TREE_MAP_SIZE) {
				offset = node->offset + 1;
				node = node->parent;
                /若又到根部了再次扩展树
				if (!node)
					goto grow;
                //同步调整shif用于下次循环
				shift = node->shift;
			}
            //获取对应slot位置上的值
			child = rcu_dereference_raw(node->slots[offset]);
		}
        //获取slot的位置
		slot = &node->slots[offset];
	}
    //修改迭代器的索引,下一个搜索位置和缓存节点
	iter->index = start;
	if (node)
		iter->next_index = 1 + min(max, (start | node_maxindex(node)));
	else
		iter->next_index = 1;
	iter->node = node;
    //更新tag属性
	set_iter_tags(iter, node, offset, IDR_FREE);

	return slot;
}

扩展树

树的扩展通过替换树的根节点来增加树高度

/*
 *	Extend a radix tree so it can store key @index.
 */
static int radix_tree_extend(struct radix_tree_root *root, gfp_t gfp,
				unsigned long index, unsigned int shift)
{
	void *entry;
	unsigned int maxshift;
	int tag;

	/* Figure out what the shift should be.  */
    //计算出需要覆盖索引的话树需要的最小偏移量
	maxshift = shift;
	while (index > shift_maxindex(maxshift))
		maxshift += RADIX_TREE_MAP_SHIFT;
    //获取树的根指针。
	entry = rcu_dereference_raw(root->xa_head);
	if (!entry && (!is_idr(root) || root_tag_get(root, IDR_FREE)))
		goto out;

	do {
        //创建一个节点并初始化
		struct radix_tree_node *node = radix_tree_node_alloc(gfp, NULL,
							root, shift, 0, 1, 0);
        //创建失败则返回
		if (!node)
			return -ENOMEM;
        
		if (is_idr(root)) {
           //若是idr类型,则设置node的free属性
			all_tag_set(node, IDR_FREE);
            //若树的free属性没有设置,则node做为新的根后第一个slot的free属性要清楚,同时树的free属性要设置
			if (!root_tag_get(root, IDR_FREE)) {
				tag_clear(node, IDR_FREE, 0);
				root_tag_set(root, IDR_FREE);
			}
		} else {
			/* Propagate the aggregated tag info to the new child */
            //将树的属性搬到node中去
			for (tag = 0; tag < RADIX_TREE_MAX_TAGS; tag++) {
				if (root_tag_get(root, tag))
					tag_set(node, tag, 0);
			}
		}

		BUG_ON(shift > BITS_PER_LONG);
		if (radix_tree_is_internal_node(entry)) {
          //若entry是一个内部节点那么其父节点需要修改为nide
			entry_to_node(entry)->parent = node;
		} else if (xa_is_value(entry)) {
			/* Moving a value entry root->xa_head to a node */
           //否则entry为值的情况下,node的nr_values设置为1
			node->nr_values = 1;
		}
		/*
		 * entry was already in the radix tree, so we do not need
		 * rcu_assign_pointer here
		 */
         //将就的头指针赋给node的slot【0】,并将创建的node转坏为entry赋给树的根指针
		node->slots[0] = (void __rcu *)entry;
		entry = node_to_entry(node);
		rcu_assign_pointer(root->xa_head, entry);
		shift += RADIX_TREE_MAP_SHIFT;
	} while (shift <= maxshift);
out:
	return maxshift + RADIX_TREE_MAP_SHIFT;
}

树的下沉

树的下沉通过对索引的右移实现获取在节点中偏移和值

static unsigned int radix_tree_descend(const struct radix_tree_node *parent,
			struct radix_tree_node **nodep, unsigned long index)
{
    //通过对索引右移后与RADIX_TREE_MAP_MASK获得在当前节点的偏移
	unsigned int offset = (index >> parent->shift) & RADIX_TREE_MAP_MASK;
    //获取在节点中对应偏移的值
	void __rcu **entry = rcu_dereference_raw(parent->slots[offset]);

	*nodep = (void *)entry;
	return offset;
}

查询下一个有效属性

属性按位存储在数组中,需要从指定位置开始往后寻找到第一个非0值即可

/**
 * radix_tree_find_next_bit - find the next set bit in a memory region
 *
 * @node: where to begin the search
 * @tag: the tag index
 * @offset: the bitnumber to start searching at
 *
 * Unrollable variant of find_next_bit() for constant size arrays.
 * Tail bits starting from size to roundup(size, BITS_PER_LONG) must be zero.
 * Returns next bit offset, or size if nothing found.
 */
static __always_inline unsigned long
radix_tree_find_next_bit(struct radix_tree_node *node, unsigned int tag,
			 unsigned long offset)
{
    //获取属性的数组起始位置
	const unsigned long *addr = node->tags[tag];
    //若偏移未大于最大有效长度
	if (offset < RADIX_TREE_MAP_SIZE) {
		unsigned long tmp;
        //按照unsigned long bit 长度来循环处理
		addr += offset / BITS_PER_LONG;
		tmp = *addr >> (offset % BITS_PER_LONG);
        //若tmp不为0则用ffs按位寻找并补足offset返回
		if (tmp)
			return __ffs(tmp) + offset;
        //从下一个unsigned long  对象开会一个个找
		offset = (offset + BITS_PER_LONG) & ~(BITS_PER_LONG - 1);
		while (offset < RADIX_TREE_MAP_SIZE) {
			tmp = *++addr;
			if (tmp)
				return __ffs(tmp) + offset;
			offset += BITS_PER_LONG;
		}
	}
	return RADIX_TREE_MAP_SIZE;
}

替换

替换仅需将新值放进对应的slot中并同步好node的属性

/**
 * __radix_tree_replace		- replace item in a slot
 * @root:		radix tree root
 * @node:		pointer to tree node
 * @slot:		pointer to slot in @node
 * @item:		new item to store in the slot.
 *
 * For use with __radix_tree_lookup().  Caller must hold tree write locked
 * across slot lookup and replacement.
 */
void __radix_tree_replace(struct radix_tree_root *root,
			  struct radix_tree_node *node,
			  void __rcu **slot, void *item)
{
   //获取slot中存储的值。并统计更新后值和数量的变化
	void *old = rcu_dereference_raw(*slot);
	int values = !!xa_is_value(item) - !!xa_is_value(old);
	int count = calculate_count(root, node, slot, item, old);

	/*
	 * This function supports replacing value entries and
	 * deleting entries, but that needs accounting against the
	 * node unless the slot is root->xa_head.
	 */
	WARN_ON_ONCE(!node && (slot != (void __rcu **)&root->xa_head) &&
			(count || values));
    //将相关信息更新进node 和slot中
	replace_slot(slot, item, node, count, values);

	if (!node)
		return;
    //查看是否需要删除该节点
	delete_node(root, node);
}

/**
 * radix_tree_iter_replace - replace item in a slot
 * @root:	radix tree root
 * @iter:	iterator state
 * @slot:	pointer to slot
 * @item:	new item to store in the slot.
 *
 * For use with radix_tree_for_each_slot().
 * Caller must hold tree write locked.
 */
void radix_tree_iter_replace(struct radix_tree_root *root,
				const struct radix_tree_iter *iter,
				void __rcu **slot, void *item)
{
	__radix_tree_replace(root, iter->node, slot, item);
}

删除节点

删除节点为从起始点开始往上删除无用的节点,若删除到根节点则尝试收缩树

static bool delete_node(struct radix_tree_root *root,
			struct radix_tree_node *node)
{
	bool deleted = false;

	do {
		struct radix_tree_node *parent;
         
		if (node->count) {
        //若节点中还有对象,并且该节点还是根节点则尝试收缩树
			if (node_to_entry(node) ==
					rcu_dereference_raw(root->xa_head))
				deleted |= radix_tree_shrink(root);
			return deleted;
		}
        //到此则意味着节点中没有对象,准备删除节点。先获取父节点
		parent = node->parent;
		if (parent) {
            //父节点有效,则将节点从父节点的slot数组中移除并修改父节点中对象个数
			parent->slots[node->offset] = NULL;
			parent->count--;
		} else {
			/*
			 * Shouldn't the tags already have all been cleared
			 * by the caller?
			 */
             //到根节点了,若为非idr类型则清理属性
			if (!is_idr(root))
				root_tag_clear_all(root);
            //将树的头指针设置为空
			root->xa_head = NULL;
		}

		WARN_ON_ONCE(!list_empty(&node->private_list));
         //删除节点,并将当前节点设置为父节点
		radix_tree_node_free(node);
		deleted = true;

		node = parent;
	} while (node);

	return deleted;
}

收缩

收缩从根节点指针开始往下查看是由slot【0】一个对象的节点,若满足删除该节点并将slot【0】替换为根节点,否则返回

/**
 *	radix_tree_shrink    -    shrink radix tree to minimum height
 *	@root:		radix tree root
 */
static inline bool radix_tree_shrink(struct radix_tree_root *root)
{
	bool shrunk = false;

	for (;;) {
        //获取根节点指针指向的对象
		struct radix_tree_node *node = rcu_dereference_raw(root->xa_head);
		struct radix_tree_node *child;
        //非内部节点则跳出返回
		if (!radix_tree_is_internal_node(node))
			break;
         //为内部节点转换为节点
		node = entry_to_node(node);

		/*
		 * The candidate node has more than one child, or its child
		 * is not at the leftmost slot, we cannot shrink.
		 */
        //检查节点slot中是否只有一个对象并且还是在slot【0】的不是则跳出
		if (node->count != 1)
			break;
		child = rcu_dereference_raw(node->slots[0]);
		if (!child)
			break;

		/*
		 * For an IDR, we must not shrink entry 0 into the root in
		 * case somebody calls idr_replace() with a pointer that
		 * appears to be an internal entry
		 */
        //idr类型需要至少保留一个节点
		if (!node->shift && is_idr(root))
			break;
        //检查child是否为节点,若是与父节点解除关系
		if (radix_tree_is_internal_node(child))
			entry_to_node(child)->parent = NULL;

		/*
		 * We don't need rcu_assign_pointer(), since we are simply
		 * moving the node from one part of the tree to another: if it
		 * was safe to dereference the old pointer to it
		 * (node->slots[0]), it will be safe to dereference the new
		 * one (root->xa_head) as far as dependent read barriers go.
		 */
         //将根节点指针指向child
		root->xa_head = (void __rcu *)child;
        //  当为idr类型时,若node的free属性没有设置则清除树的free属性
		if (is_idr(root) && !tag_get(node, IDR_FREE, 0))
			root_tag_clear(root, IDR_FREE);

		/*
		 * We have a dilemma here. The node's slot[0] must not be
		 * NULLed in case there are concurrent lookups expecting to
		 * find the item. However if this was a bottom-level node,
		 * then it may be subject to the slot pointer being visible
		 * to callers dereferencing it. If item corresponding to
		 * slot[0] is subsequently deleted, these callers would expect
		 * their slot to become empty sooner or later.
		 *
		 * For example, lockless pagecache will look up a slot, deref
		 * the page pointer, and if the page has 0 refcount it means it
		 * was concurrently deleted from pagecache so try the deref
		 * again. Fortunately there is already a requirement for logic
		 * to retry the entire slot lookup -- the indirect pointer
		 * problem (replacing direct root node with an indirect pointer
		 * also results in a stale slot). So tag the slot as indirect
		 * to force callers to retry.
		 */
         //修改node的属性。当child为非节点类型即值的时候。将slot【0】设置为RADIX_TREE_RETRY以防止读取到过期的值
		node->count = 0;
		if (!radix_tree_is_internal_node(child)) {
			node->slots[0] = (void __rcu *)RADIX_TREE_RETRY;
		}

		WARN_ON_ONCE(!list_empty(&node->private_list));
		radix_tree_node_free(node);
		shrunk = true;
	}

	return shrunk;
}

属性修改

属性的修改从节点开始,往根部修改,直到找到一个不满足条件的节点为止

static void node_tag_clear(struct radix_tree_root *root,
				struct radix_tree_node *node,
				unsigned int tag, unsigned int offset)
{
	//节点为真则修改
    while (node) {
         //当前节点指定属性数组中偏移位置没有设置则返回
		if (!tag_get(node, tag, offset))
			return;
        //清楚指定属性数组偏移位置的值
		tag_clear(node, tag, offset);
        //若该节点指定属性只要有一个位置不为0则返回
		if (any_tag_set(node, tag))
			return;
        //调整位置指向父节点
		offset = node->offset;
		node = node->parent;
	}
    
	/* clear the root's tag bit */
    //  到此树上节点都清理干净了,若树设置了此属性则清理掉该属性
	if (root_tag_get(root, tag))
		root_tag_clear(root, tag);
}

/**
  * radix_tree_iter_tag_clear - clear a tag on the current iterator entry
  * @root: radix tree root
  * @iter: iterator state
  * @tag: tag to clear
  */
void radix_tree_iter_tag_clear(struct radix_tree_root *root,
			const struct radix_tree_iter *iter, unsigned int tag)
{
	node_tag_clear(root, iter->node, tag, iter_offset(iter));
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值