Linux6.1内核中maple_tree的应用--find_vma

一、前言

在Linux6.1版本之前,内核中对于虚拟地址空间(VMA)是基于红黑树+双向链表进行实现的,之所以选取两种数据结构组织,是因为红黑树的查询效率较高,但遍历的时间复杂度也高,而双向链表适合遍历,但查询的效率低下。因此内核采用这两种数据结构进行管理,来取长补短。

红黑树+双向链表的组织方式在很长一段时间都被大家认可,但随着近年来cpu核数的扩展,应用程序的线程数量也随之增加,多线程情况下锁的争用问题也逐渐浮现出来了:

  • 红黑树的修改需要同步到双向链表
  • 红黑树的平衡操作,会影响多个节点。

在Linux6.1版本,对VMA的管理被替换成maple_tree。这种数据结构按照无锁的方式进行设计,使用Linux中的线程安全RCU无锁编程来实现。

struct maple_tree {
	union {
		spinlock_t	ma_lock;
		lockdep_map_p	ma_external_lock;
	};
	void __rcu      *ma_root;
	unsigned int	ma_flags;
};

接下来我们着重分析一下在对VMA的操作里,maple_tree的应用,在此之前,先了解一下 ma_state 和节点状态值。

二、预备知识

ma_state 结构体是一个在枫树(Maple Tree)操作期间维护操作状态的结构。它包含了指向操作树的指针、当前操作的索引范围、当前节点以及其他与操作相关的信息。每次进行插入、查找或删除操作时,ma_state 中的数据会被更新,以便在操作过程中跟踪树的状态,其定义如下:

struct ma_state {
	struct maple_tree *tree;	/* 指向当前操作的枫树 */
	unsigned long index;		/* 操作范围的起始索引位置 */
	unsigned long last;		/* 表示当前操作所涉及范围的结束索引值 */
	struct maple_enode *node;	/* 包含此entry的节点,它是一个 maple_enode 结构,保存了当前范围内的条目 */
	unsigned long min;		/* 用于表示当前节点的最小索引值 */
	unsigned long max;		/* 用于表示当前节点的最大索引值*/
	struct maple_alloc *alloc;	/* 表示为当前操作分配的节点,maple_alloc结构体中包含slot数组 */
	unsigned char depth;		/*树的当前深度 */
	unsigned char offset;//当前在节点中的插槽的位置
	unsigned char mas_flags;
};

在枫树中,nodeindex, last 之间的关系非常紧密。indexlast 定义了一个范围,而该范围内的条目存储在 node 中。在这个结构体中,记录了对maple_tree的操作,以及对应的状态信息。

节点的状态信息一般存储在struct maple_enode *node中,其状态信息有几个重要的如下:

  • MAS_START:代表这棵树还没被搜索过
  • MAS_ROOT:代表这棵树已经被搜索过了,条目存在于树根,即其索引为0,长度为1。这个树中只要这一个条目
  • MAS_NONE :表示我们搜索了该树,但该树中没有包含该条目的节点
  • MA_ERROR:代表错误

三、源码分析

3.1 find_vma

find_vma的代码 mm/nommu.c中定义,其核心是通过MA_STATE构建结构体mas,再通过调用mas_walk函数去完成遍历查找。

/*
 * look up the first VMA in which addr resides, NULL if none
 * - should be called with mm->mmap_lock at least held readlocked
 */
struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
{
	MA_STATE(mas, &mm->mm_mt, addr, addr);

	return mas_walk(&mas);
}

其中MA_STATE宏的定义如下:

#define MA_STATE(name, mt, first, end)					\
	struct ma_state name = {					\
		.tree = mt,						\
		.index = first,						\
		.last = end,						\
		.node = MAS_START,					\
		.min = 0,						\
		.max = ULONG_MAX,					\
		.alloc = NULL,						\
	}

在maple_tree结构体构造完毕之后,则调用mas_walk函数去查询。

3.2 mas_walk

