基数树标签设置函数radix_tree_tag_set
c
void *radix_tree_tag_set(struct radix_tree_root *root,
unsigned long index, int tag)
{
unsigned int height, shift;
struct radix_tree_node **slot;
height = root->height;
if (index > radix_tree_maxindex(height))
return NULL;
shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
slot = &root->rnode;
while (height > 0) {
int offset;
offset = (index >> shift) & RADIX_TREE_MAP_MASK;
tag_set(*slot, tag, offset);
slot = (struct radix_tree_node **)((*slot)->slots + offset);
BUG_ON(*slot == NULL);
shift -= RADIX_TREE_MAP_SHIFT;
height--;
}
return *slot;
}
函数整体功能
这个函数用于在基数树中为指定索引的数据项设置标签,并沿着路径传播标签信息
代码分段详解
第一段:函数定义和变量声明
c
void *radix_tree_tag_set(struct radix_tree_root *root,
unsigned long index, int tag)
{
unsigned int height, shift;
struct radix_tree_node **slot;
void *: 返回泛型指针,指向被设置标签的数据项root: 基数树的根节点指针index: 要设置标签的索引键tag: 要设置的标签编号(0, 1, 2等)height: 当前树的高度shift: 位偏移量,用于计算路径slot: 指向指针的指针,用于导航树结构
第二段:获取树高度并检查索引范围
c
height = root->height;
if (index > radix_tree_maxindex(height))
return NULL;
height = root->height: 获取树的当前高度index > radix_tree_maxindex(height): 检查索引是否超出当前树能容纳的最大范围radix_tree_maxindex(height): 返回指定高度下能存储的最大索引值return NULL: 如果索引超出范围,立即返回NULL
第三段:初始化位移量和起始位置
c
shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
slot = &root->rnode;
shift = (height - 1) * RADIX_TREE_MAP_SHIFT: 计算初始位移量height - 1: 需要处理的中间层数RADIX_TREE_MAP_SHIFT: 每个段占用的比特数(通常为6)
slot = &root->rnode: 从根节点开始,slot指向根节点指针的地址
第四段:逐层向下遍历的循环
c
while (height > 0) {
height > 0: 循环条件,表示还有中间层需要遍历- 每次循环处理一层,沿着路径设置标签
第五段:声明局部变量
c
int offset;
int offset: 在当前节点中的槽位偏移量- 在循环内部声明,限制作用域
第六段:计算当前层偏移量
c
offset = (index >> shift) & RADIX_TREE_MAP_MASK;
index >> shift: 将索引右移,提取当前层对应的位段& RADIX_TREE_MAP_MASK: 掩码操作,得到0-63的槽位编号- 作用: 确定在当前节点中要走哪个槽位
第七段:设置当前节点的标签
c
tag_set(*slot, tag, offset);
tag_set(*slot, tag, offset): 调用标签设置函数*slot: 解引用得到当前节点指针tag: 要设置的标签编号offset: 在当前节点中要设置标签的槽位- 作用: 标记该路径上的节点,表示这个方向有带标签的数据
第八段:移动到下一层
c
slot = (struct radix_tree_node **)((*slot)->slots + offset);
(*slot)->slots: 当前节点的槽位数组+ offset: 加上计算出的偏移量,得到目标槽位的地址(struct radix_tree_node **): 类型转换为双指针- 作用: 更新slot指向下一层的节点
第九段:检查下一层节点是否存在
c
BUG_ON(*slot == NULL);
BUG_ON(*slot == NULL): 内核调试宏,如果条件为真则触发系统崩溃*slot == NULL: 检查下一层节点是否为空- 作用: 确保路径上的所有节点都存在,如果遇到空节点说明数据结构不一致
第十段:更新位移量和高度
c
shift -= RADIX_TREE_MAP_SHIFT;
height--;
shift -= RADIX_TREE_MAP_SHIFT: 减少位移量,准备处理下一个低位段height--: 高度减1,表示下降了一层- 作用: 为下一轮循环准备参数
第十一段:返回目标数据项
c
}
return *slot;
}
- 循环结束后,
height == 0,到达数据层 return *slot: 返回最终槽位中的数据项指针- 注意: 即使数据项不存在(*slot为NULL),也会返回NULL
标签传播可视化
根节点 设置标签: tag, offset1 ... 中间节点
设置标签: tag, offset2 ... 叶子节点
目标数据项 ... 索引路径 层1: offset1 层2: offset2 到达数据项 标签传播 每层都标记路径 支持基于标签的快速查找
关键设计要点
1. 标签传播机制
- 沿着整个路径设置标签
- 确保基于标签的查找能够正确工作
- 维护标签的完整性
2. 强一致性检查
BUG_ON(*slot == NULL): 确保路径完整- 如果遇到空节点,说明数据结构损坏
- 立即崩溃避免数据不一致
3. 路径导航
- 与查找操作相同的导航逻辑
- 确保准确到达目标位置
基数树标签清除函数radix_tree_tag_clear
c
void *radix_tree_tag_clear(struct radix_tree_root *root,
unsigned long index, int tag)
{
struct radix_tree_path path[RADIX_TREE_MAX_PATH], *pathp = path;
unsigned int height, shift;
void *ret = NULL;
height = root->height;
if (index > radix_tree_maxindex(height))
goto out;
shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
pathp->node = NULL;
pathp->slot = &root->rnode;
while (height > 0) {
int offset;
if (*pathp->slot == NULL)
goto out;
offset = (index >> shift) & RADIX_TREE_MAP_MASK;
pathp[1].offset = offset;
pathp[1].node = *pathp[0].slot;
pathp[1].slot = (struct radix_tree_node **)
(pathp[1].node->slots + offset);
pathp++;
shift -= RADIX_TREE_MAP_SHIFT;
height--;
}
ret = *pathp[0].slot;
if (ret == NULL)
goto out;
do {
int idx;
tag_clear(pathp[0].node, tag, pathp[0].offset);
for (idx = 0; idx < RADIX_TREE_TAG_LONGS; idx++) {
if (pathp[0].node->tags[tag][idx])
goto out;
}
pathp--;
} while (pathp[0].node);
out:
return ret;
}
函数整体功能
这个函数用于清除基数树中指定索引的标签,并沿着路径向上清理不再需要的标签标记
代码分段详解
第一段:函数定义和变量声明
c
void *radix_tree_tag_clear(struct radix_tree_root *root,
unsigned long index, int tag)
{
struct radix_tree_path path[RADIX_TREE_MAX_PATH], *pathp = path;
unsigned int height, shift;
void *ret = NULL;
void *: 返回泛型指针,指向被清除标签的数据项root: 基数树的根节点指针index: 要清除标签的索引键tag: 要清除的标签编号path[RADIX_TREE_MAX_PATH]: 路径记录数组,存储遍历路径信息pathp = path: 指向路径数组的指针,初始指向数组开头height: 当前树的高度shift: 位偏移量,用于计算路径ret = NULL: 初始化返回值为NULL
第二段:获取树高度并检查索引范围
c
height = root->height;
if (index > radix_tree_maxindex(height))
goto out;
height = root->height: 获取树的当前高度index > radix_tree_maxindex(height): 检查索引是否超出范围goto out: 如果索引超出范围,跳转到函数结尾
第三段:初始化位移量和路径记录
c
shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
pathp->node = NULL;
pathp->slot = &root->rnode;
shift = (height - 1) * RADIX_TREE_MAP_SHIFT: 计算初始位移量pathp->node = NULL: 根节点没有父节点,设为NULLpathp->slot = &root->rnode: 记录根节点的槽位指针
第四段:向下遍历路径并记录路径信息
c
while (height > 0) {
int offset;
if (*pathp->slot == NULL)
goto out;
while (height > 0): 循环直到到达叶子层int offset: 声明当前层的偏移量if (*pathp->slot == NULL): 如果当前节点为空,跳转到结尾goto out: 路径中断,无法继续
第五段:计算偏移量并记录路径信息
c
offset = (index >> shift) & RADIX_TREE_MAP_MASK;
pathp[1].offset = offset;
pathp[1].node = *pathp[0].slot;
pathp[1].slot = (struct radix_tree_node **)
(pathp[1].node->slots + offset);
计算偏移量:
c
offset = (index >> shift) & RADIX_TREE_MAP_MASK;
- 提取当前层的路径段
记录路径信息到下一位置:
c
pathp[1].offset = offset;
- 记录当前层在父节点中的偏移量
c
pathp[1].node = *pathp[0].slot;
- 记录当前节点指针
c
pathp[1].slot = (struct radix_tree_node **)
(pathp[1].node->slots + offset);
- 记录下一层槽位的地址
第六段:更新指针和计数器
c
pathp++;
shift -= RADIX_TREE_MAP_SHIFT;
height--;
}
pathp++: 移动路径指针到下一个位置shift -= RADIX_TREE_MAP_SHIFT: 减少位移量height--: 高度减1,下降一层
第七段:获取目标数据项
c
ret = *pathp[0].slot;
if (ret == NULL)
goto out;
ret = *pathp[0].slot: 获取最终的数据项指针if (ret == NULL): 如果数据项不存在goto out: 跳转到结尾返回NULL
第八段:向上遍历并清除标签
c
do {
int idx;
tag_clear(pathp[0].node, tag, pathp[0].offset);
do { ... }: 循环向上遍历路径int idx: 循环索引变量tag_clear(pathp[0].node, tag, pathp[0].offset): 清除当前节点的标签位
第九段:检查节点是否还需要保留标签
c
for (idx = 0; idx < RADIX_TREE_TAG_LONGS; idx++) {
if (pathp[0].node->tags[tag][idx])
goto out;
}
for (idx = 0; idx < RADIX_TREE_TAG_LONGS; idx++): 遍历标签的所有long单元if (pathp[0].node->tags[tag][idx]): 如果发现任何标签位被设置goto out: 跳转到结尾,停止向上清理
第十段:向上移动并继续清理
c
pathp--;
} while (pathp[0].node);
pathp--: 路径指针向上移动一层(指向父节点)} while (pathp[0].node): 循环直到根节点(node为NULL)
第十一段:函数返回
c
out:
return ret;
}
out:: 标签,用于goto跳转return ret: 返回数据项指针
非常好的问题!这涉及到基数树标签系统的核心设计理念。让我详细解释为什么需要这个检查。
标签系统的设计目的
基数树的标签不是为单个槽位服务的,而是为整个子树 服务的。标签的作用是快速定位具有某种特性的所有项目
标签的语义含义
- 如果一个节点设置了某个标签,表示该节点的子树中至少有一个项目具有这个标签
- 标签是从叶子节点向根节点"传播"的
可视化理解
根节点
标签1: 设置 中间节点A
标签1: 设置 中间节点B
标签1: 未设置 叶子节点A
标签1: 设置 叶子节点B
标签1: 设置 叶子节点C
标签1: 未设置 叶子节点D
标签1: 未设置 叶子节点E
标签1: 未设置 标签1 = 脏页标签 根节点有标签1
表示整棵树中有脏页 节点A有标签1
表示A子树中有脏页 节点B无标签1
表示B子树中无脏页
具体场景分析
假设我们有这样的树结构:
根节点 (标签1: 设置)
└── 中间节点 (标签1: 设置)
├── 槽位10: 项目A (标签1: 设置)
├── 槽位20: 项目B (标签1: 设置)
└── 槽位30: 项目C (标签1: 未设置)
现在我们要清除项目A的标签1:
如果不检查其他标签位:
- 错误的方式:直接向上清理
- 清除项目A标签 → 清除中间节点槽位10标签 → 清除根节点槽位0标签
结果: 根节点和中间节点都清除了标签1,但项目B仍然有标签1!这样就无法通过标签查找到项目B了
正确的方式(实际实现):
- 清除项目A标签 → 检查中间节点是否还有其他标签1 → 发现槽位20还有标签1 → 停止向上清理
基数树标签获取函数radix_tree_tag_get
c
int radix_tree_tag_get(struct radix_tree_root *root,
unsigned long index, int tag)
{
unsigned int height, shift;
struct radix_tree_node **slot;
int saw_unset_tag = 0;
height = root->height;
if (index > radix_tree_maxindex(height))
return 0;
shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
slot = &root->rnode;
for ( ; ; ) {
int offset;
if (*slot == NULL)
return 0;
offset = (index >> shift) & RADIX_TREE_MAP_MASK;
/*
* This is just a debug check. Later, we can bale as soon as
* we see an unset tag.
*/
if (!tag_get(*slot, tag, offset))
saw_unset_tag = 1;
if (height == 1) {
int ret = tag_get(*slot, tag, offset);
BUG_ON(ret && saw_unset_tag);
return ret;
}
slot = (struct radix_tree_node **)((*slot)->slots + offset);
shift -= RADIX_TREE_MAP_SHIFT;
height--;
}
}
函数整体功能
这个函数用于检查基数树中指定索引的数据项是否设置了指定的标签,同时包含调试检查以确保标签系统的正确性
代码分段详解
第一段:函数定义和变量声明
c
int radix_tree_tag_get(struct radix_tree_root *root,
unsigned long index, int tag)
{
unsigned int height, shift;
struct radix_tree_node **slot;
int saw_unset_tag = 0;
int: 返回整型,1表示标签已设置,0表示未设置root: 基数树的根节点指针index: 要检查的索引键tag: 要检查的标签编号height: 当前树的高度shift: 位偏移量,用于计算路径slot: 指向指针的指针,用于导航树结构saw_unset_tag = 0: 调试标志,记录是否在路径上看到未设置的标签
第二段:获取树高度并检查索引范围
c
height = root->height;
if (index > radix_tree_maxindex(height))
return 0;
height = root->height: 获取树的当前高度index > radix_tree_maxindex(height): 检查索引是否超出当前树能容纳的最大范围return 0: 如果索引超出范围,立即返回0(标签未设置)
第三段:初始化位移量和起始位置
c
shift = (height - 1) * RADIX_TREE_MAP_SHIFT;
slot = &root->rnode;
shift = (height - 1) * RADIX_TREE_MAP_SHIFT: 计算初始位移量slot = &root->rnode: 从根节点开始查找
第四段:无限循环开始
c
for ( ; ; ) {
for ( ; ; ): 无限循环,通过内部的break条件退出
第五段:检查当前节点是否存在
c
int offset;
if (*slot == NULL)
return 0;
int offset: 声明当前层的偏移量if (*slot == NULL): 检查当前节点是否为空return 0: 如果遇到空节点,说明路径中断,返回标签未设置
第六段:计算当前层偏移量
c
offset = (index >> shift) & RADIX_TREE_MAP_MASK;
index >> shift: 右移索引,提取当前层对应的位段& RADIX_TREE_MAP_MASK: 掩码操作,得到0-63的槽位编号
第七段:调试检查 - 记录未设置标签
c
/*
* This is just a debug check. Later, we can bale as soon as
* we see an unset tag.
*/
if (!tag_get(*slot, tag, offset))
saw_unset_tag = 1;
!tag_get(*slot, tag, offset): 检查当前节点的当前槽位是否没有设置标签saw_unset_tag = 1: 如果发现未设置的标签,设置标志位- 作用: 记录在路径上是否遇到过未设置的标签
第八段:检查是否到达叶子层
c
if (height == 1) {
int ret = tag_get(*slot, tag, offset);
BUG_ON(ret && saw_unset_tag);
return ret;
}
高度检查:
c
if (height == 1)
- 检查是否到达最后一层(叶子层的前一层)
获取最终标签状态:
c
int ret = tag_get(*slot, tag, offset);
- 获取目标数据项所在节点的标签状态
调试断言检查:
c
BUG_ON(ret && saw_unset_tag);
- 如果最终结果有标签(
ret=1)但在路径上看到过未设置的标签(saw_unset_tag=1),触发内核BUG - 这检查标签系统的一致性: 如果路径上有节点没有标签,最终的数据项也不应该有标签
返回结果:
c
return ret;
- 返回最终的标签状态
第九段:移动到下一层
c
slot = (struct radix_tree_node **)((*slot)->slots + offset);
shift -= RADIX_TREE_MAP_SHIFT;
height--;
slot = (struct radix_tree_node **)((*slot)->slots + offset): 更新slot指向下一层shift -= RADIX_TREE_MAP_SHIFT: 减少位移量height--: 高度减1,下降一层- 循环继续处理下一层
具体执行示例
示例1:正常情况 - 标签已设置
索引: 0x123, 标签: 1
路径: 根节点 → 中间节点A → 叶子节点B
执行过程:
层1: 根节点槽位0有标签1 → saw_unset_tag=0
层2: 中间节点A槽位4有标签1 → saw_unset_tag=0
层3: 叶子节点B槽位35有标签1 → ret=1, BUG_ON检查通过, 返回1
示例2:正常情况 - 标签未设置
索引: 0x123, 标签: 1
路径: 根节点 → 中间节点A → 叶子节点B
执行过程:
层1: 根节点槽位0有标签1 → saw_unset_tag=0
层2: 中间节点A槽位4无标签1 → saw_unset_tag=1
层3: 叶子节点B槽位35无标签1 → ret=0, 返回0
示例3:异常情况 - 触发BUG_ON
索引: 0x123, 标签: 1
路径: 根节点 → 中间节点A → 叶子节点B
执行过程:
层1: 根节点槽位0有标签1 → saw_unset_tag=0
层2: 中间节点A槽位4无标签1 → saw_unset_tag=1 ← 路径中断!
层3: 叶子节点B槽位35有标签1 → ret=1, BUG_ON触发!
关键设计要点
1. 调试检查的重要性
saw_unset_tag 和 BUG_ON 用于检测标签系统的不一致性:
- 如果路径上有节点没有标签,说明这条路径不应该有带标签的数据项
- 如果最终发现数据项有标签,说明标签维护逻辑有bug
2. 标签传播规则的验证
这个函数实际上验证了基数树标签系统的基本规则:
"如果路径上的任何节点没有设置标签,那么该路径末端的数据项也不应该设置该标签"
检查基数树是否存在设置了指定标签的数据项radix_tree_tagged
c
int radix_tree_tagged(struct radix_tree_root *root, int tag)
{
int idx;
if (!root->rnode)
return 0;
for (idx = 0; idx < RADIX_TREE_TAG_LONGS; idx++) {
if (root->rnode->tags[tag][idx])
return 1;
}
return 0;
}
函数整体功能
这个函数用于快速检查基数树中是否存在至少一个设置了指定标签的数据项,是标签系统的状态查询接口
代码分段详解
第一段:函数定义
c
int radix_tree_tagged(struct radix_tree_root *root, int tag)
int: 返回整型,1表示存在带标签的数据项,0表示不存在root: 基数树的根节点指针tag: 要检查的标签编号
第二段:检查空树情况
c
if (!root->rnode)
return 0;
if (!root->rnode): 检查根节点指针是否为空return 0: 如果树为空,直接返回0(没有带标签的数据项)- 作用: 快速处理空树的边界情况
第三段:声明循环变量
c
int idx;
int idx: 循环索引变量,用于遍历标签的long数组
第四段:遍历标签位图数组
c
for (idx = 0; idx < RADIX_TREE_TAG_LONGS; idx++) {
for (idx = 0; idx < RADIX_TREE_TAG_LONGS; idx++): 循环遍历所有存储标签的long单元RADIX_TREE_TAG_LONGS:- 在64位系统 = 1 (因为64个槽位可以用1个64位long存储)
- 在32位系统 = 2 (因为64个槽位需要2个32位long存储)
第五段:检查标签位图
c
if (root->rnode->tags[tag][idx])
return 1;
root->rnode->tags[tag][idx]: 访问根节点指定标签的第idx个long单元if (...): 检查该long单元是否非零(即是否有任何位被设置)return 1: 如果发现任何设置的标签位,立即返回1(存在带标签的数据项)
第六段:返回未找到
c
return 0;
}
return 0: 如果循环结束都没有找到设置的标签位,返回0(没有带标签的数据项)- 只有在所有long单元都为零时才执行到这里
关键设计要点
1. 性能优化
- 只检查根节点:利用标签的传播特性
- 快速返回:发现第一个设置位就立即返回
- 避免遍历:不需要遍历整个树结构
2. 标签传播特性的利用
这个函数依赖于基数树标签系统的重要特性:
"如果子树中有任何数据项设置了标签,那么所有祖先节点对应的路径位都会设置该标签"
这意味着:
- 只要树中有任何一个数据项设置了标签,根节点就一定会有对应的标签位被设置
- 如果根节点没有任何标签位被设置,那么整个树中都没有该标签的数据项
3. 正确性保证
c
if (!root->rnode)
return 0;
这个检查确保:
- 空树不会访问空指针
- 空树确实没有带标签的数据项
4. 平台兼容性
通过 RADIX_TREE_TAG_LONGS 抽象:
- 64位系统:循环1次
- 32位系统:循环2次
- 代码在不同平台都能正确工作