Linux中基数树标签相关操作函数的实现

基数树标签设置函数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: 根节点没有父节点,设为NULL
  • pathp->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_tagBUG_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次
  • 代码在不同平台都能正确工作
相关推荐
or77iu_N5 小时前
Linux 解压安装(安装tomcat)
linux·运维·tomcat
海棠蚀omo5 小时前
Linux操作系统-父进程的等待:一个关于回收与终结的故事
linux·操作系统
乌萨奇也要立志学C++5 小时前
【Linux】Ext系列文件系统 从磁盘结构到文件存储的原理剖析
android·linux·缓存·1024程序员节
软安科技6 小时前
专有软件使用Linux内核的用户头文件违反GPL吗?| 开源合规场景
linux·开源软件·开源协议
A-刘晨阳6 小时前
K8S 二进制集群搭建(一主两从)
linux·运维·云原生·容器·kubernetes
egoist20236 小时前
[linux仓库]信号处理[进程信号·伍]
linux·信号处理·写时拷贝·软中断·硬件中断·缺页中断·时钟中断
HIT_Weston6 小时前
15、【Ubuntu】【VSCode】VSCode 断联问题分析:UID 补充
linux·vscode·ubuntu
碰大点7 小时前
Ubuntu 16.04交叉编译arm-linux-gnueabihf的QT5.6.2
linux·arm开发·qt·ubuntu·arm-linux
小-黯7 小时前
Linux硬盘挂载脚本
linux·运维·服务器