文章目录
- 链表
- 双向链表
- 哈希链表
- 红黑树
- 无锁环形缓冲区
- 映射
- 参考
内核版本:linux-6.6.69 longterm
链表
双向链表
Linux内核实现了一套纯链表的封装,链表节点数据结构只有指针区而没有数据区,另外还封装了各种操作函数,如创建节点函数、插入节点函数、删除节点函数、遍历节点函数等。
linux 内核中的链表在文件include/linux/types.h
中定义类型,在include/linux/list.h
中定义操作。
Linux内核链表使用struct list_head数据结构来描述。
// include/linux/types.h
struct list_head {struct list_head *next, *prev;
};
struct list_head数据结构不包含链表节点的数据区,通常是嵌入其他数据结构。
链表头的初始化有两种方法,一种是静态初始化,另一种动态初始化。把next和prev指针都初始化并指向自己,这样便初始化了一个带头节点的空链表。
// include/linux/list.h
#define LIST_HEAD_INIT(name) { &(name), &(name) }#define LIST_HEAD(name) \struct list_head name = LIST_HEAD_INIT(name)/*** INIT_LIST_HEAD - Initialize a list_head structure* @list: list_head structure to be initialized.** Initializes the list_head to point to itself. If it is a list header,* the result is an empty list.*/
static inline void INIT_LIST_HEAD(struct list_head *list)
{WRITE_ONCE(list->next, list);WRITE_ONCE(list->prev, list);
}
添加节点到一个链表中,内核提供了几个接口函数,如list_add()是把一个节点添加到表头,list_add_tail()是插入表尾。
// include/linux/list.h
/** Insert a new entry between two known consecutive entries.** This is only for internal list manipulation where we know* the prev/next entries already!*/
static inline void __list_add(struct list_head *new,struct list_head *prev,struct list_head *next)
{if (!__list_add_valid(new, prev, next))return;next->prev = new;new->next = next;new->prev = prev;WRITE_ONCE(prev->next, new);
}/*** list_add - add a new entry* @new: new entry to be added* @head: list head to add it after** Insert a new entry after the specified head.* This is good for implementing stacks.*/
static inline void list_add(struct list_head *new, struct list_head *head)
{__list_add(new, head, head->next);
}/*** list_add_tail - add a new entry* @new: new entry to be added* @head: list head to add it before** Insert a new entry before the specified head.* This is useful for implementing queues.*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{__list_add(new, head->prev, head);
}
遍历节点的接口函数。
// include/linux/list.h
/*** list_for_each - iterate over a list* @pos: the &struct list_head to use as a loop cursor.* @head: the head for your list.*/
#define list_for_each(pos, head) \for (pos = (head)->next; !list_is_head(pos, (head)); pos = pos->next)
这个宏只是遍历一个一个节点的当前位置,那么如何获取节点本身的数据结构呢?这里还需要使用list_entry()宏。
// include/linux/list.h
/*** list_entry - get the struct for this entry* @ptr: the &struct list_head pointer.* @type: the type of the struct this is embedded in.* @member: the name of the list_head within the struct.*/
#define list_entry(ptr, type, member) \container_of(ptr, type, member)
container_of也是一个基本的宏
// include/linux/container_of.h
/*** container_of - cast a member of a structure out to the containing structure* @ptr: the pointer to the member.* @type: the type of the container struct this is embedded in.* @member: the name of the member within the struct.** WARNING: any const qualifier of @ptr is lost.*/
#define container_of(ptr, type, member) ({ \void *__mptr = (void *)(ptr); \static_assert(__same_type(*(ptr), ((type *)0)->member) || \__same_type(*(ptr), void), \"pointer type mismatch in container_of()"); \((type *)(__mptr - offsetof(type, member))); })
其中offsetof()宏是通过把0地址转换为type类型的指针,然后去获取该结构体中member成员的指针,也就是获取了member在type结构体中的偏移量。最后用指针ptr减去offset,就得到type结构体的真实地址了。
哈希链表
哈希链表(Hash List)是指在对需要存储的数据进行hash时,如果产生了冲突,就使用链表的方式将产生冲突的数据进行存储。通常情况下,哈希表中元素的使用顺序是:数据存储–>数据获取–>数据删除。
内核中哈希链表在include/linux/list.h
中实现。
Linux内核链表使用struct hlist_head数据结构来描述哈希表,使用struct hlist_node数据结构来描述链表结构。代码位于include/linux/types.h
文件中。
struct hlist_head {struct hlist_node *first;
};struct hlist_node {struct hlist_node *next, **pprev;
};
pprev二级指针存在的意义是因为效率。向链表中添加节点时可以直接添加到头部,但是当删除节点时,不但要记录即将删除节点的前一个节点,还需要判断删除的节点是否是头结点。如果使用pprev指针,那么在删除节点时直接使用*(del_node->pprev) = del_node->next
即可。
哈希链表初始化
哈希链表初始化分为初始化hlist_head的节点和初始化hlist_node节点。
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
static inline void INIT_HLIST_NODE(struct hlist_node *h)
{h->next = NULL;h->pprev = NULL;
}
添加节点
添加节点函数有三个,分别是添加到指定哈希链表头上hlist_add_head()、添加到指定节点前面hlist_add_before()、添加到指定节点后面hlist_add_behind()。
/*** hlist_add_head - add a new entry at the beginning of the hlist* @n: new entry to be added* @h: hlist head to add it after** Insert a new entry after the specified head.* This is good for implementing stacks.*/
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{struct hlist_node *first = h->first;WRITE_ONCE(n->next, first);if (first)WRITE_ONCE(first->pprev, &n->next);WRITE_ONCE(h->first, n);WRITE_ONCE(n->pprev, &h->first);
}/*** hlist_add_before - add a new entry before the one specified* @n: new entry to be added* @next: hlist node to add it before, which must be non-NULL*/
static inline void hlist_add_before(struct hlist_node *n,struct hlist_node *next)
{WRITE_ONCE(n->pprev, next->pprev);WRITE_ONCE(n->next, next);WRITE_ONCE(next->pprev, &n->next);WRITE_ONCE(*(n->pprev), n);
}/*** hlist_add_behind - add a new entry after the one specified* @n: new entry to be added* @prev: hlist node to add it after, which must be non-NULL*/
static inline void hlist_add_behind(struct hlist_node *n,struct hlist_node *prev)
{WRITE_ONCE(n->next, prev->next);WRITE_ONCE(prev->next, n);WRITE_ONCE(n->pprev, &prev->next);if (n->next)WRITE_ONCE(n->next->pprev, &n->next);
}
删除节点
删除节点函数有两个,分别是删除节点hlist_del()、删除节点并初始化已删除的节点hlist_del_init()。
static inline void __hlist_del(struct hlist_node *n)
{struct hlist_node *next = n->next;struct hlist_node **pprev = n->pprev;WRITE_ONCE(*pprev, next);if (next)WRITE_ONCE(next->pprev, pprev);
}/*** hlist_del - Delete the specified hlist_node from its list* @n: Node to delete.** Note that this function leaves the node in hashed state. Use* hlist_del_init() or similar instead to unhash @n.*/
static inline void hlist_del(struct hlist_node *n)
{__hlist_del(n);n->next = LIST_POISON1;n->pprev = LIST_POISON2;
}/*** hlist_del_init - Delete the specified hlist_node from its list and initialize* @n: Node to delete.** Note that this function leaves the node in unhashed state.*/
static inline void hlist_del_init(struct hlist_node *n)
{if (!hlist_unhashed(n)) {__hlist_del(n);INIT_HLIST_NODE(n);}
}
移动节点
将哈希链表从一个移动到另外一个。
/*** hlist_move_list - Move an hlist* @old: hlist_head for old list.* @new: hlist_head for new list.** Move a list from one list head to another. Fixup the pprev* reference of the first entry if it exists.*/
static inline void hlist_move_list(struct hlist_head *old,struct hlist_head *new)
{new->first = old->first;if (new->first)new->first->pprev = &new->first;old->first = NULL;
}
遍历哈希链表
#define hlist_for_each(pos, head) \for (pos = (head)->first; pos ; pos = pos->next)
这个宏只是遍历一个一个节点的当前位置,那么如何获取节点本身的数据结构呢?这里还需要使用hlist_entry()宏。
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
红黑树
红黑树(Red Black Tree)被广泛应用在内核的内存管理和进程调度中,用于将排序的元素组织到树中。红黑树被广泛应用在计算机科学的各个领域中,它在速度和实现复杂度之间提供一个很好的平衡。
红黑树是具有以下特征的二叉树。
- 每个节点或红或黑。
- 每个叶节点是黑色的。
- 如果结点都是红色,那么两个子结点都是黑色。
- 从一个内部结点到叶结点的简单路径上,对所有叶节点来说,黑色结点的数目都是相同的。
红黑树的一个优点是,所有重要的操作(例如插入、删除、搜索)都可以在O(log n)时间内完成,n为树中元素的数目。更详细的介绍参考:https://en.wikipedia.org/wiki/Red%E2%80%93black_tree
在linux内核中,红黑树在include/linux/rbtree.h
和lib/rbtree.c
中定义和实现,有关内核中实现文档参考:Documentation/core-api/rbtree.rst
m,关于如何使用内核中的红黑树,文档里也给了详细的例子。
无锁环形缓冲区
生产者和消费者模型是计算机编程中最常见的一种模型。生产者产生数据,而消费者消耗数据,如一个网络设备,硬件设备接收网络包,然后应用程序读取网络包。环形缓冲区是实现生产者和消费者模型的经典算法。环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区可写的数据。通过移动读指针和写指针实现缓冲区数据的读取和写入。
在Linux内核中,KFIFO是采用无锁环形缓冲区的实现。FIFO的全称是“First In First Out”,即先进先出的数据结构,它采用环形缓冲区的方法来实现,并提供一个无边界的字节流服务。采用环形缓冲区的好处是,当一个数据元素被消耗之后,其余数据元素不需要移动其存储位置,从而减少复制,提高效率。
在linux内核中,无锁环形缓冲区在文件include/linux/kfifo.h
和lib/kfifo.c
中定义和实现。
- 创建KFIFO
在使用KFIFO之前需要进行初始化,这里有静态初始化和动态初始化两种方式。
/*** kfifo_alloc - dynamically allocates a new fifo buffer* @fifo: pointer to the fifo* @size: the number of elements in the fifo, this must be a power of 2* @gfp_mask: get_free_pages mask, passed to kmalloc()** This macro dynamically allocates a new fifo buffer.** The number of elements will be rounded-up to a power of 2.* The fifo will be release with kfifo_free().* Return 0 if no error, otherwise an error code.*/
#define kfifo_alloc(fifo, size, gfp_mask) \
__kfifo_int_must_check_helper( \
({ \typeof((fifo) + 1) __tmp = (fifo); \struct __kfifo *__kfifo = &__tmp->kfifo; \__is_kfifo_ptr(__tmp) ? \__kfifo_alloc(__kfifo, size, sizeof(*__tmp->type), gfp_mask) : \-EINVAL; \
}) \
)
该函数创建并分配一个大小为size的KFIFO环形缓冲区。第一个参数fifo是指向该环形缓冲区的struct kfifo数据结构;第二个参数size是指定缓冲区元素的数量;第三个参数gfp_mask表示分配KFIFO元素使用的分配掩码。
静态分配可以使用如下的宏。
/*** INIT_KFIFO - Initialize a fifo declared by DECLARE_KFIFO* @fifo: name of the declared fifo datatype*/
#define INIT_KFIFO(fifo) \
(void)({ \typeof(&(fifo)) __tmp = &(fifo); \struct __kfifo *__kfifo = &__tmp->kfifo; \__kfifo->in = 0; \__kfifo->out = 0; \__kfifo->mask = __is_kfifo_ptr(__tmp) ? 0 : ARRAY_SIZE(__tmp->buf) - 1;\__kfifo->esize = sizeof(*__tmp->buf); \__kfifo->data = __is_kfifo_ptr(__tmp) ? NULL : __tmp->buf; \
})/*** DEFINE_KFIFO - macro to define and initialize a fifo* @fifo: name of the declared fifo datatype* @type: type of the fifo elements* @size: the number of elements in the fifo, this must be a power of 2** Note: the macro can be used for global and local fifo data type variables.*/
#define DEFINE_KFIFO(fifo, type, size) \DECLARE_KFIFO(fifo, type, size) = \(typeof(fifo)) { \{ \{ \.in = 0, \.out = 0, \.mask = __is_kfifo_ptr(&(fifo)) ? \0 : \ARRAY_SIZE((fifo).buf) - 1, \.esize = sizeof(*(fifo).buf), \.data = __is_kfifo_ptr(&(fifo)) ? \NULL : \(fifo).buf, \} \} \}
- 入列
把数据写入KFIFO环形缓冲区可以使用kfifo_in()函数接口。
/*** kfifo_in - put data into the fifo* @fifo: address of the fifo to be used* @buf: the data to be added* @n: number of elements to be added** This macro copies the given buffer into the fifo and returns the* number of copied elements.** Note that with only one concurrent reader and one concurrent* writer, you don't need extra locking to use these macro.*/
#define kfifo_in(fifo, buf, n) \
({ \typeof((fifo) + 1) __tmp = (fifo); \typeof(__tmp->ptr_const) __buf = (buf); \unsigned long __n = (n); \const size_t __recsize = sizeof(*__tmp->rectype); \struct __kfifo *__kfifo = &__tmp->kfifo; \(__recsize) ?\__kfifo_in_r(__kfifo, __buf, __n, __recsize) : \__kfifo_in(__kfifo, __buf, __n); \
})
该函数把buf指针指向的n个数据复制到KFIFO环形缓冲区中。第一个参数fifo指的是KFIFO环形缓冲区;第二个参数buf指向要复制的数据的buffer;第三个数据是要复制数据元素的数量。
- 出列
从KFIFO环形缓冲区中列出或者摘取数据可以使用kfifo_out()函数接口。
/*** kfifo_out - get data from the fifo* @fifo: address of the fifo to be used* @buf: pointer to the storage buffer* @n: max. number of elements to get** This macro get some data from the fifo and return the numbers of elements* copied.** Note that with only one concurrent reader and one concurrent* writer, you don't need extra locking to use these macro.*/
#define kfifo_out(fifo, buf, n) \
__kfifo_uint_must_check_helper( \
({ \typeof((fifo) + 1) __tmp = (fifo); \typeof(__tmp->ptr) __buf = (buf); \unsigned long __n = (n); \const size_t __recsize = sizeof(*__tmp->rectype); \struct __kfifo *__kfifo = &__tmp->kfifo; \(__recsize) ?\__kfifo_out_r(__kfifo, __buf, __n, __recsize) : \__kfifo_out(__kfifo, __buf, __n); \
}) \
)
该函数是从fifo指向的环形缓冲区中复制n个数据元素到buf指向的缓冲区中。如果KFIFO环形缓冲区的数据元素小于n个,那么复制出去的数据元素小于n个。
- 获取缓冲区大小
KFIFO提供了几个接口函数来查询环形缓冲区的状态。
/*** kfifo_size - returns the size of the fifo in elements* @fifo: address of the fifo to be used*/
#define kfifo_size(fifo) ((fifo)->kfifo.mask + 1)/*** kfifo_len - returns the number of used elements in the fifo* @fifo: address of the fifo to be used*/
#define kfifo_len(fifo) \
({ \typeof((fifo) + 1) __tmpl = (fifo); \__tmpl->kfifo.in - __tmpl->kfifo.out; \
})/*** kfifo_is_empty - returns true if the fifo is empty* @fifo: address of the fifo to be used*/
#define kfifo_is_empty(fifo) \
({ \typeof((fifo) + 1) __tmpq = (fifo); \__tmpq->kfifo.in == __tmpq->kfifo.out; \
})/*** kfifo_is_full - returns true if the fifo is full* @fifo: address of the fifo to be used*/
#define kfifo_is_full(fifo) \
({ \typeof((fifo) + 1) __tmpq = (fifo); \kfifo_len(__tmpq) > __tmpq->kfifo.mask; \
})
kfifo_size()用来获取环形缓冲区的大小,也就是最大可以容纳多少个数据元素。kfifo_len()用来获取当前环形缓冲区中有多少个有效数据元素。kfifo_is_empty()判断环形缓冲区是否为空。kfifo_is_full()判断环形缓冲区是否为满。
- 与用户空间数据交互
KFIFO还封装了两个函数与用户空间数据交互。
/*** kfifo_from_user - puts some data from user space into the fifo* @fifo: address of the fifo to be used* @from: pointer to the data to be added* @len: the length of the data to be added* @copied: pointer to output variable to store the number of copied bytes** This macro copies at most @len bytes from the @from into the* fifo, depending of the available space and returns -EFAULT/0.** Note that with only one concurrent reader and one concurrent* writer, you don't need extra locking to use these macro.*/
#define kfifo_from_user(fifo, from, len, copied) \
__kfifo_uint_must_check_helper( \
({ \typeof((fifo) + 1) __tmp = (fifo); \const void __user *__from = (from); \unsigned int __len = (len); \unsigned int *__copied = (copied); \const size_t __recsize = sizeof(*__tmp->rectype); \struct __kfifo *__kfifo = &__tmp->kfifo; \(__recsize) ? \__kfifo_from_user_r(__kfifo, __from, __len, __copied, __recsize) : \__kfifo_from_user(__kfifo, __from, __len, __copied); \
}) \
)/*** kfifo_to_user - copies data from the fifo into user space* @fifo: address of the fifo to be used* @to: where the data must be copied* @len: the size of the destination buffer* @copied: pointer to output variable to store the number of copied bytes** This macro copies at most @len bytes from the fifo into the* @to buffer and returns -EFAULT/0.** Note that with only one concurrent reader and one concurrent* writer, you don't need extra locking to use these macro.*/
#define kfifo_to_user(fifo, to, len, copied) \
__kfifo_int_must_check_helper( \
({ \typeof((fifo) + 1) __tmp = (fifo); \void __user *__to = (to); \unsigned int __len = (len); \unsigned int *__copied = (copied); \const size_t __recsize = sizeof(*__tmp->rectype); \struct __kfifo *__kfifo = &__tmp->kfifo; \(__recsize) ? \__kfifo_to_user_r(__kfifo, __to, __len, __copied, __recsize) : \__kfifo_to_user(__kfifo, __to, __len, __copied); \
}) \
)
kfifo_from_user()是把from指向的用户空间的len个数据元素复制到KFIFO中,最后一个参数copied表示成功复制了几个数据元素。kfifo_to_user()则相反,把KFIFO的数据元素复制到用户空间。这两个宏结合了copy_to_user()、copy_from_user()以及KFIFO的机制,给驱动开发者提供了方便。
映射
在Linux中,IDR是一个Small id to pointer translation service,用于管理整数ID,将整数和指针映射。使用的时候首先为一个数据结构的指针分配一个整数ID,接下来通过ID可以快速查找对应的指针。
数组和链表也能用于这样的转换,但是数组不能用于查询范围很大的情况,链表的迭代效率很低,因此不能用于映射量很大的情况。某些情况下可以用hash表来替代IDR,但是IDR相比于hash表来说不必预分配一个很大的数组,并且最坏情况要比hash表好。平衡二叉树能更好的控制最坏情况,但是IDR处理的情况比较特殊,只需要管理整数和指针,所以可以实现出比平衡二叉树更优的算法,不论在存储上还是在查询上都表现更好。 IDR也是一种radix tree,每个节点有256个分支,通过一些技巧性的位运算可以得到很高的查询效率。
内核中idr在文件include/linux/idr.h
和lib/idr.c
中定义和实现。
静态初始化接口
/*** IDR_INIT() - Initialise an IDR.* @name: Name of IDR.** A freshly-initialised IDR contains no IDs.*/
#define IDR_INIT(name) IDR_INIT_BASE(name, 0)/*** DEFINE_IDR() - Define a statically-allocated IDR.* @name: Name of IDR.** An IDR defined using this macro is ready for use with no additional* initialisation required. It contains no IDs.*/
#define DEFINE_IDR(name) struct idr name = IDR_INIT(name)
动态初始化接口
/*** idr_init_base() - Initialise an IDR.* @idr: IDR handle.* @base: The base value for the IDR.** This variation of idr_init() creates an IDR which will allocate IDs* starting at %base.*/
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() - Initialise an IDR.* @idr: IDR handle.** Initialise a dynamically allocated IDR. To initialise a* statically allocated IDR, use DEFINE_IDR().*/
static inline void idr_init(struct idr *idr)
{idr_init_base(idr, 0);
}
释放接口
/*** idr_remove() - Remove an ID from the IDR.* @idr: IDR handle.* @id: Pointer ID.** Removes this ID from the IDR. If the ID was not previously in the IDR,* this function returns %NULL.** Since this function modifies the IDR, the caller should provide their* own locking to ensure that concurrent modification of the same IDR is* not possible.** Return: The pointer formerly associated with this ID.*/
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);
// lib/radix-tree.c
/*** idr_destroy - release all internal memory from an IDR* @idr: idr handle** After this function is called, the IDR is empty, and may be reused or* the data structure containing it may be freed.** A typical clean-up sequence for objects stored in an idr tree will use* idr_for_each() to free all objects, if necessary, then idr_destroy() to* free the memory used to keep track of those objects.*/
void idr_destroy(struct idr *idr)
{struct radix_tree_node *node = rcu_dereference_raw(idr->idr_rt.xa_head);if (radix_tree_is_internal_node(node))radix_tree_free_nodes(node);idr->idr_rt.xa_head = NULL;root_tag_set(&idr->idr_rt, IDR_FREE);
}
EXPORT_SYMBOL(idr_destroy);
分配ID
// lib/radix-tree.c
/*** idr_preload - preload for idr_alloc()* @gfp_mask: allocation mask to use for preloading** Preallocate memory to use for the next call to idr_alloc(). This function* returns with preemption disabled. It will be enabled by idr_preload_end().*/
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);// lib/idr.c
/*** idr_alloc() - Allocate an ID.* @idr: IDR handle.* @ptr: Pointer to be associated with the new ID.* @start: The minimum ID (inclusive).* @end: The maximum ID (exclusive).* @gfp: Memory allocation flags.** Allocates an unused ID in the range specified by @start and @end. If* @end is <= 0, it is treated as one larger than %INT_MAX. This allows* callers to use @start + N as @end as long as N is within integer range.** The caller should provide their own locking to ensure that two* concurrent modifications to the IDR are not possible. Read-only* accesses to the IDR may be done under the RCU read lock or may* exclude simultaneous writers.** Return: The newly allocated ID, -ENOMEM if memory allocation failed,* or -ENOSPC if no free IDs could be found.*/
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);// include/linux/idr.h
/*** idr_preload_end - end preload section started with idr_preload()** Each idr_preload() should be matched with an invocation of this* function. See idr_preload() for details.*/
static inline void idr_preload_end(void)
{local_unlock(&radix_tree_preloads.lock);
}
查询迭代
// lib/idr.c
/*** idr_find() - Return pointer for given ID.* @idr: IDR handle.* @id: Pointer ID.** Looks up the pointer associated with this ID. A %NULL pointer may* indicate that @id is not allocated or that the %NULL pointer was* associated with this ID.** This function can be called under rcu_read_lock(), given that the leaf* pointers lifetimes are correctly managed.** Return: The pointer associated with this ID.*/
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_replace() - replace pointer for given ID.* @idr: IDR handle.* @ptr: New pointer to associate with the ID.* @id: ID to change.** Replace the pointer registered with an ID and return the old value.* This function can be called under the RCU read lock concurrently with* idr_alloc() and idr_remove() (as long as the ID being removed is not* the one being replaced!).** Returns: the old value on success. %-ENOENT indicates that @id was not* found. %-EINVAL indicates that @ptr was not valid.*/
void *idr_replace(struct idr *idr, void *ptr, unsigned long id)
{struct radix_tree_node *node;void __rcu **slot = NULL;void *entry;id -= idr->idr_base;entry = __radix_tree_lookup(&idr->idr_rt, id, &node, &slot);if (!slot || radix_tree_tag_get(&idr->idr_rt, id, IDR_FREE))return ERR_PTR(-ENOENT);__radix_tree_replace(&idr->idr_rt, node, slot, ptr);return entry;
}
EXPORT_SYMBOL(idr_replace);/*** idr_for_each() - Iterate through all stored pointers.* @idr: IDR handle.* @fn: Function to be called for each pointer.* @data: Data passed to callback function.** The callback function will be called for each entry in @idr, passing* the ID, the entry and @data.** If @fn returns anything other than %0, the iteration stops and that* value is returned from this function.** idr_for_each() can be called concurrently with idr_alloc() and* idr_remove() if protected by RCU. Newly added entries may not be* seen and deleted entries may be seen, but adding and removing entries* will not cause other entries to be skipped, nor spurious ones to be seen.*/
int idr_for_each(const struct idr *idr,int (*fn)(int id, void *p, void *data), void *data)
{struct radix_tree_iter iter;void __rcu **slot;int base = idr->idr_base;radix_tree_for_each_slot(slot, &idr->idr_rt, &iter, 0) {int ret;unsigned long id = iter.index + base;if (WARN_ON_ONCE(id > INT_MAX))break;ret = fn(id, rcu_dereference_raw(*slot), data);if (ret)return ret;}return 0;
}
EXPORT_SYMBOL(idr_for_each);// include/linux/idr.h
/*** idr_is_empty() - Are there any IDs allocated?* @idr: IDR handle.** Return: %true if any IDs have been allocated from this IDR.*/
static inline bool idr_is_empty(const struct idr *idr)
{return radix_tree_empty(&idr->idr_rt) &&radix_tree_tagged(&idr->idr_rt, IDR_FREE);
}
参考
Linux内核数据结构
linux
Trees I: Radix trees
Trees II: red-black trees
巧夺天工的kfifo(修订版)