内核数据结构-XArray

XArray简介

XArray是一种抽象数据类型,类似于一个大的指针数组,它满足了许多与哈希或常规可调整大小数组相同的需求。由于 xarray 中的数据都是指针,使用 RCU 这种无锁的方法查找数据是再合适不过了。

xarray 数据结构主要的应用场景是在文件缓存。我们知道在Linux内核中,为了加快文件的访问速度,将空闲的内存页就用做了磁盘的 cache。一个文件的缓存通过 address_space 数据结构进行管理,而这里面用于记录文件数据与内存页之间映射关系的数据结构就是采用了 xarray。当每次需要访问文件的数据时,都需要先查找这个缓存表,所以它的最重要的需求就是查找速度要快。如果把文件的偏移看作是虚拟地址,那么这个表其实做的事情就是——虚拟地址->内存页 的映射。这不就是虚拟内存的页表所做的事情吗!所以其实 xarray 就是类似于内存的页表结构,二者的区别就是 xarray 的 “地址” 范围是可变的,而页表的地址范围是固定的(CPU的寻址位宽决定)。

XArray 基本数据结构

XArray主要包括结构体struct xarray和结构体struct xa_node.
struct xarray:

278 /**
 279  * struct xarray - The anchor of the XArray.
 280  * @xa_lock: Lock that protects the contents of the XArray.
 281  *
 282  * To use the xarray, define it statically or embed it in your data structure.
 283  * It is a very small data structure, so it does not usually make sense to
 284  * allocate it separately and keep a pointer to it in your data structure.
 285  *
 286  * You may use the xa_lock to protect your own data structures as well.
 287  */
 288 /*
 289  * If all of the entries in the array are NULL, @xa_head is a NULL pointer.
 290  * If the only non-NULL entry in the array is at index 0, @xa_head is that
 291  * entry.  If any other entry in the array is non-NULL, @xa_head points
 292  * to an @xa_node.
 293  */
 294 struct xarray {
 295     spinlock_t  xa_lock;
 296 /* private: The rest of the data structure is not to be used directly. */
 297     gfp_t       xa_flags;
 298     void __rcu *    xa_head;
 299 };

成员说明:
xa_lock: 用于保护XArray内容的锁。
xa_head:用于顶级的xa_node节点。

struct xa_node:

1117 /*
1118  * @count is the count of every non-NULL element in the ->slots array
1119  * whether that is a value entry, a retry entry, a user pointer,
1120  * a sibling entry or a pointer to the next level of the tree.
1121  * @nr_values is the count of every element in ->slots which is
1122  * either a value entry or a sibling of a value entry.
1123  */
1124 struct xa_node {
1125     unsigned char   shift;      /* Bits remaining in each slot */
1126     unsigned char   offset;     /* Slot offset in parent */
1127     unsigned char   count;      /* Total entry count */
1128     unsigned char   nr_values;  /* Value entry count */
1129     struct xa_node __rcu *parent;   /* NULL at top of tree */
1130     struct xarray   *array;     /* The array we belong to */
1131     union {
1132         struct list_head private_list;  /* For tree user */
1133         struct rcu_head rcu_head;   /* Used when freeing node */
1134     };
1135     void __rcu  *slots[XA_CHUNK_SIZE];
1136     union {
1137         unsigned long   tags[XA_MAX_MARKS][XA_MARK_LONGS];
1138         unsigned long   marks[XA_MAX_MARKS][XA_MARK_LONGS];
1139     };
1140 };

成员说明

(1)shift成员用于指定当前xa_node的slots数组中成员的单位,当shift为0时,说明当前xa_node的slots数组中成员为叶子节点,当shift为6时,说明当前xa_node的slots数组中成员指向的xa_node可以最多包含2^6(即64)个节点,
(2)offset成员表示该xa_node在父节点的slots数组中的偏移。(这里要注意,如果xa_node在父节点为NULL,offset是任意的值,因为没有被初始化)
(3)count成员表示该xa_node有多少个slots已经被使用。
(4)nr_values成员表示该xa_node有多少个slots存储的Value Entry。
(5)parent成员指向该xa_node的父节点
(6)array成员指向该xa_node所属的xarray
(7)slots是个指针数组,该数组既可以存储下一级的节点, 也可以用于存储即将插入的对象指针.

Xarray结构图

xarray 作为一个树形结构,每个树中的节点(也就是 xa_node )包含着 XA_CHUNK_SIZE(默认为64) 个 slot。叶子结点中的 slot 指向实际实际的数据(在文件缓存中就是存放文件数据的内存页框指针),中间结点中的 slot 则指向下一级表,类似于页表的页目录。正如前面所说的,它类似于虚拟内存的页表,xarray 的查找 key 是一个类似于地址的 index,存储的主体则是指针。下面以一个 node 包含 16个 slot 的 xarray 为例,绘制 xarray 的结构图:
在这里插入图片描述
总的来讲和系统的页表很像。有了这个图像概念在心里,有些函数就相对比较容易理解了。

