XArray 的全称为:eXtensible Arrays。看用途,明明是一个字典(有键值对),为什么名字里带着 Array 呢?可能是因为 key 不能是字符串,只能是 unsigned long 类型的。所以看起来就像是一个 Array 的下标,只不过这个 Array 非常大,可以有 unsigned long 可取值范围个元素。

XArray 是基于基数树(Radix Tree) 实现的一种数据结构。因为 key 是 unsigned long,所以其是根据二进制表示来作为前缀树里的前缀的。其实在 XArray 之前就是有 Radix Trie 的 API 的,只不过 Matthew Wilcox 认为内核的基数树的 API 设计不合理,比如:

  • “树”这个术语就很有迷惑性。基数树跟传统的,教科书上那种树,并不是很像。举例来说,传统树上的增加 entry 的操作,一直都被称为“插入”。但对基数树而言,“插入”并不是字面上发生的事情,尤其是当 key 已经存在的时候。基数树也支持“异常 entry“,光是这个名字,就让用户听着不敢用了。
  • 基数树还要求用户自己处理锁。

Wilcox 决定改良接口。基数树本身不变,它本身没什么问题。改变的是接口,现在接口暗示用户,把它当做数组来用,而不是当做树来用。因为基数树看起来就像是一个自动增长的数组:一个用 unsigned long 来索引的指针数组。这种视图,更好地描述了基数树的用途。

  • XArray 默认自己处理了锁,简化了使用;
  • 基数树的“预加载”机制允许用户获取锁之前先预先分配内存,这个机制在 XArray 中被取消了,它太复杂又没有太多实际价值。
  • XArray API 被分为两部分,普通 API高级 API。后者给用户更多可控性,比如用户可以显式管理锁。API 可以用于不同的场景,满足不同的需求。比如 Page Cache 就可以用 XArray。普通 API 完全在高级 API 的基础上实现,所以普通 API 也是高级 API 的使用范例。

内核基础设施——XArray - Notes about linux and my work

一个新初始化的 XArray 在每个索引处都包含一个 NULL 指针。

XArray 是 resizable 的。XArray 是可以存普通的指针的。

XArray 还有一个 feature,它可以把很多个 index 组成的 range 看作是原子的。也就是说,这些 indexes share 同一个存储空间,在这个 range 内的任意一个 index 取或者存都是针对同一个数据进行的操作。

XArray 基础 API / XArray Normal API

Start by initializing an XArray, either

  • with DEFINE_XARRAY() for statically allocated XArrays or
  • xa_init() for dynamically allocated ones.

然后你可以用 xa_store() 来设置条目,用 xa_load() 来获取条目。

xa_insert(),在 index 处插入值,只有在此处为空时才成功。

Iterate using xa_for_each(), xa_for_each_start() or xa_for_each_range().

xa_find() or xa_find_after() 可以 iterate 到下一个不为空的地方。

xa_erase() 用来把一个 index 重新置 NULL。

Sometimes you need to ensure that a subsequent call to xa_store() will not need to allocate memory. The xa_reserve() function will store a reserved entry at the indicated index. Use xa_release() to cancel the reservation.

用完了需要 xa_destroy() 来 destroy:Finally, you can remove all entries from an XArray by calling xa_destroy().

为什么 XArray 的 value 需要是非负值?

因为最低位被用来当作一个 mark 标记使用了。这意味着原来的值需要左移一位,所以最高位会被丢掉,因此不能有数据。

static inline void *xa_mk_value(unsigned long v)
{
	WARN_ON((long)v < 0);
	return (void *)((v << 1) | 1);
}

xa_load() Kernel

如果没有存值怎么办?

XArray marks

Each entry in the array has three bits associated with it called marks. You can iterate over marked entries by using the xa_for_each_marked() iterator.

Setting or clearing a mark on any index of a multi-index entry will affect all indices covered by that entry. Querying the mark on any index will return the same result.

XArray — The Linux Kernel documentation

XArray 高级 API

高级 API 提供了更多的灵活性和更好的性能,但代价是接口可能更难使用,保障措施更少。

高级 API 是基于 xa_state 的。这是一个不透明的数据结构,你使用 XA_STATE() 宏在堆栈中声明。It is used as a cursor to maintain the position in the XArray and let you compose various operations together without having to restart from the top every time.

完整的 API 参考:XArray — The Linux Kernel documentation

完整的英文文档:XArray — The Linux Kernel documentation

The XArray data structure [LWN.net]

xas_retry

Internal entry

The XArray reserves some entries for its own purposes.

  • These are never exposed through the normal API,
  • but when using the advanced API, it's possible to see them.

Usually the best way to handle them is to pass them to xas_retry(), and retry the operation if it returns true.

xas.xa_index

当前 entry 的 index。

xa_to_value() Kernel

Get value stored in an XArray entry.

xas_next()

Move state to next index.