[TOC]
lib/radix-tree.c 基数树(Radix Tree) 整数键到指针的高效映射 历史与背景 这项技术是为了解决什么特定问题而诞生的? lib/radix-tree.c
提供的基数树(Radix Tree)是为了解决一个在内核中非常常见的问题:如何将一个整数(特别是unsigned long
类型)作为键,高效地、空间优化地映射到一个指针 。
在基数树出现之前,内核中存在多种方式来解决这个问题,但各有缺点:
哈希表 :对于稀疏的键(例如,文件偏移量,可能非常大且不连续),哈希表虽然平均查找速度快,但存在哈希冲突问题,且无法高效地进行范围查找或有序遍历。
红黑树 :可以处理稀疏的键并支持有序遍历,但它是指针密集型的数据结构,每个节点都有左右子指针、父指针和颜色等额外开销,导致内存占用较高,且缓存效率不佳。
直接映射数组 :如果键的范围不大且密集,这是最快的方法。但如果键的范围很大(例如unsigned long
的所有可能值),创建一个如此巨大的指针数组是完全不可行的,会消耗天文数字般的内存。
基数树被设计出来,就是为了结合以上方法的优点,同时避免它们的缺点。它特别擅长管理稀疏但可能聚集成块 的整数键,这在文件系统和内存管理中非常常见。
它的发展经历了哪些重要的里程碑或版本迭代? 基数树在Linux内核中的发展是一个持续优化和功能演进的过程:
基本实现 :最初的基数树实现奠定了其核心思想——使用整数键的比特位作为路径,逐级在树中导航。它被迅速应用到核心的页缓存(Page Cache)中,用于将文件的逻辑偏移量映射到struct page
指针。
标签(Tags)功能 :为了满足更复杂的需求,基数树增加了“标签”功能。这允许为树中的每个条目设置或清除一些标志位(tags)。例如,页缓存使用标签来标记哪些页面是“脏”的(dirty)或“需要回写”(writeback)。这使得可以非常快速地查找到所有带有特定标签的页面,而无需遍历整个文件。
多阶(Multi-order)条目 :为了进一步优化空间,特别是当多个连续的键都映射到同一个值时,基数树引入了多阶条目的概念。这允许一个叶子节点代表一个键的范围,而不是单个键,从而减少了树的深度和节点的数量。
XArray的演进 :尽管基数树非常成功,但其API被认为有些复杂和晦涩。内核开发者Matthew Wilcox主导了一个项目,旨在创建一个API更友好、功能更强大、性能更好的替代品,这就是XArray 。现代内核中,许多原先使用基数树的地方都已经被迁移到了XArray。XArray可以看作是基数树的下一代演进,它提供了更丰富的功能(如存储多种类型的值而不仅是指针)和更简洁的接口,但其底层核心思想仍然源于基数树。
目前该技术的社区活跃度和主流应用情况如何? 基数树仍然是内核中的一个核心数据结构,尽管它的现代继承者XArray正在逐步取代它。
核心应用 :它最著名和最核心的应用是在页缓存 中,用于建立文件偏移量(pgoff_t
,一个unsigned long
)到内存页(struct page *
)的映射。
遗留与迁移 :许多老的代码路径仍然在使用基数树。新编写的代码则被强烈推荐使用XArray。社区的活跃度主要体现在将现有的基数树用户迁移到XArray上,以及对XArray本身的持续开发。
核心原理与设计 它的核心工作原理是什么? 基数树是一种基于键的二进制位进行路径压缩的前缀树(Trie)。它将一个unsigned long
类型的键看作是一个二进制串,并利用这个串来在树中导航。
树的结构 :树由radix_tree_node
组成。每个节点内部是一个指针数组(通常大小为64,对应unsigned long
中的6个比特位)。树的高度是固定的,取决于unsigned long
的总位数和每个节点所能表示的位数。
查找过程 :
从键的最高有效位开始,取出一组比特(例如6个比特)。
使用这组比特作为索引,在当前节点的指针数组中找到下一级节点的指针。
移动到下一级节点,然后取键的下一组比特,重复上述过程。
这个过程一直持续,直到到达叶子节点,叶子节点中存储的就是与该键关联的指针。
空间优化 :基数树的核心优势在于其空间效率。
路径压缩 :如果一个中间节点只有一个子节点,那么这个中间节点实际上可以被省略,从而压缩树的路径。
懒分配 :只有当需要插入一个键时,通往该键的路径上所需的节点才会被动态分配。对于稀疏的键空间,这意味着树中绝大部分可能的节点根本不会存在,从而节省了大量内存。
它的主要优势体现在哪些方面?
空间效率高 :对于稀疏的整数键分布,其内存占用远低于直接映射数组,也通常优于指针开销大的红黑树。
查找速度快且稳定 :查找操作的时间复杂度为O(k),其中k是键的位数。这是一个与树中元素总数无关的、非常稳定的上界。
高效的范围和标签查找 :标签机制使得查找具有特定属性的条目变得非常高效。
缓存友好 :相比于指针追逐的红黑树,基数树的节点内部是连续的数组,这在一定程度上提高了缓存局部性。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
API复杂 :基数树的API(特别是涉及标签和锁的部分)相对复杂,容易误用。这也是驱动XArray开发的主要动机之一。
只适用于整数键 :它被专门设计用来处理unsigned long
类型的键。如果键是字符串或其他复杂类型,哈希表是更好的选择。
写操作的锁竞争 :基数树的修改操作需要锁来保护。在高并发写入的场景下,这可能会成为瓶颈。(XArray在这方面做了很多改进)。
使用场景 在哪些具体的业务或技术场景下,它是首选解决方案? 在内核中,任何需要将一个稀疏的、unsigned long
类型的ID或索引映射到一个指针 ,特别是还需要高效地查找带有特定标记的条目 时,基数树(或其后继者XArray)是首选解决方案。
页缓存(Page Cache) :这是基数树的经典应用。一个文件可能非常大(GB甚至TB级别),但同一时间在内存中的页面可能只有很少一部分。页缓存使用基数树将文件的页偏移量(一个稀疏的unsigned long
)映射到struct page
指针。同时,它使用标签来快速定位脏页,以便回写线程能高效地找到需要写入磁盘的数据。
文件描述符管理 :在某些实现中,可以使用基数树来管理一个进程的文件描述符表,将整数fd映射到struct file
指针。
内存管理 :用于管理物理内存页帧号(PFN)到struct page
的映射,特别是在非连续内存(discontiguous memory)模型中。
是否有不推荐使用该技术的场景?为什么?
键不是unsigned long
:如上所述,非整数键应使用哈希表。
需要按键的完整顺序进行遍历 :虽然基数树存储的键是有序的,但遍历它不如遍历红黑树或链表来得直接。红黑树能提供中序遍历。
数据量很小且键是密集的 :如果键是从0开始的连续整数且数量不多,一个简单的数组会更快、更简单。
对比分析 请将其 与 其他相似技术 进行详细对比。
特性
基数树 (Radix Tree) / XArray
哈希表 (Hash Table)
红黑树 (Red-Black Tree)
核心功能
整数键 到指针的空间优化映射,支持标签 。
任意键 到值的快速映射。
任意可比较的键 到值的有序 映射。
数据模型
unsigned long
-> void *
key
-> value
key
-> value
查找性能
O(k) (k为键的位数),稳定 。
平均 O(1),最坏 O(n),不稳定 。
O(log n),稳定 。
空间效率
非常高 (对稀疏键)。
中等 。
较低 (指针开销大)。
有序性
有序 。
无序 。
有序 。
特殊功能
标签(Tags) ,用于快速查找带标记的条目。
无。
无。
范围查找
支持,效率高。
不支持。
支持,效率高。
实现复杂性
高。
较低。
中等。
典型用途
页缓存 ,文件描述符表,PFN到page的映射。
dcache,PID表,连接跟踪。
VMA管理(已被Maple Tree取代),高精度定时器。
include/linux/radix-tree.h RADIX_TREE_INIT 1 2 3 4 5 6 #define ROOT_IS_IDR ((__force gfp_t)4) #define ROOT_TAG_SHIFT (__GFP_BITS_SHIFT) #define RADIX_TREE_INIT(name, mask) XARRAY_INIT(name, mask)
INIT_RADIX_TREE 1 #define INIT_RADIX_TREE(root, mask) xa_init_flags(root, mask)
radix_tree_iter_init 初始化基数树迭代器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static __always_inline void __rcu **radix_tree_iter_init (struct radix_tree_iter *iter, unsigned long start) { iter->index = 0 ; iter->next_index = start; return NULL ; }
radix_tree_next_chunk 迭代下一个槽位 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 void __rcu **radix_tree_next_chunk (const struct radix_tree_root *root, struct radix_tree_iter *iter, unsigned flags) { unsigned tag = flags & RADIX_TREE_ITER_TAG_MASK; struct radix_tree_node *node , *child ; unsigned long index, offset, maxindex; if ((flags & RADIX_TREE_ITER_TAGGED) && !root_tag_get(root, tag)) return NULL ; index = iter->next_index; if (!index && iter->index) return NULL ; restart: radix_tree_load_root(root, &child, &maxindex); if (index > maxindex) return NULL ; if (!child) return NULL ; if (!radix_tree_is_internal_node(child)) { iter->index = index; iter->next_index = maxindex + 1 ; iter->tags = 1 ; iter->node = NULL ; return (void __rcu **)&root->xa_head; } do { node = entry_to_node(child); offset = radix_tree_descend(node, &child, index); if ((flags & RADIX_TREE_ITER_TAGGED) ? !tag_get(node, tag, offset) : !child) { if (flags & RADIX_TREE_ITER_CONTIG) return NULL ; if (flags & RADIX_TREE_ITER_TAGGED) offset = radix_tree_find_next_bit(node, tag, offset + 1 ); else while (++offset < RADIX_TREE_MAP_SIZE) { void *slot = rcu_dereference_raw( node->slots[offset]); if (slot) break ; } index &= ~node_maxindex(node); index += offset << node->shift; if (!index) return NULL ; if (offset == RADIX_TREE_MAP_SIZE) goto restart; child = rcu_dereference_raw(node->slots[offset]); } if (!child) goto restart; if (child == RADIX_TREE_RETRY) break ; } while (node->shift && radix_tree_is_internal_node(child)); iter->index = (index &~ node_maxindex(node)) | offset; iter->next_index = (index | node_maxindex(node)) + 1 ; iter->node = node; if (flags & RADIX_TREE_ITER_TAGGED) set_iter_tags(iter, node, offset, tag); return node->slots + offset; } EXPORT_SYMBOL(radix_tree_next_chunk);
radix_tree_for_each_slot 迭代非空槽位 1 2 3 4 5 6 7 8 #define radix_tree_for_each_slot(slot, root, iter, start) \ for (slot = radix_tree_iter_init(iter, start) ; \ slot || (slot = radix_tree_next_chunk(root, iter, 0)) ; \ slot = radix_tree_next_slot(slot, iter, 0))
radix_tree_lookup 在基数树中查找项目 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 void *__radix_tree_lookup(const struct radix_tree_root *root, unsigned long index, struct radix_tree_node **nodep, void __rcu ***slotp) { struct radix_tree_node *node , *parent ; unsigned long maxindex; void __rcu **slot; restart: parent = NULL ; slot = (void __rcu **)&root->xa_head; radix_tree_load_root(root, &node, &maxindex); if (index > maxindex) return NULL ; while (radix_tree_is_internal_node(node)) { unsigned offset; parent = entry_to_node(node); offset = radix_tree_descend(parent, &node, index); slot = parent->slots + offset; if (node == RADIX_TREE_RETRY) goto restart; if (parent->shift == 0 ) break ; } if (nodep) *nodep = parent; if (slotp) *slotp = slot; return node; } void *radix_tree_lookup (const struct radix_tree_root *root, unsigned long index) { return __radix_tree_lookup(root, index, NULL , NULL ); } EXPORT_SYMBOL(radix_tree_lookup);
radix_tree_delete_item 删除基数树中的项目 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 static bool __radix_tree_delete(struct radix_tree_root *root, struct radix_tree_node *node, void __rcu **slot) { void *old = rcu_dereference_raw(*slot); int values = xa_is_value(old) ? -1 : 0 ; unsigned offset = get_slot_offset(node, slot); int tag; if (is_idr(root)) node_tag_set(root, node, IDR_FREE, offset); else for (tag = 0 ; tag < RADIX_TREE_MAX_TAGS; tag++) node_tag_clear(root, node, tag, offset); replace_slot(slot, NULL , node, -1 , values); return node && delete_node(root, node); } void *radix_tree_delete_item (struct radix_tree_root *root, unsigned long index, void *item) { struct radix_tree_node *node = NULL ; void __rcu **slot = NULL ; void *entry; entry = __radix_tree_lookup(root, index, &node, &slot); if (!slot) return NULL ; if (!entry && (!is_idr(root) || node_tag_get(root, node, IDR_FREE, get_slot_offset(node, slot)))) return NULL ; if (item && entry != item) return NULL ; __radix_tree_delete(root, node, slot); return entry; } EXPORT_SYMBOL(radix_tree_delete_item);
lib/radix-tree.c radix_tree_init radix_tree_node 缓存池的初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static void radix_tree_node_ctor (void *arg) { struct radix_tree_node *node = arg; memset (node, 0 , sizeof (*node)); INIT_LIST_HEAD(&node->private_list); } static int radix_tree_cpu_dead (unsigned int cpu) { struct radix_tree_preload *rtp ; struct radix_tree_node *node ; rtp = &per_cpu(radix_tree_preloads, cpu); while (rtp->nr) { node = rtp->nodes; rtp->nodes = node->parent; kmem_cache_free(radix_tree_node_cachep, node); rtp->nr--; } return 0 ; } void __init radix_tree_init (void ) { int ret; BUILD_BUG_ON(RADIX_TREE_MAX_TAGS + __GFP_BITS_SHIFT > 32 ); BUILD_BUG_ON(ROOT_IS_IDR & ~GFP_ZONEMASK); BUILD_BUG_ON(XA_CHUNK_SIZE > 255 ); radix_tree_node_cachep = kmem_cache_create("radix_tree_node" , sizeof (struct radix_tree_node), 0 , SLAB_PANIC | SLAB_RECLAIM_ACCOUNT, radix_tree_node_ctor); ret = cpuhp_setup_state_nocalls(CPUHP_RADIX_DEAD, "lib/radix:dead" , NULL , radix_tree_cpu_dead); WARN_ON(ret < 0 ); }
__radix_tree_preload 用于预加载当前 CPU 的 radix_tree_node 缓冲区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 DEFINE_PER_CPU(struct radix_tree_preload, radix_tree_preloads) = { .lock = INIT_LOCAL_LOCK(lock), }; EXPORT_PER_CPU_SYMBOL_GPL(radix_tree_preloads); static __must_check int __radix_tree_preload(gfp_t gfp_mask, unsigned nr){ struct radix_tree_preload *rtp ; struct radix_tree_node *node ; int ret = -ENOMEM; gfp_mask &= ~__GFP_ACCOUNT; local_lock(&radix_tree_preloads.lock); rtp = this_cpu_ptr(&radix_tree_preloads); while (rtp->nr < nr) { local_unlock(&radix_tree_preloads.lock); node = kmem_cache_alloc(radix_tree_node_cachep, gfp_mask); if (node == NULL ) goto out; local_lock(&radix_tree_preloads.lock); rtp = this_cpu_ptr(&radix_tree_preloads); if (rtp->nr < nr) { node->parent = rtp->nodes; rtp->nodes = node; rtp->nr++; } else { kmem_cache_free(radix_tree_node_cachep, node); } } ret = 0 ; out: return ret; }
idr_preload idr_alloc() 的预加载 1 2 3 4 5 6 7 8 9 10 11 12 13 void idr_preload (gfp_t gfp_mask) { if (__radix_tree_preload(gfp_mask, IDR_PRELOAD_SIZE)) local_lock(&radix_tree_preloads.lock); } EXPORT_SYMBOL(idr_preload);
radix_tree_insert: 向基数树中插入一个条目 此函数是Linux内核基数树(Radix Tree)数据结构的核心API之一。它的作用是将一个”条目”(item
, 通常是一个指针)插入到基数树中, 并与一个无符号长整型的”索引”(index
)关联起来。
其核心原理是: 将index
值的二进制位作为路径, 在树中进行遍历。从高位到低位, index
的每若干位被用来决定在树的当前节点中选择哪个分支(槽, slot)进入下一层。
路径查找与创建 : 函数首先调用内部辅助函数__radix_tree_create
。这个辅助函数会沿着index
指定的路径在树中行进。如果路径上的任何一个中间节点(radix_tree_node
)不存在, 它会自动分配内存并创建这个节点, 从而确保路径是完整的。最终, 它会返回一个指向目标槽位(slot
)的指针。
条目放置 : 找到目标槽位后, 它会将用户提供的item
指针存入该位置。
状态断言 : 在插入成功后, 它会执行一系列BUG_ON
断言, 检查与新插入条目关联的”标签(tags)”是否都为0。标签是基数树提供的一种额外机制, 用于标记条目的状态(例如”脏”、”回写”等)。此处的断言确保了一个新条目在初始时没有任何状态标签, 这是一个重要的完整性校验。
在STM32H750这样的嵌入式系统中, pinctrl
驱动程序正是利用基数树来管理其所有的物理引脚。它使用引脚的编号(一个整数)作为index
, 将指向内核内部引脚描述符(pin_desc
)的指针作为item
。这提供了一种非常高效的方式来根据引脚编号快速查找到其详细信息, 即使引脚编号是稀疏的(例如, 一个64引脚的芯片可能只使用了0-63范围内的部分编号), 基数树也能有效地利用内存。当__radix_tree_create
需要分配新节点时, 它会使用GFP_KERNEL
, 这在单核抢占式系统上是安全的, 意味着内存分配过程在需要时可以休眠。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 int radix_tree_insert (struct radix_tree_root *root, unsigned long index, void *item) { struct radix_tree_node *node ; void __rcu **slot; int error; BUG_ON(radix_tree_is_internal_node(item)); error = __radix_tree_create(root, index, &node, &slot); if (error) return error; error = insert_entries(node, slot, item); if (error < 0 ) return error; if (node) { unsigned offset = get_slot_offset(node, slot); BUG_ON(tag_get(node, 0 , offset)); BUG_ON(tag_get(node, 1 , offset)); BUG_ON(tag_get(node, 2 , offset)); } else { BUG_ON(root_tags_get(root)); } return 0 ; } EXPORT_SYMBOL(radix_tree_insert);