在树结构中根据 mas->index 搜索一个特定的节点,并在搜索过程中更新 mas->indexmas->last 以维持该状态的范围。具体如下:

  • 通过mas_state_walk() 来尝试获取当前的条目
  • 如果该枫树还没查找,则重新执行上面的步骤
  • 如果该枫树只有一个节点,并且当前查找的索引值为0,说明第一个entry就是要查找的,则将其的结束索引值设置为0。如果当前查找的索引值不为0,说明要查找的entry不是第一个,则将起始索引设置为1,结束索引设置为ULONG_MAX。并返回当前查找的条目。
  • 如果枫树为空,则重新设置查找范围为0-ULONG_MAX,并返回当前查找的条目
void *mas_walk(struct ma_state *mas)
{
	void *entry;

retry:
    // 调用 mas_state_walk() 来尝试获取当前的条目
	entry = mas_state_walk(mas);
  	//如果mas->node是MAS_START,则说明这棵树还没开始查找,则重新尝试
	if (mas_is_start(mas))
		goto retry;
	//如果mas->node是 MAS_ROOT,说明该树只有一个节点,则
	if (mas_is_ptr(mas)) {
        //此时如果当前的索引值index=0,说明查找操作在起始的节点,则将 mas->last 置为 0,并返回当前查找的条目
		if (!mas->index) {
			mas->last = 0;
		} else {
         //如果当前索引值index不为0,将 mas->index 置为 1,更新索引值。将 mas->last 置为 ULONG_MAX,重置查找范围
			mas->index = 1;
			mas->last = ULONG_MAX;
		}
		return entry;
	}
	//如果mas->node为 MAS_NONE,说明节点无效,则设置查找范围为0-ULONG_MAX,并返回当前查找的条目
	if (mas_is_none(mas)) {
		mas->index = 0;
		mas->last = ULONG_MAX;
	}

	return entry;
}

3.3 mas_state_walk

mas_walk() 函数中调用mas_state_walk()函数去获取当前查询的条目。其具体工作是:

  • 根据mas_start函数去设置枫树的状态
  • 查看mas->node的值,去判断返回当前的条目还是NULL
  • 如果mas->node的值不为MAS_NONE或MAS_ROOT,则说明枫树是一颗有多个节点的树,因此通过函数mtree_range_walk()去遍历查找
static inline void *mas_state_walk(struct ma_state *mas)
{
	void *entry;

	entry = mas_start(mas);//设置枫树的状态
    //如果mas->node是MAS_NONE,则说明是空树,返回NULL
	if (mas_is_none(mas))
		return NULL;
 	//如果mas->node是MAS_ROOT,则说明节点有效,返回当前的条目
	if (mas_is_ptr(mas))
		return entry;

	return mtree_range_walk(mas);//实际的调用操作是通过mtree_range_walk()函数来实现的
}

接下来分析,如何去设置枫树的状态函数mas_start()

3.3.1 mas_start

该函数会根据mas->node的值进行如下操作:

  • mas->node!==MAS_START:则返回NULL
  • 否则,代表这棵树还没遍历,则初始化mas的值,并获取枫树的根节点,根据枫树类型去做处理:
    • 如果枫树是空树,则设置偏移量mas->offset = MAPLE_NODE_SLOTS,并返回NULL
    • 如果枫树具有多个节点,则设置其depth为1,返回NULL
    • 如果枫树仅包含一个根节点,则根据index的值去判定返回根节点还是NULL
//该函数用于设置当前操作的枫树的状态,即设置mas->node
static inline struct maple_enode *mas_start(struct ma_state *mas)
{
    //如果mas->node是MAS_START,则初始化mas的成员
	if (likely(mas_is_start(mas))) {
		struct maple_enode *root;

		mas->node = MAS_NONE;
		mas->min = 0;
		mas->max = ULONG_MAX;
		mas->depth = 0;
		mas->offset = 0;

		root = mas_root(mas);
		/* 具有多个节点的树 */
		if (likely(xa_is_node(root))) {
			mas->depth = 1;
			mas->node = mte_safe_root(root);
			return NULL;
		}

		/* 空树 */
		if (unlikely(!root)) {
			mas->offset = MAPLE_NODE_SLOTS;
			return NULL;
		}

		/* 代表树中仅包含一个节点,检查mas->index是否大于0。如果是则返回NULL,否则返回当前的条目entry */
		mas->node = MAS_ROOT;
		mas->offset = MAPLE_NODE_SLOTS;
		if (mas->index > 0)
			return NULL;

		return root;
	}
    
    //如果节点的状态不是MAS_START或者有错误,则返回NULL
	return NULL;
}

