Linux中基数树的初始化
初始化基数树radix_tree_init
void __init radix_tree_init(void)
{radix_tree_node_cachep = kmem_cache_create("radix_tree_node",sizeof(struct radix_tree_node), 0,SLAB_PANIC, radix_tree_node_ctor, NULL);radix_tree_init_maxindex();hotcpu_notifier(radix_tree_callback, 0);
}
static void
radix_tree_node_ctor(void *node, kmem_cache_t *cachep, unsigned long flags)
{memset(node, 0, sizeof(struct radix_tree_node));
}
函数整体功能
这个函数用于初始化 Linux 内核中的基数树(radix tree)子系统,创建内存缓存池、初始化最大索引值,并注册 CPU 热插拔通知器
代码分段详解
第一段:创建内存缓存池
radix_tree_node_cachep = kmem_cache_create("radix_tree_node",sizeof(struct radix_tree_node), 0,SLAB_PANIC, radix_tree_node_ctor, NULL);
详细解释:
kmem_cache_create()
: 内核函数,创建专用的内存对象缓存池"radix_tree_node"
: 缓存池的名称,用于调试和识别sizeof(struct radix_tree_node)
: 每个对象的大小,即基数树节点的大小0
: 对齐参数,0表示使用默认对齐SLAB_PANIC
: 标志位,如果内存分配失败则系统panicradix_tree_node_ctor
: 构造函数指针,创建对象时自动调用NULL
: 析构函数,这里不需要radix_tree_node_cachep
: 全局变量,保存创建的缓存池指针
作用: 为基数树节点创建专用的内存缓存,提高内存分配效率。
第二段:初始化最大索引值
radix_tree_init_maxindex();
详细解释:
- 调用
radix_tree_init_maxindex()
函数 - 该函数内部会计算并设置每个高度(level)能存储的最大索引值
- 根据树的深度和每个节点的槽位数(RADIX_TREE_MAP_SIZE)计算
- 结果存储在全局数组
height_to_maxindex[]
中
作用: 预计算不同树高度对应的最大键值,用于后续的边界检查。
第三段:注册CPU热插拔通知
hotcpu_notifier(radix_tree_callback, 0);
详细解释:
hotcpu_notifier()
: 注册CPU热插拔事件的通知器radix_tree_callback
: 回调函数,当CPU状态变化时被调用0
: 优先级,0表示默认优先级
作用: 确保在多CPU环境下,当CPU被热添加或移除时,基数树能够正确地进行内存管理和状态同步
构造函数详解
static void
radix_tree_node_ctor(void *node, kmem_cache_t *cachep, unsigned long flags)
{memset(node, 0, sizeof(struct radix_tree_node));
}
详细解释:
radix_tree_node_ctor
: 构造函数名称void *node
: 新分配的内存对象指针kmem_cache_t *cachep
: 所属的缓存池unsigned long flags
: 分配标志memset(node, 0, sizeof(struct radix_tree_node))
: 将新节点内存清零
作用: 确保每个新分配的基数树节点初始状态为全零,避免未初始化内存带来的问题
预计算基数树在不同高度的最大索引值radix_tree_init_maxindex
static __init unsigned long __maxindex(unsigned int height)
{unsigned int tmp = height * RADIX_TREE_MAP_SHIFT;unsigned long index = (~0UL >> (RADIX_TREE_INDEX_BITS - tmp - 1)) >> 1;if (tmp >= RADIX_TREE_INDEX_BITS)index = ~0UL;return index;
}
static __init void radix_tree_init_maxindex(void)
{unsigned int i;for (i = 0; i < ARRAY_SIZE(height_to_maxindex); i++)height_to_maxindex[i] = __maxindex(i);
}
函数整体功能
这两个函数用于预计算基数树在不同高度(level)下能够存储的最大索引值,并将结果存储在全局数组中供后续使用
__maxindex
函数详解
函数定义
static __init unsigned long __maxindex(unsigned int height)
static
: 限制函数作用域在当前文件__init
: 表示这是初始化函数,内核启动后会被释放unsigned long
: 返回值类型,64位系统上是64位无符号整数height
: 参数,表示基数树的高度
第一段:计算位移量
unsigned int tmp = height * RADIX_TREE_MAP_SHIFT;
详细解释:
RADIX_TREE_MAP_SHIFT
: 通常为6(因为 2^6 = 64,每个节点有64个槽位)height
: 树的高度,从0开始tmp
: 计算该高度下总共可以表示的比特位数- 示例: 如果高度为2,则
tmp = 2 * 6 = 12
,表示可以索引 2^12 = 4096 个元素
第二段:计算最大索引值
unsigned long index = (~0UL >> (RADIX_TREE_INDEX_BITS - tmp - 1)) >> 1;
这是最复杂的部分,分解解释:
-
~0UL
:- 将所有位设为1的unsigned long值
-
RADIX_TREE_INDEX_BITS
:- 通常32(在32位系统上)
- 表示索引的最大比特位数
-
(RADIX_TREE_INDEX_BITS - tmp - 1)
:- 计算需要右移的位数
- 减1是为了避免溢出
-
(~0UL >> (RADIX_TREE_INDEX_BITS - tmp - 1))
:- 通过右移创建一个掩码
- 示例: 如果
tmp
=12,则右移 32-12-1 = 19位
-
>> 1
:- 再右移1位,确保索引值正确
实际效果: 创建一个具有 tmp
个有效位的最大数值,计算在给定高度下,基数树可以寻址的总比特位数
为什么不直接通过(1 << tmp) - 1
实现?
- 原因:当
tmp
=32时,1 << 32
在32位系统上会溢出(因为只能左移0-31位),在64为系统上同理
为什么要-1
再>>1
,不能直接右移(RADIX_TREE_INDEX_BITS - tmp)
吗?
- 原因:当
tmp
=0 时,我们想要最大索引为0(因为高度为0不能存储任何元素),如果直接用~0UL >> (64 - 0)
是未定义行为
第三段:边界检查
if (tmp >= RADIX_TREE_INDEX_BITS)index = ~0UL;
详细解释:
- 如果计算的比特位数
tmp
大于等于最大索引比特数 - 说明这个高度可以表示所有可能的索引值
- 直接将最大索引设为全1(最大值)
- 防止情况: 当高度足够大时,避免计算错误
radix_tree_init_maxindex
函数详解
函数定义
static __init void radix_tree_init_maxindex(void)
void
: 无返回值- 初始化函数,在内核启动时调用
循环初始化数组
unsigned int i;for (i = 0; i < ARRAY_SIZE(height_to_maxindex); i++)height_to_maxindex[i] = __maxindex(i);
详细解释:
height_to_maxindex[]
: 全局数组,存储每个高度对应的最大索引ARRAY_SIZE()
: 宏,计算数组大小- 循环遍历所有可能的高度
- 对每个高度
i
,调用__maxindex(i)
计算最大索引值 - 将结果存储在全局数组的对应位置
清理基数树预分配节点缓存radix_tree_callback
#ifdef CONFIG_HOTPLUG_CPU
static int radix_tree_callback(struct notifier_block *nfb,unsigned long action,void *hcpu)
{int cpu = (long)hcpu;struct radix_tree_preload *rtp;/* Free per-cpu pool of perloaded nodes */if (action == CPU_DEAD) {rtp = &per_cpu(radix_tree_preloads, cpu);while (rtp->nr) {kmem_cache_free(radix_tree_node_cachep,rtp->nodes[rtp->nr-1]);rtp->nodes[rtp->nr-1] = NULL;rtp->nr--;}}return NOTIFY_OK;
}
#endif /* CONFIG_HOTPLUG_CPU */
函数整体功能
这是一个CPU热插拔回调函数,当CPU被移除时,负责清理该CPU的基数树预分配节点缓存,防止内存泄漏。、
代码分段详解
第一段:条件编译和函数定义
#ifdef CONFIG_HOTPLUG_CPU
static int radix_tree_callback(struct notifier_block *nfb,unsigned long action,void *hcpu)
详细解释:
#ifdef CONFIG_HOTPLUG_CPU
: 条件编译,只有在内核配置支持CPU热插拔时才编译此代码static int
: 静态函数,返回整型状态码struct notifier_block *nfb
: 通知块指针,标识哪个通知器被调用unsigned long action
: 动作类型,表示发生了什么事件(CPU上线、下线等)void *hcpu
: 泛型指针,指向相关的CPU编号
第二段:获取CPU编号
{int cpu = (long)hcpu;
详细解释:
int cpu = (long)hcpu
: 将泛型指针转换为long类型,再赋给cpu
变量hcpu
参数实际上包含的是CPU的编号- 这种转换是Linux内核通知链的标准做法
- 作用: 获取事件相关的具体CPU编号
第三段:声明变量
struct radix_tree_preload *rtp;
详细解释:
struct radix_tree_preload *rtp
: 声明指向基数树预加载结构的指针radix_tree_preload
结构通常包含预分配节点数组和计数- 这个结构是per-CPU的,每个CPU有自己的实例
第四段:检查CPU死亡事件
/* Free per-cpu pool of perloaded nodes */if (action == CPU_DEAD) {
详细解释:
- 释放每个CPU的预加载节点池
action == CPU_DEAD
: 检查是否是CPU死亡(被移除)事件CPU_DEAD
是预定义常量,表示CPU已经完全下线- 作用: 只处理CPU移除事件,忽略其他事件(如CPU上线)
第五段:获取per-CPU数据
rtp = &per_cpu(radix_tree_preloads, cpu);
详细解释:
per_cpu(radix_tree_preloads, cpu)
: 获取指定CPU的预加载数据结构radix_tree_preloads
: 全局per-CPU变量名cpu
: 目标CPU编号&
: 取地址,得到指向该CPU数据结构的指针- 作用: 定位到要被移除的CPU的专用数据结构
第六段:循环释放预分配节点
while (rtp->nr) {kmem_cache_free(radix_tree_node_cachep,rtp->nodes[rtp->nr-1]);rtp->nodes[rtp->nr-1] = NULL;rtp->nr--;}
第一行:循环条件
while (rtp->nr) {
rtp->nr
: 预分配节点的数量计数器- 循环继续条件:当还有预分配节点时(nr > 0)
第二行:释放节点内存
kmem_cache_free(radix_tree_node_cachep, rtp->nodes[rtp->nr-1]);
kmem_cache_free()
: 释放对象回缓存池radix_tree_node_cachep
: 全局基数树节点缓存池rtp->nodes[rtp->nr-1]
: 节点数组的最后一个元素(因为nr是计数,索引从0开始)- 作用: 将节点内存归还给缓存池
第三行:清空指针
rtp->nodes[rtp->nr-1] = NULL;
- 将数组中的指针设为NULL,防止悬空指针
- 这是良好的编程实践,避免后续误用
第四行:递减计数器
rtp->nr--;
- 减少预分配节点计数
- 为处理下一个节点做准备
第七段:返回成功状态
}return NOTIFY_OK;
}
#endif /* CONFIG_HOTPLUG_CPU */
详细解释:
return NOTIFY_OK
: 返回通知处理成功状态NOTIFY_OK
是预定义常量,表示通知已正确处理#endif
: 结束条件编译块