文章目录
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 测试环境
本文基于 linux-4.14.132
内核代码分析。
3. 实现
3.1 概念
基数树(radix tree)
,在 Linux 中是将一个 整数键值
,映射到 数据指针
的字典树。
3.2 数据结构
Linux 基数树用数据结构 struct radix_tree_root
描述,定义在文件 linux/inclue/radix-tree.h
中:
struct radix_tree_root {
/* 调用 kmem_cache_alloc() 分配 radix_tree_node 节点时使用的掩码 */
gfp_t gfp_mask;
/* 存储 第1个数据 item 指针 或 根节点 radix_tree_node 指针 */
struct radix_tree_node __rcu *rnode;
};
在基数树
中,当数据 item 指针大于1个情形下
, 要额外分配 struct radix_tree_node
来存储它们,来看一下 struct radix_tree_node
:
/*
* @count is the count of every non-NULL element in the ->slots array
* whether that is an exceptional entry, a retry entry, a user pointer,
* a sibling entry or a pointer to the next level of the tree.
* @exceptional is the count of every element in ->slots which is
* either radix_tree_exceptional_entry() or is a sibling entry for an
* exceptional entry.
*/
struct radix_tree_node {
unsigned char shift; /* Bits remaining in each slot(当前节点slots[]存放item的索引移位:即 (index>>RADIX_TREE_MAP_SHIFT) & RADIX_TREE_MAP_MASK) */
unsigned char offset; /* Slot offset in parent(当前节点指针存放在父节点@parent slots[]中的偏移位置) */
unsigned char count; /* Total entry count (节点slots[]中已存放item数目)*/
unsigned char exceptional; /* Exceptional entry count */
struct radix_tree_node *parent; /* Used when ascending tree (父节点,根节点为 NULL) */
struct radix_tree_root *root; /* The tree we belong to */
union {
struct list_head private_list; /* For tree user */
struct rcu_head rcu_head; /* Used when freeing node */
};
/*
* 存放 item 或 子节点 radix_tree_node 指针:
* . 叶节点存放 item
* . 非叶节点存放下一级子节点 radix_tree_node 指针
*/
void __rcu *slots[RADIX_TREE_MAP_SIZE];
/*
* Linux 4.14 内核每个 slot 只支持 {0,1,2} 3 个 tag 值。
* 每个 slot 是否设置了对应的 tag ,通过 bit map 标记:1 表示设置了 tag 。
* 每 slot 、每 tag 一个 bit ,所以每 slot 占用 1x3 = 3 个 bit 。
*/
unsigned long tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS];
};
基树通过索引 key 值,逐层映射,如下图:
上图是一个3层的基数树
,在 RADIX_TREE_MAP_SHIFT
配置为 6
的情形下,映射的步骤如下:
1. key[17:12] 索引根节点的slots[],从根节点的slots[key[17:12]]处找到第二层的对应节点;
2. key[11:6] 索引步骤1中找到节点的slots[],找到第三层的对应叶节点;
3. key[5:0] 索引步骤2中找到的叶节点的slots[],定位到要操作的目标item。
上述 根节点
指 struct radix_tree_node::rnode
指向的节点 struct radix_tree_node
,叶节点
是指没有子节点的节点。树不一定是3层,根据现存数据的变化,树的高度会变高或变矮。树的节点也不是一次性建立的,也是根据现存数据情况出现增减。一种特殊情形是:当基树只有一个数据项的时候
,直接用 struct radix_tree_node::rnode
存放。
3.3 对基数树的操作
3.3.1 初始化
基树的初始化可以通过定义时初始化
或运行时初始化
两种,下面分别进行演示:
/* 定义时初始化 */
RADIX_TREE(my_radix_tree, GFP_KERNEL);
struct radix_tree_root my_radix_tree;
/* 运行时初始化 */
INIT_RADIX_TREE(&my_radix_tree, GFP_KERNEL);
3.3.2 插入
int __radix_tree_insert(struct radix_tree_root *, unsigned long index,
unsigned order, void *);
static inline int radix_tree_insert(struct radix_tree_root *root,
unsigned long index, void *entry)
{
return __radix_tree_insert(root, index, 0, entry);
}
root
参数传递基树对象指针 &my_radix_tree
,index
参数为 item 的索引,entry
参数为 item 指针。看下插入操作的代码实现:
int __radix_tree_insert(struct radix_tree_root *root, unsigned long index,
unsigned order, void *item)
{
...
error = __radix_tree_create(root, index, order, &node, &slot);
if (error)
return error;
/* 插入 @item 到节点 @node 的 @slot */
error = insert_entries(node, slot, item, order, false);
if (error < 0)
return error;
...
return 0;
}
int __radix_tree_create(struct radix_tree_root *root, unsigned long index,
unsigned order, struct radix_tree_node **nodep,
void __rcu ***slotp)
{
struct radix_tree_node *node = NULL, *child;
void __rcu **slot = (void __rcu **)&root->rnode;
unsigned long maxindex;
unsigned int shift, offset = 0;
unsigned long max = index | ((1UL << order) - 1);
...
/*
* 加载【根节点】信息:
* child: 根节点指针;
* maxindex: 根节点最大索引值;
* shift: 【 根节点索引移位 + RADIX_TREE_MAP_SHIFT:从该值计算根节点索引区间范围 】。
*/
shift = radix_tree_load_root(root, &child, &maxindex);
...
if (max > maxindex) { /* 请求插入 item 的 index ,大于基树当前索引区间,需要扩展基树 */
/* 按 {max,shift} 扩展基树,以能容纳新 item 的 index */
int error = radix_tree_extend(root, gfp, max, shift);
if (error < 0)
return error;
shift = error;
child = rcu_dereference_raw(root->rnode); /* 扩展后基树的新根节点 */
}
/*
* 寻找插入的目标位置: {@node, @slot}.
*
* 循环直到基树叶节点为止:
* 所有上层节点的 slots 不用来存放 item,而是存放更下一层
* radix_tree_node 的指针,不存放 item ;
* 只有叶节点才能插入 item .
*/
while (shift > order) {
shift -= RADIX_TREE_MAP_SHIFT;
if (child == NULL) { /* 子节点 radix_tree_node 尚未创建,创建它 */
/* Have to add a child node. */
child = radix_tree_node_alloc(gfp, node, root, shift,
offset, 0, 0);
if (!child)
return -ENOMEM;
/* 新分配的子节点 @child 插入上层父节点 @node offset 位置的 slot */
rcu_assign_pointer(*slot, node_to_entry(child));
if (node) /* 父节点 @node 不为空 */
node->count++; /* 添加子节点 @child 到了 上层父节点 @node ,父节点 slot 计数加1 */
} else if (!radix_tree_is_internal_node(child)) // ???
break;
/* Go a level down */
node = entry_to_node(child);
/*
* 计算 @index 在 @node 中的插入位置:
* offset: 插入位置在 @node 中 slot 的偏移位置;
* child: 插入位置 offset 处 slot 当前值:
* 如果 @node 是叶节点,可能返回已插入 item 地址 或 NULL (空闲情形);
* 如果 @node 是非叶节点,可能返回已创建的 radix_tree_node 指针 或 NULL (尚未分配)。
*/
offset = radix_tree_descend(node, &child, index);
slot = &node->slots[offset];
}
if (nodep)
*nodep = node; /* 返回要插入的 node */
if (slotp)
*slotp = slot; /* 返回要插入的 node 中 slot */
return 0;
}
static unsigned radix_tree_load_root(const struct radix_tree_root *root,
struct radix_tree_node **nodep, unsigned long *maxindex)
{
struct radix_tree_node *node = rcu_dereference_raw(root->rnode);
/*
* 返回【根节点】指针:
* . 没有 item 时返回 NULL
* . 只有 1个 item 时返回该 item
* . 其它情形返回【根节点】指针
*/
*nodep = node;
if (likely(radix_tree_is_internal_node(node))) {
node = entry_to_node(node);
*maxindex = node_maxindex(node); /* 返回【根节点】的最大索引值 */
return node->shift + RADIX_TREE_MAP_SHIFT; /* 返回: 【根节点】的索引移位 + RADIX_TREE_MAP_SHIFT */
}
*maxindex = 0; /* 当前只有 @root ,它只能存一个 item ,所以最大索引为0 */
return 0; /* 同理 shift 返回 0 */
}
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)) /* 可能需要将基树增高多级,才能容纳 @index */
maxshift += RADIX_TREE_MAP_SHIFT;
/*
* 保存【当前根节点】:
* 增高基树后,新增的、最顶层的 radix_tree_node 会成为新的【根节点】,
* 而【当前根节点及其子树】会成为比其高一层的、新增节点的子节点(子树)。
*/
entry = rcu_dereference_raw(root->rnode);
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 (radix_tree_is_internal_node(entry)) {
/* 旧的根节点在基树中下移一层:设置新节点 @node 为 旧根节点 @entry 的父节点 */
entry_to_node(entry)->parent = node;
} else if (radix_tree_exceptional_entry(entry)) {
...
}
/*
* entry was already in the radix tree, so we do not need
* rcu_assign_pointer here
*/
node->slots[0] = (void __rcu *)entry; /* 【新的根节点】的第1个slot指向【旧的根节点】 */
entry = node_to_entry(node);
rcu_assign_pointer(root->rnode, entry); /* 设置新 @node 成为基树根节点 */
shift += RADIX_TREE_MAP_SHIFT; /* 计算更高一层节点的索引移位 */
} while (shift <= maxshift); /* 是否还要增加一层,索引范围才能容纳 @index ? */
out:
return maxshift + RADIX_TREE_MAP_SHIFT; /* 返回比新基数根节点更高一层节点的 shift */
}
static struct radix_tree_node *
radix_tree_node_alloc(gfp_t gfp_mask, struct radix_tree_node *parent,
struct radix_tree_root *root,
unsigned int shift, unsigned int offset,
unsigned int count, unsigned int exceptional)
{
struct radix_tree_node *ret = NULL;
...
ret = kmem_cache_alloc(radix_tree_node_cachep, gfp_mask);
out:
BUG_ON(radix_tree_is_internal_node(ret));
if (ret) {
ret->shift = shift;
ret->offset = offset;
ret->count = count;
ret->exceptional = exceptional;
ret->parent = parent; /* 设置父节点 */
ret->root = root;
}
return ret;
}
static unsigned int radix_tree_descend(const struct radix_tree_node *parent,
struct radix_tree_node **nodep, unsigned long index)
{
unsigned int offset = (index >> parent->shift) & RADIX_TREE_MAP_MASK; /* 计算 node / item 在节点内 slots[] 内偏移 */
void __rcu **entry = rcu_dereference_raw(parent->slots[offset]);
...
*nodep = (void *)entry;
return offset;
}
小结一下基数树的插入过程:
如章节 3.2 末尾处描述,通过将 index 按每位域 RADIX_TREE_MAP_SHIFT 位进行拆分,通过这些
位域,在基树中逐级定位每层级的 struct radix_tree_node ,直到找到一个叶节点为止,然后用
index 的最低 RADIX_TREE_MAP_SHIFT 位来索引该叶节点的 slots[] ,最后将 item 存入到目标
slot 。
在插入过程中:如果某层级所有节点的 slots[] 存满,则会引起基数树增高一层;如果某个要插入
的目标节点不存在,则会创建新的一个或多个节点。
正如前面所述,基数树只有叶节点的 slots[] 才存储 item ,其它层级节点的 slots[] 存储下一
层级节点的指针。
3.3.3 查找
有了插入过程的铺垫,查找过程就不难理解了。这里只描述 radix_tree_lookup()
,其它接口读者可自行分析。看一下 radix_tree_lookup()
的实现:
void *radix_tree_lookup(const struct radix_tree_root *root, unsigned long index)
{
return __radix_tree_lookup(root, index, NULL, NULL);
}
void *__radix_tree_lookup(const struct radix_tree_root *root,
unsigned long index, struct radix_tree_node **nodep,
void __rcu ***slotp)
{
struct radix_tree_node *node, *parent;
unsigned long maxindex;
void __rcu **slot;
restart:
parent = NULL;
slot = (void __rcu **)&root->rnode;
radix_tree_load_root(root, &node, &maxindex);
if (index > maxindex) /* 索引指向的 item 不存在 */
return NULL;
while (radix_tree_is_internal_node(node)) {
unsigned offset;
if (node == RADIX_TREE_RETRY)
goto restart;
parent = entry_to_node(node);
offset = radix_tree_descend(parent, &node, index); /* 基树层级下沉:逐级向下查找 */
slot = parent->slots + offset;
}
if (nodep)
*nodep = parent;
if (slotp)
*slotp = slot;
return node;
}
/* radix_tree_descend() 的细节见 3.3.2 插入 */
像插入操作一样,查找也是通过将 index
按每位域 RADIX_TREE_MAP_SHIFT
位进行拆分,通过这些位域,在基树中逐级定位每层级的 struct radix_tree_node
,直到找到一个叶节点为止,然后用 index
的最低 RADIX_TREE_MAP_SHIFT
位来索引该叶节点的 slots[]
,最后找到目标 item 。
3.3.4 删除
删除操作是以查找操作为基础,同样有多个接口,这里只分析 radix_tree_delete()
的实现,对其它接口感兴趣的读者,可自行阅读代码分析。看一下 radix_tree_delete()
的具体实现:
void *radix_tree_delete(struct radix_tree_root *root, unsigned long index)
{
return radix_tree_delete_item(root, index, NULL);
}
void *radix_tree_delete_item(struct radix_tree_root *root,
unsigned long index, void *item)
{
struct radix_tree_node *node = NULL;
void __rcu **slot = NULL;
void *entry;
entry = __radix_tree_lookup(root, index, &node, &slot);
if (!slot) /* @index 指向的 item 不存在,无法删除 */
return NULL;
...
if (item && entry != item) /* 删除指定 item: @index 和 @item 指代不一致 */
return NULL;
__radix_tree_delete(root, node, slot); /* 删除 item {node,slot} */
return entry;
}
static bool __radix_tree_delete(struct radix_tree_root *root,
struct radix_tree_node *node, void __rcu **slot)
{
void *old = rcu_dereference_raw(*slot);
int exceptional = radix_tree_exceptional_entry(old) ? -1 : 0;
unsigned offset = get_slot_offset(node, slot);
int tag;
if (is_idr(root))
node_tag_set(root, node, IDR_FREE, offset);
else
for (tag = 0; tag < RADIX_TREE_MAX_TAGS; tag++) /* 删除的同时清空 tag */
node_tag_clear(root, node, tag, offset);
/*
* . @node slot 计数减1: node->count += -1
* . 清空 @slot: *slot = NULL
*/
replace_slot(slot, NULL, node, -1, exceptional);
return node && delete_node(root, node, NULL, NULL); /* 删除 item 可能引发节点删除 */
}
static bool delete_node(struct radix_tree_root *root,
struct radix_tree_node *node,
radix_tree_update_node_t update_node, void *private)
{
bool deleted = false;
do {
struct radix_tree_node *parent;
if (node->count) { /* 节点 slot 尚不为空,但可能存在只有一个子节点的情形,这时候压缩基树 */
if (node_to_entry(node) ==
rcu_dereference_raw(root->rnode)) /* @node 为根节点 */
deleted |= radix_tree_shrink(root, update_node,
private); /* 压缩基树 */
return deleted;
}
/* 节点 @node 当前包含的 slot 数目为0,则该节点可以删除了 */
parent = node->parent;
if (parent) { /* 如果节点 @node 有父节点,删除节点,父节点对应 slot 位置应清空,slot 计数也应减1 */
parent->slots[node->offset] = NULL; /* 父节点对应 slot 位置应清空 */
parent->count--; /* 父节点 slot 计数也应减1 */
} else { /* @node 没有父节点,则其为根节点 */
/*
* Shouldn't the tags already have all been cleared
* by the caller?
*/
if (!is_idr(root))
root_tag_clear_all(root);
root->rnode = NULL; /* 根节点被删除了 */
}
WARN_ON_ONCE(!list_empty(&node->private_list));
radix_tree_node_free(node); /* 释放节点 @node */
deleted = true; /* 标记有节点被删除 */
node = parent; /* 继续,子节点被删除,可能导致父节点的 slot 全空,也即父节点也应被删除 */
} while (node);
return deleted;
}
static inline bool radix_tree_shrink(struct radix_tree_root *root,
radix_tree_update_node_t update_node,
void *private)
{
bool shrunk = false;
/* 层层压缩基树:将那些只有一个最左子节点的根节点删除:它们的索引域为0,没有存在的必要了 */
for (;;) {
struct radix_tree_node *node = rcu_dereference_raw(root->rnode); /* 取当前根节点 */
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, or the child is a multiorder
* entry, we cannot shrink.
*/
if (node->count != 1)
break;
/*
* 根节点只有1个子节点的情形,存在压缩基树的可能性:
* 因为此时根节点没有存在的必要了,只需把 root->rnode 指向其子树根节点,
* 就可以删除当前根节点了。
*/
child = rcu_dereference_raw(node->slots[0]); /* 取根节点的最左边子节点 */
/*
* 虽然根节点只有1个子节点,但不在最左边,意味着索引的根节点域不为0,
* 不能删除根节点,否则子树的节点没法索引到对应的 item 。
*/
if (!child)
break;
if (!radix_tree_is_internal_node(child) && node->shift) // ???
break;
/* 已满足删除根节点的所有条件,将其位于最左边、唯一的子节点的父节点清空: 根节点不存在父节点 */
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->rnode) as far as dependent read barriers go.
*/
root->rnode = (void __rcu *)child; /* 根节点的最左边、唯一的子节点,成为新的根节点 */
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->count = 0; /* 旧的根节点 @node 子节点数清0 */
if (!radix_tree_is_internal_node(child)) { /* 基树只有一层的情形,回归 */
node->slots[0] = (void __rcu *)RADIX_TREE_RETRY;
...
}
...
radix_tree_node_free(node); /* 释放旧的根节点的 radix_tree_node */
shrunk = true; /* 标记压缩过基树 */
}
return shrunk;
}
static inline unsigned long
get_slot_offset(const struct radix_tree_node *parent, void __rcu **slot)
{
return slot - parent->slots;
}
3.3.5 tag 操作
3.3.5.1 设置 tag
void *radix_tree_tag_set(struct radix_tree_root *root,
unsigned long index, unsigned int tag)
{
struct radix_tree_node *node, *parent;
unsigned long maxindex;
radix_tree_load_root(root, &node, &maxindex);
BUG_ON(index > maxindex);
/* tag 设置是一个链式操作,index 关联的各级节点 tag 都会被设置 */
while (radix_tree_is_internal_node(node)) {
unsigned offset;
parent = entry_to_node(node);
offset = radix_tree_descend(parent, &node, index);
BUG_ON(!node);
if (!tag_get(parent, tag, offset))
tag_set(parent, tag, offset);
}
/* set the root's tag bit */
/* 基树对象 tag 设置 (ROOT_TAG_SHIFT) : 复用 gfp_mask 中用不到的 bit 空间 */
if (!root_tag_get(root, tag))
root_tag_set(root, tag);
return node;
}
3.3.5.2 清除 tag
void *radix_tree_tag_clear(struct radix_tree_root *root,
unsigned long index, unsigned int tag)
{
struct radix_tree_node *node, *parent;
unsigned long maxindex;
int uninitialized_var(offset);
radix_tree_load_root(root, &node, &maxindex);
if (index > maxindex) /* @index 指向的 item 不存在 */
return NULL;
parent = NULL;
while (radix_tree_is_internal_node(node)) { /* 按 @index 在基树逐级下层,直到找到一个 item 为止 */
parent = entry_to_node(node);
offset = radix_tree_descend(parent, &node, index); /* node <== 下一级 node 或 目标item */
}
if (node)
node_tag_clear(root, parent, tag, offset); /* 清除 tag */
return node;
}
3.3.5.3 获取 tag 状态
int radix_tree_tag_get(const struct radix_tree_root *root,
unsigned long index, unsigned int tag)
{
struct radix_tree_node *node, *parent;
unsigned long maxindex;
if (!root_tag_get(root, tag))
return 0;
radix_tree_load_root(root, &node, &maxindex);
if (index > maxindex)
return 0;
while (radix_tree_is_internal_node(node)) {
unsigned offset;
parent = entry_to_node(node);
offset = radix_tree_descend(parent, &node, index);
if (!tag_get(parent, tag, offset))
return 0;
if (node == RADIX_TREE_RETRY)
break;
}
return 1;
}
4. 使用范例
我将 Linux 基数树内核代码剥离出来,和测试代码一同放在 此处,这样可以方便我们再用户空间用 VS 或 GDB 进行调试,感兴趣的读者可以自取。
5. Linux 基数树的应用和发展
在 Linux 中,使用基数树来管理中断描述符;在内存管理中,基数树有广泛的应用,如用来快速发现脏页或回写中的页面等。
在较新版的 Linux 内核中,基数树使用 xarray
实现。
6. 参考资料
https://lwn.net/Articles/175432/
https://0xax.gitbooks.io/linux-insides/content/DataStructures/linux-datastructures-2.html