【Kernel】内核数据结构之映射(v5.15.10)

【Kernel】内核数据结构之映射(v5.15.10)

内核源码快捷查看

一、概念

  • 映射常称为关联数组,是一个由唯一键组成的集合,而每个键必然关联一个特定的值。键到值的关联关系称为映射。映射至少支持三个操作:add(key,value) remove(key) value = Lookup(key) 映射可以通过散列表和二叉搜索树实现。

二、IDR简介

系统许多资源都用整数 ID 来标识,如进程 ID、文件描述符 ID、IPC ID 等;资源信息 通常存放在对应的数据结构中 (如进程信息存放在 task_struct 中、ipc 信息存放在 ipc_perm 中),id 与数据结构的关联机制有不同的实现,IDR 机制是其中的一种。

IDR,ID Radix 的缩写。IDR 主要用于建立 id 与指针(指向对应的数据结构) 之间的对应关系。IDR 用类基数树结构来构造一个稀疏数组,以 id 为索引找到对应数组元素, 进而找到对应的数据结构指针。用到 IDR 机制的主要有:IPC id (消息队列 id、 信号量 id、共享内存 id 等),磁盘分区 id (sda 中数字部分)等。

三、内核映射:IDR

Linux 内核提供了一套完整的 IDR 实现机制,其基于 Radix-tree。在 linux 4.19 之前内核并未采用 xarray 代替 radix,但 linux 4.20 之后,内核采用 xarray 替代了 radix-tree,因此 IDR 的底层实现也发生了改变,但这不影响上层 IDR 的接口功能。 内核关于 IDR 的源码位于:

include/linux/idr.h
lib/idr.c

在 Linux 内核中,IDR 作为重要的基础数据,内核定义了相应的数据结构对 IDR 进行维护。

struct idr {
        struct radix_tree_root  idr_rt;
        unsigned int            idr_base;
        unsigned int            idr_next;
};

内核定义了 struct idr 结构用来维护一个 IDR。idr_rt 成员定义了一棵 radix-tree 树; idr_base 成员用于指定 ID 分配的起始地址;idr_next 成员指定下一个 ID 的偏移号。

radix tree–基数树

对于长整型数据的映射,如何解决Hash冲突和Hash表大小的设计是一个很头疼的问题。
radix树就是针对这种稀疏的长整型数据查找,能快速且节省空间地完成映射。借助于Radix树,我们可以实现对于长整型数据类型的路由。利用radix树可以根据一个长整型(比如一个长ID)快速查找到其对应的对象指针。这比用hash映射来的简单,也更节省空间,使用hash映射hash函数难以设计,不恰当的hash函数可能增大冲突,或浪费空间。

radix tree是一种多叉搜索树,树的叶子结点是实际的数据条目。每个结点有一个固定的、2^n指针指向子结点(每个指针称为槽slot,n为划分的基的大小)

插入、删除

radix Tree(基数树) 其实就差不多是传统的二叉树,只是在寻找方式上,利用比如一个unsigned int的类型的每一个比特位作为树节点的判断。
可以这样说,比如一个数1000101010101010010101010010101010,那么按照Radix 树的插入就是在根节点,如果遇到0,就指向左节点,如果遇到1就指向右节点,在插入过程中构造树节点,在删除过程中删除树节点。如果觉得太多的调用Malloc的话,可以采用池化技术,预先分配多个节点。
(使用一个比特位判断,会使树的高度过高,非叶节点过多。故在实际应用中,我们一般是使用多个比特位作为树节点的判断,但多比特位会使节点的子节点槽变多,增大节点的体积,一般选用2个或4个比特位作为树节点即可)

img

扫描二维码关注公众号,回复: 14616086 查看本文章

插入:

我们在插入一个新节点时,我们根据数据的比特位,在树中向下查找,若没有相应结点,则生成相应结点,直到数据的比特位访问完,则建立叶节点映射相应的对象。

删除:

我们可以“惰性删除”,即沿着路径查找到叶节点后,直接删除叶节点,中间的非叶节点不删除。

Linux基数树(radix tree)是将long整数键值与指针相关联的机制,它存储有效率,并且可快速查询,用于整数值与指针的映射(如:IDR机制)、内存管理等。
IDR(ID Radix)机制是将对象的身份鉴别号整数值ID与对象指针建立关联表,完成从ID与指针之间的相互转换。IDR机制使用radix树状结构作为由id进行索引获取指针的稀疏数组,通过使用位图可以快速分配新的ID,IDR机制避免了使用固定尺寸的数组存放指针。

Linux 内核中,每个 IDR 都包含一个 radix-tree 树,内核在初始化完 IDR 之后,每当需要分配新的ID与指针绑定的时候,IDR 通过计算 idr_base + idr_next 的值计算下一个ID的值,并且从radix-tree 中找到 ID 对应的 slot 供存储指针。由于ID申请是连续的,因此从 radix-tree 来看,树都是往一侧偏移退化形成一个稀疏数组。如下图,连续的ID导致树的偏移退化。

DTS

IDR 初始化操作

IDR 机制中,内核使用 struct idr 数据结构维护着 IDR。在使用 IDR 之前需要对 IDR 进行初始化。初始化的内容主要包括 IDR 包含 radix-tree 的初始化,以及 分配 ID 的起始值。内核提供了多个接口函数用于 IDR 的初始化,开发者可以参考 下面的文章进行实践:


IDR 插入操作

IDR 插入操作就是从内核中分配一个 ID 与给定的指针进行绑定操作。IDR 机制中, 通过分配一个 ID 之后,并在 IDR 对应的 radix-tree 中找到 ID 对应的 slot, 然后将给定的指针存储在 slot 中,以此实现 ID 与指针绑定的原理。内核也提供了 相应的函数用于 ID 的插入操作,开发者可以参考下面的文章进行实践:


IDR 查询操作

IDR 查询操作就是通过 ID 找到对应的指针。IDR 通过 radix-tree 机制提供的函数, 实现了 ID 的快速查找到对应的 slot 之后,以此获得对应的指针。内核也提供了 相应的函数用于 ID 的查找操作,开发者可以参考下面的文章进行实践:


IDR 修改操作

IDR 修改操作就是通过 ID 替换与之绑定的指针。IDR 通过 radix-tree 机制提供的函数, 找到 ID 在 radix-tree 中对应的 slot,然后将 slot 的值替换成新值,以此实现 IDR 的修改操作。内核也提供了相应的函数用于 ID 的修改操作,开发者可以参考下面的 文章进行实践:


IDR 删除操作

IDR 删除操作指的是解除 ID 与之绑定的指针关系。IDR 通过删除操作将 ID 与指针 解除关系之后,并回收 ID。内核也提供了相应的函数用于 ID 的删除操作,开发者可以 参考下面的文章进行实践:


IDR 遍历操作

IDR 的遍历操作指的是遍历 IDR 所有 ID 对应的指针。内核也提供了相应的函数用于 ID 的遍历操作,开发者可以参考下面的文章进行实践:


IDR 其他操作

IDR 机制还提供了许多有用的接口,开发者可以参考如下文档进行实践:

四、参考资料与补充

参考

IDR:ID Radix(包括IDR源码实践,从构建到实现)

Linux 内核设计与实现笔记 – 数据结构

猜你喜欢

转载自blog.csdn.net/qq_40392981/article/details/123064188
今日推荐