Linux内核学习——数据结构

文章目录

    • 链表
      • 双向链表
      • 哈希链表
    • 红黑树
    • 无锁环形缓冲区
    • 映射
    • 参考

内核版本: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.hlib/rbtree.c中定义和实现,有关内核中实现文档参考:Documentation/core-api/rbtree.rstm,关于如何使用内核中的红黑树,文档里也给了详细的例子。

无锁环形缓冲区

生产者和消费者模型是计算机编程中最常见的一种模型。生产者产生数据,而消费者消耗数据,如一个网络设备,硬件设备接收网络包,然后应用程序读取网络包。环形缓冲区是实现生产者和消费者模型的经典算法。环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区可写的数据。通过移动读指针和写指针实现缓冲区数据的读取和写入。

在Linux内核中,KFIFO是采用无锁环形缓冲区的实现。FIFO的全称是“First In First Out”,即先进先出的数据结构,它采用环形缓冲区的方法来实现,并提供一个无边界的字节流服务。采用环形缓冲区的好处是,当一个数据元素被消耗之后,其余数据元素不需要移动其存储位置,从而减少复制,提高效率。

在linux内核中,无锁环形缓冲区在文件include/linux/kfifo.hlib/kfifo.c中定义和实现。

  1. 创建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, \} \} \}
  1. 入列
    把数据写入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;第三个数据是要复制数据元素的数量。

  1. 出列
    从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个。

  1. 获取缓冲区大小
    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()判断环形缓冲区是否为满。

  1. 与用户空间数据交互
    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.hlib/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(修订版)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/66424.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

初学Linux电源管理

学习文档出处&#xff1a; 万字整理 | 深入理解Linux电源管理&#xff1a;万字整理 | 深入理解Linux电源管理-CSDN博客 电源管理 因为设备需要用电&#xff0c;而且设备中的各个硬件所需要的电压是不一样的。故计算机需要对硬件的电源状态管理。但是电能并不是免费的&#x…

基于开发/发布/缺陷分离模型的 Git 分支管理实践20250103

基于开发/发布/缺陷分离模型的 Git 分支管理实践 引言 在现代软件开发中&#xff0c;合理的分支管理策略是保证项目成功的关键因素之一。本文将详细介绍一种基于开发/发布/缺陷分离的 Git 分支管理模型&#xff0c;这种模型不仅能提升团队协作效率&#xff0c;还能确保代码质…

数据库回滚:大祸临头时

原文地址 什么是数据库回滚&#xff1f; 数据库技术中&#xff0c;回滚是通过撤销对数据库所做的一项或多项更改&#xff0c;将数据库返回到先前状态的操作。它是维护数据完整性和从错误中恢复的重要机制。 什么时候需要数据库回滚&#xff1f; 数据库回滚在以下几个场景中很…

1月7日星期二今日早报简报微语报早读

1月7日星期二&#xff0c;农历腊月初八&#xff0c;早报#微语早读。 1、公安部&#xff1a;已为一线民警配备执法记录仪130万余部&#xff0c;规范现场执法&#xff1b; 2、浙江提出2035年全省域基本实现共同富裕&#xff1b; 3、“汕头牛肉丸”有新标准&#xff01;1月6日起…

数据结构:时间复杂度和空间复杂度

我们知道代码和代码之间算法的不同&#xff0c;一定影响了代码的执行效率&#xff0c;那么我们该如何评判算法的好坏呢&#xff1f;这就涉及到了我们算法效率的分析了。 &#x1f4d6;一、算法效率 所谓算法效率的分析分为两种&#xff1a;第一种时间效率&#xff0c;又称时间…

Clisoft SOS设置Workarea

Clisoft SOS设置Workarea 本人也是刚刚接触这个软件&#xff0c;可能有些方面不够完善&#xff0c;欢迎大佬指点。 这里就不演示创建创建Server和Project&#xff0c;可以参考Clisoft SOS设置Server和Project 创建Workarea 创建好目录并设置好权限 # 创建组 [bhlumaster ~]$…

安徽省乡镇界面图层+arcgis数据shp格式-乡镇名称和编码2020年+wgs84坐标内容测评

最新安徽省乡镇界面图层arcgis数据shp格式-乡镇名称和编码2020年wgs84坐标无偏移

LabVIEW软件Bug的定义与修改

在LabVIEW软件开发过程中&#xff0c;bug&#xff08;程序错误或缺陷&#xff09;指的是程序中导致不符合预期行为的任何问题。Bug可能是由于编码错误、逻辑漏洞、硬件兼容性问题、系统资源限制等因素引起的。它可能会导致程序崩溃、功能无法正常执行或输出结果不符合预期。理解…

