[TOC]
lib/idr.c IDR/IDA机制(ID-to-Pointer/ID Allocator) 内核对象ID的分配与管理 历史与背景 这项技术是为了解决什么特定问题而诞生的? lib/idr.c
中的IDR(ID Radix Tree)和IDA(ID Allocator)机制是为了解决一个在内核中普遍存在的问题:如何为一个内核对象动态地分配一个唯一的、通常是小整数的ID,并能通过这个ID快速地反向查找到该对象 。
这解决了以下几个关键痛点:
需要稳定的“句柄” :内核中的许多对象(如设备、定时器)需要被用户空间或其他子系统通过一个简单的整数ID来引用,这个ID就像一个“句柄”。直接暴露内核指针既不安全(违反KASLR)也不稳定(对象可能被重新分配)。
稀疏ID的空间效率 :如果要支持的ID范围很大(例如0到INT_MAX
),但实际同时存在的对象数量却相对较少,使用一个简单的指针数组(void *pointers[INT_MAX]
)将会造成巨大的内存浪费。IDR/IDA需要一种空间高效的方式来管理这种“稀疏”的ID分配。
ID的循环使用 :当一个对象被销毁后,它所占用的ID应该能被回收并重新分配给新的对象,以保持ID的紧凑性。
简化ID分配逻辑 :在没有通用框架之前,需要此功能的各个子系统都必须自己实现一套ID分配和管理的逻辑,导致代码重复和潜在的错误。
它的发展经历了哪些重要的里程碑或版本迭代? IDR/IDA的发展是一个功能提炼和分离的过程:
IDR的诞生 :最初,内核引入了IDR(ID Radix Tree)。它将两个功能合二为一:分配一个新的、未被使用的整数ID,并将这个ID与一个指针关联起来。它的底层实现是基于基数树(Radix Tree),这使得它在处理稀疏ID时非常节省空间。
IDA的提炼 :开发者们发现,在很多场景下,他们只需要分配一个唯一的整数ID ,而不需要存储与之关联的指针 。例如,仅仅需要一个唯一的标签号。为了满足这种更轻量级的需求,IDA(ID Allocator)被从IDR的逻辑中提炼出来。IDA只负责分配和回收ID,其内部实现使用基数树来跟踪哪些ID是空闲的,因此比IDR更简单、开销更小。
IDR基于IDA的重构 :在现代内核中,IDR的实现通常被重构为在内部使用一个IDA来处理ID的分配和回收,然后再额外负责管理ID到指针的映射。这种分层使得代码更加清晰和模块化。
目前该技术的社区活跃度和主流应用情况如何? IDR/IDA是内核中一个非常核心、稳定且被广泛使用的基础库。它不是一个试验性功能,而是许多关键子系统的基石。
cdev (字符设备) :使用IDR机制来管理次设备号,并将它们映射到对应的字符设备结构。
POSIX Timers :当用户空间创建一个POSIX定时器时,内核使用IDR来分配一个timer_t
ID,并将其映射到内部的k_itimer
结构。
I/O子系统 :一些I/O调度器和块设备驱动使用IDA来分配请求标签(request tags)。
DRM/GPU驱动 :用于管理图形内存对象或其他硬件资源的句柄。
InfiniBand和网络驱动 :用于管理各种资源的标识符。
核心原理与设计 它的核心工作原理是什么? IDR/IDA的核心数据结构是基数树(Radix Tree) ,一种针对整数键进行优化的、空间效率极高的前缀树。
它的主要优势体现在哪些方面?
空间效率 :对于稀疏的ID分布,基数树的内存占用远小于简单的数组。
性能高效 :查找、插入和删除操作的时间复杂度为O(log N),更准确地说是O(k),其中k是ID的位数。这与已分配ID的总数无关,具有很好的可扩展性。
ID分配策略 :能自动找到并分配当前未被使用的最小ID,有助于保持ID的紧凑性。
线程安全 :IDR/IDA的所有操作都由内部的自旋锁保护,是线程安全的。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
锁竞争 :IDR/IDA使用一个单一的自旋锁来保护其内部数据结构。如果在性能要求极高的场景下,有大量的CPU核心同时、高频率地进行ID的分配和释放,这个锁可能会成为一个性能瓶颈。
复杂性 :相比于简单的数组或位图,基数树的实现更复杂,有一定的理解和维护成本。
ID范围限制 :ID的最大值受到基数树深度的限制,通常是30位或31位,即最大ID不能超过INT_MAX
。
使用场景 在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。 当需要在内核中实现一个句柄(Handle)系统 时,IDR是首选解决方案。即,你需要:
生成 一个唯一的整数ID。
将这个ID关联 到一个内核对象指针。
在未来的某个时刻,能通过这个ID快速找回 这个指针。
例一:USB设备驱动 一个复杂的USB设备可能有多个逻辑单元(例如,一个U盘+读卡器设备)。驱动程序需要为每个逻辑单元分配一个唯一的ID,以便上层协议栈可以通过这个ID来寻址。驱动可以使用IDR,将生成的ID(如0, 1, 2…)映射到代表每个逻辑单元的内部数据结构指针。
例二:cdev实现 内核的cdev
子系统需要管理系统中所有的字符设备。当一个驱动调用cdev_add()
注册一个字符设备时,cdev
子系统需要将设备号dev_t
和一个struct cdev
对象关联起来。IDR是实现这个映射的理想工具。
如果只需要分配唯一ID而不需要存储指针 ,那么更轻量级的IDA 是首选。
例三:I/O请求标签 一个NVMe驱动需要为每个发往硬件的命令分配一个唯一的标签号(Tag),以便在命令完成时能识别是哪个命令。驱动可以使用IDA来快速获取一个未被使用的Tag号。当命令完成后,再用IDA释放这个Tag号。
是否有不推荐使用该技术的场景?为什么?
键不是整数或范围很大 :IDR/IDA的键是整数。如果你需要用字符串或其他复杂结构作为键来查找对象,应该使用哈希表。
ID是连续且密集的 :如果你需要分配的ID总是从0开始,连续递增,且最大值不大(例如0-255),那么使用一个简单的指针数组 (void *my_pointers[256]
) 会比IDR更简单、更快。
只需要一个简单的位图 :如果你只是想从一个小的、固定的范围内分配ID(例如,管理32个可用的资源槽),使用内核的位图(bitmap)API会比IDA更直接、开-销更小。
对比分析 请将其 与 其他相似技术 进行详细对比。
特性
IDR/IDA
哈希表 (Hash Table)
位图 (Bitmap)
简单数组 (Simple Array)
核心功能
分配 并(对于IDR)映射 稀疏的整数ID。
映射 一个已存在 的键到一个值。
分配 来自一个固定、密集 范围的ID。
映射 一个密集 的整数索引到一个值。
键/ID的来源
由机制本身生成 。
由调用者提供 。
由机制本身生成 (返回第一个空闲位的索引)。
由调用者提供 (作为数组索引)。
空间效率
高 (对稀疏ID友好)。
中等 。
低 (对稀疏ID非常浪费)。
极低 (对稀疏ID完全不可行)。
查找性能
O(k) (k为ID位数)。
平均 O(1)。
O(n) (查找一个空闲位)。
O(1)。
分配性能
O(k) (k为ID位数)。
不适用(不分配ID)。
O(n) (查找一个空-闲位)。
不适用(不分配ID)。
典型用途
内核句柄系统(cdev, POSIX timers),唯一标签生成。
快速查找(dcache, PID表)。
管理固定数量的资源槽。
管理固定、小范围、密集的对象(如中断向量表)。
关键区别
“请给我一个空闲的ID,并帮我记住它指向谁”
“帮我记住这个键对应这个值”
“请告诉我这个范围里哪个位置是空的”
“请把这个值存在第N个位置”
include/linux/idr.h DEFINE_IDR 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 #define IDR_FREE 0 #define IDR_RT_MARKER (ROOT_IS_IDR | (__force gfp_t) \ (1 << (ROOT_TAG_SHIFT + IDR_FREE))) #define IDR_INIT_BASE(name, base) { \ .idr_rt = RADIX_TREE_INIT(name, IDR_RT_MARKER), \ .idr_base = (base), \ .idr_next = 0, \ } #define IDR_INIT(name) IDR_INIT_BASE(name, 0) #define DEFINE_IDR(name) struct idr name = IDR_INIT(name)
idr_for_each_entry 遍历给定类型的IDR元素. 1 2 3 4 5 6 7 8 9 10 #define idr_for_each_entry(idr, entry, id) \ for (id = 0; ((entry) = idr_get_next(idr, &(id))) != NULL; id += 1U)
idr_init_base 初始化 IDR 1 2 3 4 5 6 7 8 9 10 11 12 13 static inline void idr_init_base (struct idr *idr, int base) { INIT_RADIX_TREE(&idr->idr_rt, IDR_RT_MARKER); idr->idr_base = base; idr->idr_next = 0 ; }
idr_init - 初始化 IDR 1 2 3 4 5 6 7 8 9 10 static inline void idr_init (struct idr *idr) { idr_init_base(idr, 0 ); }
lib/idr.c idr_get_next_ul 查找下一个已填充的条目(即非空条目) 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 void *idr_get_next_ul (struct idr *idr, unsigned long *nextid) { struct radix_tree_iter iter ; void __rcu **slot; void *entry = NULL ; unsigned long base = idr->idr_base; unsigned long id = *nextid; id = (id < base) ? 0 : id - base; radix_tree_for_each_slot(slot, &idr->idr_rt, &iter, id) { entry = rcu_dereference_raw(*slot); if (!entry) continue ; if (!xa_is_internal(entry)) break ; if (slot != &idr->idr_rt.xa_head && !xa_is_retry(entry)) break ; slot = radix_tree_iter_retry(&iter); } if (!slot) return NULL ; *nextid = iter.index + base; return entry; } EXPORT_SYMBOL(idr_get_next_ul);
idr_get_next 查找下一个填充的条目 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void *idr_get_next (struct idr *idr, int *nextid) { unsigned long id = *nextid; void *entry = idr_get_next_ul(idr, &id); if (WARN_ON_ONCE(id > INT_MAX)) return NULL ; *nextid = id; return entry; } EXPORT_SYMBOL(idr_get_next);
replace_slot 替换槽位 1 2 3 4 5 6 7 8 9 10 static void replace_slot (void __rcu **slot, void *item, struct radix_tree_node *node, int count, int values) { if (node && (count || values)) { node->count += count; node->nr_values += values; } rcu_assign_pointer(*slot, item); }
radix_tree_replace_slot 替换插槽中的项目 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 void __radix_tree_replace(struct radix_tree_root *root, struct radix_tree_node *node, void __rcu **slot, void *item) { void *old = rcu_dereference_raw(*slot); int values = !!xa_is_value(item) - !!xa_is_value(old); int count = calculate_count(root, node, slot, item, old); WARN_ON_ONCE(!node && (slot != (void __rcu **)&root->xa_head) && (count || values)); replace_slot(slot, item, node, count, values); if (!node) return ; delete_node(root, node); } void radix_tree_replace_slot (struct radix_tree_root *root, void __rcu **slot, void *item) { __radix_tree_replace(root, NULL , slot, item); } EXPORT_SYMBOL(radix_tree_replace_slot);
idr_alloc 分配一个ID 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 int idr_alloc_u32 (struct idr *idr, void *ptr, u32 *nextid, unsigned long max, gfp_t gfp) { struct radix_tree_iter iter ; void __rcu **slot; unsigned int base = idr->idr_base; unsigned int id = *nextid; if (WARN_ON_ONCE(!(idr->idr_rt.xa_flags & ROOT_IS_IDR))) idr->idr_rt.xa_flags |= IDR_RT_MARKER; id = (id < base) ? 0 : id - base; radix_tree_iter_init(&iter, id); slot = idr_get_free(&idr->idr_rt, &iter, gfp, max - base); if (IS_ERR(slot)) return PTR_ERR(slot); *nextid = iter.index + base; radix_tree_iter_replace(&idr->idr_rt, &iter, slot, ptr); radix_tree_iter_tag_clear(&idr->idr_rt, &iter, IDR_FREE); return 0 ; } EXPORT_SYMBOL_GPL(idr_alloc_u32); int idr_alloc (struct idr *idr, void *ptr, int start, int end, gfp_t gfp) { u32 id = start; int ret; if (WARN_ON_ONCE(start < 0 )) return -EINVAL; ret = idr_alloc_u32(idr, ptr, &id, end > 0 ? end - 1 : INT_MAX, gfp); if (ret) return ret; return id; } EXPORT_SYMBOL_GPL(idr_alloc);
idr_find 查找ID 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void *idr_find (const struct idr *idr, unsigned long id) { return radix_tree_lookup(&idr->idr_rt, id - idr->idr_base); } EXPORT_SYMBOL_GPL(idr_find);
idr_remove 删除ID 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void *idr_remove (struct idr *idr, unsigned long id) { return radix_tree_delete_item(&idr->idr_rt, id - idr->idr_base, NULL ); } EXPORT_SYMBOL_GPL(idr_remove);
ida_free 释放ID 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 void ida_free (struct ida *ida, unsigned int id) { XA_STATE(xas, &ida->xa, id / IDA_BITMAP_BITS); unsigned bit = id % IDA_BITMAP_BITS; struct ida_bitmap *bitmap ; unsigned long flags; if ((int )id < 0 ) return ; xas_lock_irqsave(&xas, flags); bitmap = xas_load(&xas); if (xa_is_value(bitmap)) { unsigned long v = xa_to_value(bitmap); if (bit >= BITS_PER_XA_VALUE) goto err; if (!(v & (1UL << bit))) goto err; v &= ~(1UL << bit); if (!v) goto delete; xas_store(&xas, xa_mk_value(v)); } else { if (!bitmap || !test_bit(bit, bitmap->bitmap)) goto err; __clear_bit(bit, bitmap->bitmap); xas_set_mark(&xas, XA_FREE_MARK); if (bitmap_empty(bitmap->bitmap, IDA_BITMAP_BITS)) { kfree(bitmap); delete: xas_store(&xas, NULL ); } } xas_unlock_irqrestore(&xas, flags); return ; err: xas_unlock_irqrestore(&xas, flags); WARN(1 , "ida_free called for id=%d which is not allocated.\n" , id); } EXPORT_SYMBOL(ida_free);
idr_alloc_cyclic 在指定范围内循环分配唯一的 ID 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 int idr_alloc_cyclic (struct idr *idr, void *ptr, int start, int end, gfp_t gfp) { u32 id = idr->idr_next; int err, max = end > 0 ? end - 1 : INT_MAX; if ((int )id < start) id = start; err = idr_alloc_u32(idr, ptr, &id, max, gfp); if ((err == -ENOSPC) && (id > start)) { id = start; err = idr_alloc_u32(idr, ptr, &id, max, gfp); } if (err) return err; idr->idr_next = id + 1 ; return id; } EXPORT_SYMBOL(idr_alloc_cyclic);