关注微信公众号:Linux内核拾遗
文章来源:mp.weixin.qq.com/s/UnoxxfpU3…
1 引言
在现代操作系统中,高效的数据结构对于处理大规模数据和高并发访问非常重要。Linux内核作为一个开放源代码的操作系统内核,一直致力于改进数据结构以提高性能和可扩展性。其中一个引人注目的数据结构是Linux XArray(扩展数组)。
Linux XArray是一种高效的键值对数据结构,旨在解决大规模数据集上的高并发访问问题。它被广泛应用于Linux内核的各个子系统,如文件系统、网络子系统和内存管理等。
本文将深入探讨XArray的设计和实现细节,重点介绍XArray的数据结构、基本操作和扩展功能。通过本文的介绍,读者将能够更好地理解Linux XArray的作用和优势。
2 XArray基本概念
2.1 XArray的定义和特点
XArray是一种高效的键值对数据结构,用于在Linux内核中管理大规模数据集。XArray的核心思想是通过索引来跟踪和定位数据项,而不是依赖于哈希函数或平衡树结构。它使用一组指针和键来组织数据,通过这些指针和键,可以快速地定位到数据项,而无需遍历整个数据集。XArray通过采用特殊的索引和跳表结构,提供了快速的插入、删除、更新和查询操作。
XArray的索引结构由一系列的节点组成,每个节点包含一个键和指向下一个节点的指针。节点之间通过指针相互链接,形成一个跳表结构。这种结构允许在插入、删除和查询操作中快速地定位到目标数据项,同时保持较低的时间复杂度。
Linux XArray是一种扩展数组(eXtensible Array)数据结构,它定义和实现在Linux内核中,提供了一种通用的数据管理机制,可以被不同的子系统使用。
XArray具备以下特点:
2.2 XArray与传统数据结构的比较
2.3 XArray的应用场景
3 XArray的设计与实现
3.1 XArray的数据结构
XArray的数据结构在Linux内核源代码中的实现位于头文件include/linux/xarray.h
中:
struct xa_node
:这是XArray的基本节点结构,表示XArray中的节点,用于存储键值条目(entry)。
struct xa_node {
unsigned char shift; /* Bits remaining in each slot */
unsigned char offset; /* Slot offset in parent */
unsigned char count; /* Total entry count */
unsigned char nr_values; /* Value entry count */
struct xa_node __rcu *parent; /* NULL at top of tree */
struct xarray *array; /* The array we belong to */
union {
struct list_head private_list; /* For tree user */
struct rcu_head rcu_head; /* Used when freeing node */
};
void __rcu *slots[XA_CHUNK_SIZE];
union {
unsigned long tags[XA_MAX_MARKS][XA_MARK_LONGS];
unsigned long marks[XA_MAX_MARKS][XA_MARK_LONGS];
};
};
shift
:shift
是一个无符号字符(unsigned char),用于表示每个槽(slot)中剩余的位数。它确定了键(index)的位置,即用于计算在当前节点的槽位中的偏移量。offset
:offset
是一个无符号字符(unsigned char),用于表示当前节点在父节点中的槽位偏移量。通过偏移量,可以在父节点的槽位中定位到当前节点。count
:count
是一个无符号字符(unsigned char),用于表示当前节点中的总条目数。它统计了当前节点中存储的所有条目的数量。nr_values
:nr_values
是一个无符号字符(unsigned char),用于表示当前节点中的值条目数。它记录了当前节点中存储的值类型的条目的数量。parent
:parent
是指向父节点的指针。它允许在XArray的层次结构中进行导航,从而实现了对上层节点的访问。array
:array
是指向所属XArray的指针。它指示当前节点所属的XArray对象,用于在操作中获取相关的XArray属性和功能。private_list
:private_list
是用于树用户的私有链表。它在树结构中的用户定义中使用,以实现自定义的树结构功能。rcu_head
:rcu_head
是用于在释放节点时使用的RCU(Read-Copy-Update)机制的头结构。RCU是一种用于实现高效内存回收的机制。slots[XA_CHUNK_SIZE]
:slots
是一个指针数组,用于存储指向子节点或条目的指针。它具体指向当前节点的子节点或存储在当前节点中的条目。tags[XA_MAX_MARKS][XA_MARK_LONGS]
:tags
是一个位图数组,用于存储键的标记位图。它支持多个标记,每个标记由一个位图表示。marks[XA_MAX_MARKS][XA_MARK_LONGS]
:marks
是一个位图数组,用于存储节点的标记位图。它支持多个标记,每个标记由一个位图表示。
struct xarray
: 这是XArray的主要结构,用于管理XArray的属性和根节点。
struct xarray {
spinlock_t xa_lock;
/* private: The rest of the data structure is not to be used directly. */
gfp_t xa_flags;
void __rcu * xa_head;
};
spinlock_t xa_lock
: 这是一个自旋锁(spinlock),用于保护XArray的并发访问。自旋锁是一种用于同步访问共享资源的锁,它使用忙等待的方式来阻塞其他线程或进程的访问,直到锁可用。在访问XArray时,需要先获取该锁来确保数据的一致性和正确性。gfp_t xa_flags
: 这是一个标志位,用于指定内存分配的行为和特性。gfp_t
是一种用于描述内存分配的标志类型,在Linux内核中广泛使用。通过设置不同的标志位,可以指定内存分配的方式、要求和约束,以满足特定的内存管理需求。void __rcu * xa_head
: 这是一个指针,指向XArray的头部。__rcu
是一种用于实现内核中的读-复制-更新(RCU)机制的修饰符。RCU是一种无锁机制,用于在多线程环境中实现高效的并发访问。通过使用RCU修饰符,可以确保在读取XArray时不会阻塞其他线程对XArray的修改操作,从而提高并发性能。
XArray的其他辅助数据结构和函数在源代码中也有定义和实现,用于支持XArray的操作和功能,如用于标记槽的状态的位图(bitmap)、迭代器等。
3.2 XArray的基本操作
XArray的基本操作在Linux内核源代码中的实现涉及一系列函数和宏定义。以下是对XArray的基本操作的简要介绍。
3.2.1 插入数据
插入数据是向XArray中添加新的键值对的操作。
-
函数:
void *xa_store(struct xarray *, unsigned long index, void *entry, gfp_t);
。 -
作用:向XArray中插入一个键值对。
-
源代码位置:
lib/xarray.c
。 -
示例用法:
xa_store(&xarray, index, value, GFP_KERNEL);
函数的步骤如下:
3.2.2 删除数据
删除数据是从XArray中移除指定键值对的操作。
-
函数:
void *xa_erase(struct xarray *xa, unsigned long index);
。 -
作用:从XArray中删除指定键的条目。
-
源代码位置:
lib/xarray.c
。 -
示例用法:
xa_erase(&xarray, index);
函数的步骤如下:
- 准备工作:函数接收一个指向XArray的指针和一个索引作为参数。然后创建一个XArray状态(XA_STATE)对象,并将其与XArray和索引关联起来。
- 删除操作:使用XA_STATE对象,函数尝试在XArray中查找指定索引的键值对。如果找到了对应的键值对,就将其删除,并将结果存储在XArray状态对象中。
- 返回结果:函数通过调用
xas_store()
和xas_result()
函数将删除操作的结果返回。
3.2.3 更新数据
更新数据是修改XArray中指定键对应的值的操作。
-
函数:
void *xa_store(struct xarray *xa, unsigned long index, void *entry, gfp_t gfp)
。 -
作用:更新XArray中指定键的值。
-
源代码位置:
lib/xarray.c
。 -
示例用法:
xa_store(&xarray, index, new_value, GFP_KERNEL);
函数的步骤同插入数据。
3.2.4 查询数据
查询数据是根据给定键从XArray中检索相应的值的操作。
-
宏:
void *xa_load(struct xarray *xa, unsigned long index);
。 -
作用:从XArray中获取指定键的值。
-
源代码位置:
include/linux/xarray.h
。 -
示例用法:
value = xa_load(&xarray, index);
函数的步骤如下:
XArray
状态变量: 使用XA_STATE
宏,创建一个名为xas
的XArray
状态变量,该变量用于在XArray中定位和访问指定索引处的条目。rcu_read_lock
函数,获取RCU读取锁。这是一种读取操作所需的锁,它允许多个读取操作同时进行,而不会阻塞其他读取操作。xas_load
函数从XArray中加载指定索引处的条目,并将结果存储在entry
变量中。如果加载的条目是空条目(即没有有效数据),则将entry
置为NULL
。xas_retry
函数,检查在加载条目期间是否需要进行重试。如果需要重试,函数会重新加载条目,并继续循环,直到成功加载非空条目或不再需要重试。rcu_read_unlock
函数,释放之前获取的RCU读取锁。NULL
),作为函数的结果。3.3 XArray的扩展功能
3.3.1 范围查询
XArray支持范围查询,允许按照键的范围进行查询操作。范围查询可以通过遍历槽中的链表和跳表结构来实现。通过设置起始键和结束键,可以检索指定范围内的键值对。
范围查询功能的实现主要涉及使用xa_find_range()
函数进行遍历和检索。
int xa_find_range(struct xarray *xa, unsigned long start, unsigned long end,
xa_tag_t tag, void *result[]);
xa_find_range()
函数接受一个起始键和结束键,以及一个用于存储结果的数组result[]
。它将在指定范围内搜索符合条件的键值对,并将结果存储在result[]
中。
以下是一个示例代码片段,演示如何使用范围查询功能:
unsigned long start_key = 100;
unsigned long end_key = 200;
void *result[XA_CHUNK_SIZE];
int num_entries = xa_find_range(&xarray, start_key, end_key, XA_PRESENT, result);
if (num_entries > 0) {
// 处理查询结果
}
3.3.2 迭代器
XArray提供迭代器功能,使得可以逐个遍历XArray中的键值对。迭代器可以按照特定的顺序遍历槽中的链表和跳表结构,以获取所有的键值对。
迭代器可以使用xa_for_each()
或xa_for_each_marked()
函数来实现。
#define XA_FLAGS_LOCK_IRQ 0x1
int xa_for_each(struct xarray *xa, unsigned long *index, void *entry, xa_mark_t mark);
int xa_for_each_marked(struct xarray *xa, unsigned long *index, void *entry,
xa_mark_t mark, xa_mark_t *last);
xa_for_each()
函数按顺序迭代XArray中的每个条目,而xa_for_each_marked()
函数只迭代标记为特定标记的条目。
以下是一个示例代码片段,演示如何使用迭代器功能:
unsigned long index = 0;
void *entry;
xa_for_each(&xarray, &index, entry, XA_PRESENT) {
// 处理迭代到的键值对
}
4 综合示例
以下是一个简单的代码示例,演示如何使用Linux XArray进行插入、删除和查询操作:
#include
#include
#include
#include
#include
MODULE_LICENSE("GPL");
struct my_data {
int id;
char name[20];
};
DEFINE_XARRAY(my_xarray); // 定义一个全局的XArray
static void insert_data(int id, const char* name) {
struct my_data* data = kmalloc(sizeof(struct my_data), GFP_KERNEL);
data->id = id;
strncpy(data->name, name, sizeof(data->name));
xa_store(&my_xarray, id, data, GFP_KERNEL);
}
static void remove_data(int id) {
struct my_data* data = xa_erase(&my_xarray, id);
if (data)
kfree(data);
}
static struct my_data* get_data(int id) {
return xa_load(&my_xarray, id);
}
static int __init xarray_example_init(void) {
pr_info("xarray_example: Initializing XArray example modulen");
insert_data(1, "John");
insert_data(2, "Alice");
insert_data(3, "Bob");
insert_data(4, "Steve");
struct my_data* data = get_data(2);
if (data)
pr_info("xarray_example: ID: %d, Name: %sn", data->id, data->name);
unsigned long index;
pr_info("xarray_example: before remove data 3");
xa_for_each(&my_xarray, index, data) {
pr_info("index = %d, data = %p, data->id = %d, data->name = %sn",
index, data, data->id, data->name);
}
remove_data(3);
pr_info("xarray_example: after remove data 3n");
xa_for_each(&my_xarray, index, data) {
pr_info("index = %d, data = %p, data->id = %d, data->name = %sn",
index, data, data->id, data->name);
}
return 0;
}
static void __exit xarray_example_exit(void) {
pr_info("xarray_example: Cleaning up XArray example modulen");
struct my_data* data;
unsigned long index;
xa_for_each(&my_xarray, index, data) {
xa_erase(&my_xarray, index);
kfree(data);
}
}
module_init(xarray_example_init);
module_exit(xarray_example_exit);
在上面的示例中,我们使用了DEFINE_XARRAY
宏来定义一个全局的XArray,命名为my_xarray
。然后,我们定义了insert_data
函数来插入数据,remove_data
函数来删除数据,以及get_data
函数来查询数据。在test_xarray
函数中,我们进行了一些简单的插入、查询和删除操作,并最后调用cleanup_xarray
函数来清理XArray中的所有数据。
上述示例的输出结果如下:
5 总结
Linux XArray是一种高效的键值对数据结构,在Linux内核中用于管理大规模数据集。它通过索引和跳表的结构,实现了快速的插入、删除、更新和查询操作。XArray具有低内存占用、可扩展性和线程安全性等特点,使其在文件系统、网络子系统、内存管理和虚拟化等领域具有广泛的应用潜力。通过XArray,Linux内核能够以高性能和高效率处理复杂的数据操作需求,为系统的性能和可扩展性提供了重要的支持。
6 参考资料
关注微信公众号:Linux内核拾遗
文章来源:mp.weixin.qq.com/s/UnoxxfpU3…