WebRTC 在视频联网平台中的应用:开启实时通信新篇章

在当今这个以数字化为显著特征的时代浪潮之下&#xff0c;实时通信已然稳稳扎根于人们生活与工作的方方面面&#xff0c;成为了其中不可或缺的关键一环。回首日常生活&#xff0c;远程办公场景中的视频会议让分散各地的团队成员能够跨越地理距离的鸿沟&#xff0c;齐聚一堂共商…

oracle位运算、左移右移、标签算法等

文章目录 oracle基础函数创建bitor(按位或)函数bitnot(按位非)函数bitxor(按位异或)函数左移函数BITSHIFT()函数(实测不可用&#xff0c;废弃掉该方案)右移函数(略&#xff0c;有此场景吗?) 实际应用资质字典增删改查分别对应什么操作新增对应操作查询对应的操作修改删除(正向…

【银河麒麟高级服务器操作系统实例】tcp半链接数溢出分析及处理全过程

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://document.kylinos.cn 服务器环境以及配置 系统环境 物理机/虚拟机/云…

[文献精汇]使用PyCaret预测 Apple 股价

2024年专题 量化专题 共计 102 篇 涵盖量化框架、数据篇、风险与收益、策略篇、多因子模型、编程篇、图形篇、机器学习、Backtrader等主题&#xff0c;全方面了解量化领域知识。机器学习共计29篇 涵盖机器学习基本介绍、监督学习、集成算法、无监督学习以及机器学习实战。研报…

新一代智能工控系统网络安全合规解决方案

01.新一代智能工控系统概述 新一代智能工控系统是工业自动化的核心&#xff0c;它通过集成人工智能、工业大模型、物联网、5G等技术&#xff0c;实现生产过程的智能化管理和控制。这些系统具备实时监控、自动化优化、灵活调整等特点&#xff0c;能够提升生产效率、保证产品质量…

【简博士统计学习方法】第1章:1. 统计学习的定义与分类

自用笔记 1. 统计学习的定义与分类 1.1 统计学习的概念 统计学习&#xff08;Statistical Machine Learning&#xff09;是关于计算机基于数据构建概率统计模型并运用模型对数据进行预测与分析的一门学科。 以计算机和网络为平台&#xff1b;以数据为研究对象&#xff1b;以…

PS 解析成ES流

最近做ps 解析&#xff0c;整理了一下&#xff1a; 解析如下&#xff1a; 这里理解为&#xff0c;后面会跟着pes头&#xff0c;在长度F00D-2-0A字节以后。

Android存储方案对比(SharedPreferences 、 MMKV 、 DataStore)

简介&#xff1a;本文介绍了Android开发中常用的键值对存储方案&#xff0c;包括SharedPreferences、MMKV和DataStore&#xff0c;并且对比了它们在性能、并发处理、易用性和稳定性上的特点。通过实际代码示例&#xff0c;帮助开发者根据项目需求选择最适合的存储方案&#xff…

基于 GEE Sentinel-1 数据集提取水体

目录 1 水体提取原理 2 完整代码 3 运行结果 1 水体提取原理 水体提取是地理信息和遥感技术的关键应用之一&#xff0c;对于多个领域都具有重要的应用价值。它有助于更好地管理水资源&#xff0c;保护环境&#xff0c;减少灾害风险&#xff0c;促进可持续发展&#xff0c;以…

w~自动驾驶~合集16

我自己的原文哦~ https://blog.51cto.com/whaosoft/12765612 #SIMPL 用于自动驾驶的简单高效的多智能体运动预测基准 原标题&#xff1a;SIMPL: A Simple and Efficient Multi-agent Motion Prediction Baseline for Autonomous Driving 论文链接&#xff1a;https://ar…

外驱功率管电流型PWM控制芯片CRE6281B1

CRE6281B1 是一款外驱功率管的高度集成的电流型PWM 控制 IC&#xff0c;为高性能、低待机功率、低成本、高效率的隔离型反激式开关电源控制器。在满载时&#xff0c;CRE6281B1工作在固定频率(65kHz)模式。在负载较低时&#xff0c;CRE6281B1采用节能模式&#xff0c;实现较高的…

亚信安全2025年第1期《勒索家族和勒索事件监控报告》

本周态势快速感知 本周全球共监测到勒索事件51起&#xff0c;本周勒索事件数量降至近一年来最低&#xff0c;需注意防范。从整体上看Ransomhub依旧是影响最严重的勒索家族&#xff1b;本周Acrusmedia和Safepay也是两个活动频繁的恶意家族&#xff0c;需要注意防范。本周&#…