头文件:include/linux/xarray.h
实现文件:lib/xarray.c
struct xarray {
spinlock_t xa_lock; // 内置自旋锁
gfp_t xa_flags; // 内存分配标志
void __rcu *xa_head; // 指向树根或直接存储的值
};
// 高级状态机(用于复杂操作)
struct xa_state {
struct xarray *xa;
unsigned long xa_index; // 当前索引
unsigned char xa_shift; // 当前层级
unsigned char xa_sibs; // sibling 节点数
void __rcu **xa_node; // 当前节点指针
void *xa_entry; // 当前条目
struct xa_node *xa_node_ptr; // 节点指针
};
struct xa_node {
unsigned char shift; // 当前层级的位移
unsigned char offset; // 在父节点中的偏移
unsigned char count; // 有效条目数
unsigned char nr_values; // 值条目数
struct xa_node __rcu *parent; // 父节点
struct xarray *array; // 所属的 xarray
void __rcu *slots[XA_CHUNK_SIZE]; // 64 个槽位
};
指针最低有效位编码额外信息:
// 位编码规则(64 位系统)
#define XA_CHUNK_SHIFT 6 // 每个节点 64 个槽位
#define XA_CHUNK_SIZE (1 << XA_CHUNK_SHIFT) // 64
// 条目类型标志
#define XA_ZERO_ENTRY ((void *)1) // 零条目
#define XA_RETRY_ENTRY ((void *)2) // 重试标志
#define XA_BUSY_ENTRY ((void *)3) // 忙碌标志
// 检测是否为内部条目
static inline bool xa_is_internal(const void *entry)
{
return ((unsigned long)entry & 3) == 2;
}
// 标记是否为值条目(不是指针也不是内部节点)
static inline bool xa_is_value(const void *entry)
{
return (unsigned long)entry & 1;
}
// 初始化
void xa_init(struct xarray *xa);
void xa_init_flags(struct xarray *xa, gfp_t flags);
// 存储(自动加锁)
int xa_store(struct xarray *xa, unsigned long index,
void *entry, gfp_t gfp);
// 读取(RCU 安全)
void *xa_load(struct xarray *xa, unsigned long index);
// 删除
void *xa_erase(struct xarray *xa, unsigned long index);
// 条件存储(比较并交换)
void *xa_cmpxchg(struct xarray *xa, unsigned long index,
void *old, void *entry, gfp_t gfp);
static inline bool xa_is_node(const void *entry)
{
// 小于 4096 的地址通常是内核保留的特殊值(如 NULL、错误码),不会是有效的节点指针
return xa_is_internal(entry) && (unsigned long)entry > 4096;
}
对于一个 64 位索引,如何定位到具体的槽位?
以 32 位索引为例(实际支持 64 位):
索引值 = 0x12345678
转换为二进制:0001 0010 0011 0100 0101 0110 0111 1000
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ └─ 层级0(6 bits)
│ │ │ │ │ │ └────── 层级1
│ │ │ │ │ └─────────── 层级2
│ │ │ │ └──────────────── 层级3
│ │ │ └───────────────────── 层级4
│ │ └────────────────────────── 层级5
│ └─────────────────────────────── 层级6
└──────────────────────────────────── 层级7(如果需要)
每个层级使用 6 bits,共支持 64 位(最多 11 层,因为 6×11=66 > 64)
层级 0 (叶子): 每个槽位直接存储值
↓
层级 1: 每个槽位指向一个叶子节点
slots[0] → 叶子节点 (管理索引 0-63)
slots[1] → 叶子节点 (管理索引 64-127)
...
层级 2: 每个槽位指向一个层级 1 节点
slots[0] → 节点1 (管理索引 0-4095)
slots[1] → 节点2 (管理索引 4096-8191)
...
性能特性
操作 平均复杂度 最坏复杂度
查找 O(1) ~ O(log_64 n) O(log_64 n)
插入 O(1) ~ O(log_64 n) O(log_64 n)
删除 O(1) ~ O(log_64 n) O(log_64 n)
实际性能(基于 Btrfs 的测试数据):
插入 100 万个连续键值:约 0.3-0.5 秒(比红黑树快 3-4 倍)
内存开销:内部节点共享,平均每个条目约 8-16 字节 额外开销
关键设计点:
1.紧凑布局:使用 unsigned char 存储小整数,节省内存
2.RCU 保护:__rcu 标记启用 RCU 机制
3.双重计数:count 和 nr_values 分离,加速某些查询
适用场景对比
选择红黑树的场景:
键值完全随机,无法预测
需要支持范围查询(如查找最小键值)
对内存分配零容忍(红黑树插入不分配内存)
数据对象本身已经存在,只需组织关系
选择 Xarray 的场景:
键值相对密集(如文件偏移、PID、对象 ID)
需要高并发读取
希望简化代码(避免手写遍历逻辑)
可以使用 RCU 保护读路径
是否需要快速查找?
├─ 否 → 使用链表(list_head)
└─ 是 → 键值类型?
├─ 整数且密集 → Xarray
├─ 整数但稀疏 → 红黑树 或 Xarray(取决于内存)
├─ 字符串 → 哈希表(hlist)
└─ 需要范围查询 → 红黑树
只有以下情况才选择红黑树:
1.需要频繁的前驱/后继遍历
2.键值完全随机且极其稀疏
3.绝对不能有内存分配失败的可能
4.已有大量代码依赖红黑树 API
未来趋势
1.Xarray 逐步替代:在新代码中,Xarray 正逐步替代红黑树处理 ID 映射类场景
2.红黑树不会消失:在调度器、内存管理等核心路径,红黑树依然不可替代
3.混合使用:内核开发者开始根据场景混合使用(如 Btrfs 同时使用两者)

6万+

被折叠的 条评论
为什么被折叠?