API介绍

先理解一下slots中存放的Entry的规则
slots中存储的entry必须4字节对齐,低两位决定了Xarray如何解析其内容。

  • 低2位是00时,它是一个Pointer Entry
  • 低2位是10时,它是一个Internal Entry,一般指向下一级的xa_node.但是有些Internal Entry有特别的含义,比如:
    0-62: Sibling entries
    256: Zero entry
    257: Retry entry
  • 低2为是x1时,它是一个Value Entry,或者tagged pointer。

(1)定义一个XArray数组:

DEFINE_XARRAY(array_name);
/* or */
struct xarray array;
xa_init(&array);

(2)在XArray里存放一个值:

void *xa_store(struct xarray *xa, unsigned long index, void *entry, gfp_t gfp);

这个函数会把参数给出的entry,放到请求的index这个地方。如果要XArray需要分配内存,会使用给定的gfp来分配。如果成功,返回值是之前存放在index的值。删除一个entry可以通过在这里存放NULL来实现,或者调用

void *xa_erase(struct xarray *xa, unsigned long index);

(3)xa_store的变体:

  • xa_insert用于存放但不覆盖现有的entry
  • 另一个变体:xa_cmpxchg,只有当存的值和old参数匹配上时,才会将entry存在index处。
static inline int __must_check xa_insert(struct xarray *xa,
		unsigned long index, void *entry, gfp_t gfp);
static inline void *xa_cmpxchg(struct xarray *xa, unsigned long index,
			void *old, void *entry, gfp_t gfp);

(4)用xa_load()从XArray里取出一个值:

void *xa_load(struct xarray *xa, unsigned long index);

返回值是存放在index处的值。XArray里,空entry和存入NULL的entry是等价的。因此xa_load不会对空entry有特殊的处理。
(5)非空entry上还可以设置最多3个比特的标签,标签管理函数:

bool xa_get_mark(struct xarray *xa, unsigned long index, xa_mark_t mark);
void xa_set_mark(struct xarray *xa, unsigned long index, xa_mark_t mark);
void xa_clear_mark(struct xarray *xa, unsigned long index, xa_mark_t mark);

mark的值是XA_MARK_0, XA_MARK_1, XA_MARK_2三者之一。 xa_set_mark用于在index处的entry上设置标签 xa_clear_mark用于清除标签 xa_get_mark用于返回index处的entry的标签
(6)XArray是很稀疏的,因此一个普遍的准则是,不要进行低效的遍历查找非空项。要查找多个非空项,应该使用这个宏:

xa_for_each(xa, entry, index, max, filter) {
    /* Process "entry" */
}

在进入循环之前,需要把index设为遍历的起点,max设为遍历的最大index,filter指定需要过滤的mark。 循环执行时,index会被设为当前匹配到的entry。可以在循环里修改index,来改变迭代过程。修改XArray自身也是允许的。
(7)删除一个Xarray中所有的Entry

void xa_destroy(struct xarray *xa);

还有其他很多操作XArray的普通API。特殊情况下还可以使用高级API。

Xarray锁

使用NormalAPI时,不必担心锁的问题。XArray使用RCU和内部自旋锁来同步访问:
无需锁定:

xa_empty()
xa_marked()

采取RCU读取锁定:

xa_load()
xa_for_each()
xa_find()
xa_find_after()
xa_extract()
xa_get_mark()

内部采用xa_lock:

xa_store()
xa_store_bh()
xa_store_irq()
xa_insert()
xa_insert_bh()
xa_insert_irq()
xa_erase()
xa_erase_bh()
xa_erase_irq()
xa_cmpxchg()
xa_cmpxchg_bh()
xa_cmpxchg_irq()
xa_store_range()
xa_alloc()
xa_alloc_bh()
xa_alloc_irq()
xa_reserve()
xa_reserve_bh()
xa_reserve_irq()
xa_destroy()
xa_set_mark()
xa_clear_mark()

以下高级api需要拿xa_lock后调用:

__xa_store()
__xa_insert()
__xa_erase()
__xa_cmpxchg()
__xa_alloc()
__xa_reserve()
__xa_set_mark()
__xa_clear_mark()

参考链接

链接: 内核基础设施(一)xarray
链接: Linux lib 之 xarray
链接: xarray这个数据结构
链接: The XArray data structure
链接: 内核基础设施——XArray
链接: XArray

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值