mas_state_walk()函数中通过检查枫树状态不是MAS_PTR和MAS_NONE,则调用mtree_range_walk()进行遍历查找的

3.3.2 mtree_range_walk

这个函数主要是用于遍历枫树,通过给定的索引 (mas->index) 查找树中符合条件的节点,并逐层遍历直到找到叶节点,同时更新相关状态。具体工作如下:

  • 首先保存当前节点以及当前节点的最大最小索引值,在去做遍历:
    • 首先初始化offset=0,并保存当前节点。然后根据函数ma_pivots获取当前节点的pivots,pivot 在树的内部节点中用于划分范围,每个 pivot 范围表示一个节点的边界。
    • 根据pivots[offset]以及mas->index的大小进行不同的处理,这里借助了二分法的思想。如果pivots[offset]值大于等于给定的索引mas->index,则将pivots[offset]设置为新的max,接着去获取当前节点的slots,然后根据offset获取下一个节点。
    • 如果pivots[offset]值小于给定的索引mas->index,则offset++,继续寻找下一个 pivot,直到找到大于等于 mas->index 的 pivot,找到后更新最小值为 pivots[offset-1]+1
    • 一直遍历,直到到达叶子节点。在遍历完成之后,更新mas结构体,并返回当前的节点。
//根据给定的索引(mas->index)查找并更新树中的节点,并且逐步遍历树的每一层,直到找到符合条件的叶节点
static inline void *mtree_range_walk(struct ma_state *mas)
{
	unsigned long *pivots;
	unsigned char offset;
	struct maple_node *node; 
	struct maple_enode *next, *last;
	enum maple_type type;
	void __rcu **slots;
	unsigned char end;
	unsigned long max, min;
	unsigned long prev_max, prev_min;
	/* 保存当前操作的节点及当前节点的最小最大的索引值 */
	next = mas->node;
	min = mas->min;
	max = mas->max;
	do {
		offset = 0;
		last = next;//遍历
		node = mte_to_node(next);//将节点强制转换为maple_node类型
		type = mte_node_type(next);
        //获取当前节点的 pivots 数组
		pivots = ma_pivots(node, type);
        //获取数据的结束位置
		end = ma_data_end(node, type, pivots, max);
        //如果当前的节点是死节点,即父节点无效,则返回NULL
		if (unlikely(ma_dead_node(node)))
			goto dead_node;
		//检查 pivots[offset] 是否大于等于给定的索引,如果是,则更新pivots[offset]为最大值
		if (pivots[offset] >= mas->index) {
            //将当前节点的max和min更新为prev的max和min
			prev_max = max;
			prev_min = min;
			max = pivots[offset];
			goto next;
		}
		//如果 pivots[offset] 小于 mas->index,继续寻找下一个 pivot,直到找到大于等于 mas->index 的 pivot,找到更新最小值为 pivots[offset - 1] + 1
		do {
			offset++;
		} while ((offset < end) && (pivots[offset] < mas->index));

		prev_min = min;
		min = pivots[offset - 1] + 1;
		prev_max = max;
		if (likely(offset < end && pivots[offset]))
			max = pivots[offset];

next:
        //获取当前节点的slots
		slots = ma_slots(node, type);
        //根据 offset 获取下一个子节点
		next = mt_slot(mas->tree, slots, offset);
        
        //如果标记为死节点,返回null
		if (unlikely(ma_dead_node(node)))
			goto dead_node;
	} while (!ma_is_leaf(type));//继续遍历,直到到达叶子节点
    
	//遍历完成,更新mas结构体,并返回当前的节点
	mas->offset = offset;
	mas->index = min;
	mas->last = max;
	mas->min = prev_min;
	mas->max = prev_max;
	mas->node = last;
	return (void *) next;

dead_node:
	mas_reset(mas);
	return NULL;
}

笔者能力有限,如有错误请指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值