linux kernel xarray

头文件: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 同时使用两者)
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值