场景
在某些场景我们需要将一个指针映射到一个数字,然后使用这个数字进行相关业务的执行,这就是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));
}
1367

被折叠的 条评论
为什么被折叠?



