Radix tree
原理
利用radix tree可以根据一个长整形快速查找其对应的对象指针。radix树与trie树有点相似,trie树一般用于字符串到对象的映射,radix树一般用于长整数到对象的映射。
![这里写图片描述 Radix tree/IDR/IDA_初始化](https://s2.51cto.com/images/blog/202210/31161247_635f837f8ebbc46103.bmp?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=,x-oss-process=image/resize,m_fixed,w_1184)
来看一个四叉radix树,树高为4,每一个根节点最多有四个叶节点,总共最多有256个节点。而且这里每个叶节点使用两个字母(00/01/10/11)表示,一层层下来,由于总共有4层,每个长整型一定是0x00000010类似的8个字母组成的数据。
当我们插入一个新节点的时候,我们根据数据的比特位,在树中向下查找,若没有相应的节点,则生成相应的节点,直到数据比特位访问完,则建立叶节点映射相应的对象。
当我们删除一个节点的时候,沿着路径查找到叶节点后,直接删除叶节点,中间的非叶节点不删除。
结构体及API介绍
linux内核中相关的文件include/linux/radix-tree.h和lib/radix-tree.c,使用时包含头文件#include<linux/radix-tree.h>
radix树的root节点的结构体
struct radix_tree_root {
unsigned int height;//从叶节点向上计算出的树高度
gfp_t gfp_mask;//内存申请的标识
struct radix_tree_node __rcu *rnode;//子节点指针
};
radix树叶子节点的结构体
struct radix_tree_node {
unsigned int height;/* Height from the bottom */
unsigned int count;//子节点个数
union {
struct radix_tree_node *parent;//父节点指针
struct rcu_head rcu_head;//用于节点释放的RCU链表
};
void __rcu *slots[RADIX_TREE_MAP_SIZE];//指向存储数据指针
unsigned long tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS];
};
初始化
#define RADIX_TREE_INIT(mask) { \
.height = 0, \
.gfp_mask = (mask), \
.rnode = NULL, \
}
#define RADIX_TREE(name, mask) \
struct radix_tree_root name = RADIX_TREE_INIT(mask)
或者使用
先定义struct radix_tree_root my_radix_tree;变量之后,再使用下面的宏对这个变量初始化。
#define INIT_RADIX_TREE(root, mask) \
do { \
(root)->height = 0; \
(root)->gfp_mask = (mask); \
(root)->rnode = NULL; \
} while (0)
节点的插入与删除与查找
int radix_tree_insert(struct radix_tree_root *, unsigned long, void *);
void *radix_tree_delete(struct radix_tree_root *, unsigned long);
void *radix_tree_lookup(struct radix_tree_root *, unsigned long);
例子
IDR
IDR机制是内核中将一个整数ID号和指针关联在一起的机制。
比如,当适配器要访问总线上的I2C设备时,首先要知道他们的ID号,同时要在内核中建立一个用于描述该设备的设备结构体,驱动程序将ID号和设备结构体关联起来,如果使用数组进行索引,一旦ID号很大,则用数组索引会占据大量的存储空间,如果使用链表,在总线上设备特别多的情况下,链表的查询效率不高。此时,IDR机制应运而生,内部采用红黑树,可以很方便的将整数和指针关联起来,并且有很高的搜索效率。
下面是linux/idr.h头文件中的一些定义,具体的函数调用接口可能随着新的内核已经发生了改变,要根据对应的include/idr.h头文件中的定义来。
相关结构体
struct idr {
struct idr_layer __rcu *hint; /* the last layer allocated from */
struct idr_layer __rcu *top;
struct idr_layer *id_free;
int layers; /* only valid w/o concurrent changes */
int id_free_cnt;
int cur; /* current pos for cyclic allocation */
spinlock_t lock;
};
struct idr_layer {
int prefix; /* the ID prefix of this idr_layer */
DECLARE_BITMAP(bitmap, IDR_SIZE); /* A zero bit means "space here" */
struct idr_layer __rcu *ary[1<<IDR_BITS];
int count; /* When zero, we can release it */
int layer; /* distance from leaf */
struct rcu_head rcu_head;
};
定义并初始化
//定义
#define DEFINE_IDR(name) struct idr name = IDR_INIT(name)
//静态初始化
#define IDR_INIT(name) \
{ \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
}
//动态初始化
void idr_init(struct idr *idp);
分配存放ID号的内存
int idr_pre_get(struct idr *idp, gfp_t gfp_mask)
将ID号和指针关联
int idr_get_new(struct idr *idp, void *ptr, int *id)
参数ptr是需要关联的指针,参数id是由内核自动分配的ID号
通过ID号查找对应的指针
void *idr_find(struct idr *idr, int id)
删除ID号
void idr_remove(struct idr *idp, int id);
IDA
ida只是用来分配id,并不将某数据结构和id关联起来。
sd设备的设备名,如sda,驱动在生成设备文件的时候会向系统申请一个ida,也就是唯一id,然后把id映射成设备文件名sdxxx,
#define DEFINE_IDA(name) struct ida name = IDA_INIT(name)
#define IDA_INIT(name) { .idr = IDR_INIT((name).idr), .free_bitmap = NULL, }
void ida_init(struct ida *ida);
int ida_pre_get(struct ida *ida, gfp_t gfp_mask);
int ida_get_new(struct ida *ida, int *p_id);
void ida_remove(struct ida *ida, int id);
void ida_destroy(struct ida *ida);
参考文章
- IDR机制