openVswitch 2.10.0 (OVS)源码分析 FlexArray

FlexArray 是什么

FlexArrayovs中内核模块openvswitch.ko中使用了一种数据结构, 意为灵活数组(Flexible Array)

FlexArray 定义

#define FLEX_ARRAY_BASE_SIZE PAGE_SIZE
#define FLEX_ARRAY_PART_SIZE PAGE_SIZE
struct flex_array {
    union {
        struct {
            int element_size;
            int total_nr_elements;
            int elems_per_part;
            struct reciprocal_value reciprocal_elems;
            struct flex_array_part *parts[];
        };
        char padding[FLEX_ARRAY_BASE_SIZE];
    };
}
struct flex_array_part {
   char elements[FLEX_ARRAY_PART_SIZE];
}

FlexArray定义如上, 它是一个union, 其中第二项 padding 可以使整个union结构大小固定为FLEX_ARRAY_BASE_SIZE. 第一项struct中的字段意义如下:

  • element_size - FlexArray中存放的每个元素的大小. FlexArray每个元素大小是相同的, 在创建FlexArray时确定
  • total_nr_elements - FlexArray中可以存放的元素的最大个数. 尽管是灵活数组, 但这个值在创建FlexArray时确定,并且使用过程中不能改变.
  • elems_per_part - 每个part中所能存放的元素的个数.这是一个计算值, 在创建FlexArray时确定, 一个part的大小为PAGE_SIZE, 比如在PAGE_SIZE = 4096 时, 如果要存放的元素大小为 256 Bytes, 则 elems_per_part = 4096 / 256 = 16, 即每个part能容纳16个元素
  • reciprocal_elems - 它表示 elems_per_part 的倒数, 即 1/elems_per_part . 这并不是说它会以浮点数的形式存放,而是在计算以elems_per_part 为除数的除法时, 可以让机器计算地更有效率一点.
  • parts - 这是一个指针数组. 数组长度为空, 这种定义方式参见GNU中的零长度数组, 每一个指针指向一个part (额外的空间), 元素存放在part中. 注意, FlexArray还具有一个特性, 当 ( 元素最大个数 * 元素大小 <= 限定值 ) 时, FlexArray将不再使用额外的part空间, 转而使用FlexArray自己的空间.

使用中的FlexArray的空间结构如下图所示
openVswitch 2.10.0 (OVS)源码分析 FlexArray_第1张图片

FlexArray 使用

Array 创建
struct flex_array *rpl_flex_array_alloc(int element_size, unsigned int total, gfp_t flags)
{
	struct flex_array *ret;
	struct reciprocal_value reciprocal_elems = {0};
	int elems_per_part = 0;
	int max_size = 0;

	if (element_size) {
		elems_per_part = FLEX_ARRAY_ELEMENTS_PER_PART(element_size);  // 每一个part可以存放多少 element
		reciprocal_elems = reciprocal_value(elems_per_part);          // 计算elems_per_part 的倒数
		max_size = FLEX_ARRAY_NR_BASE_PTRS * elems_per_part;
	}
	/* max_size will end up 0 if element_size > PAGE_SIZE */
	if (total > max_size)
		return NULL;
	ret = kzalloc(sizeof(struct flex_array), flags);
	if (!ret)
		return NULL;
	ret->element_size = element_size;
	ret->total_nr_elements = total;
	ret->elems_per_part = elems_per_part;
	ret->reciprocal_elems = reciprocal_elems;
	if (elements_fit_in_base(ret) && !(flags & __GFP_ZERO)) // elements_fit_in_base 返回是否使用flex_array 本身就够了
		memset(&ret->parts[0], FLEX_ARRAY_FREE,
						FLEX_ARRAY_BASE_BYTES_LEFT);
	return ret;
}  

以上为FlexArray创建的代码, 主要包括申请FlexArray占用内存和初始化结构中的参数,入参为要存放的每个元素大小和需要容纳的元素个数. 注意, 这里不会去申请part占用的额外的空间, part空间是需要时才申请.

Array 存放元素
/*
  向 @fa的 @element_nr位置, 存入一个元素, 该元素首地址在 @src
 */
int rpl_flex_array_put(struct flex_array *fa, unsigned int element_nr, void *src,
			gfp_t flags)
{
	int part_nr = 0;
	struct flex_array_part *part;
	void *dst;

	if (element_nr >= fa->total_nr_elements)  // 是否超过 array的最大容量
		return -ENOSPC;
	if (!fa->element_size)
		return 0;
	if (elements_fit_in_base(fa))             //  fa是否支持使用额外的空间
		part = (struct flex_array_part *)&fa->parts[0];
	else {
		part_nr = fa_element_to_part_nr(fa, element_nr); // 根据入参, 计算它要存放的part编号
		part = __fa_get_part(fa, part_nr, flags);        // 得到part空间指针, part不存在就创建
		if (!part)
			return -ENOMEM;
	}
	dst = &part->elements[index_inside_part(fa, element_nr, part_nr)]; // 得到元素目的地址
	memcpy(dst, src, fa->element_size);
	return 0;
} 

以上为向FlexArray存入元素的代码 , 需要注意入参element_nr为存入的位置编号. 举个例子, 假设元素大小为256, PAGE_SIZE为 4096, 那么一个part可容纳16个元素, part 0包含元素范围是0~15, part 1包含元素范围是 16~31, 以此类推.如果向编号17的位置存入元素, 则自然会找到 part 1的对应位置.

Array 读取元素

读取元素在rpl_flex_array_get中实现, 比较简单, 同样是计算位置后取出

Array 清除元素
#define FLEX_ARRAY_FREE 0x6c
int rpl_flex_array_clear(struct flex_array *fa, unsigned int element_nr)
{
   ......
   dst = &part->elements[index_inside_part(fa, element_nr, part_nr)]; // 得到元素目的地址
   memset(dst, FLEX_ARRAY_FREE, fa->element_size);
}

清除元素在rpl_flex_array_clear中实现, 前面部分如存入元素类似. 差别在最后, 清除元素,即把元素所占用的空间全部写为 0x6c

Array 收缩
static int part_is_free(struct flex_array_part *part)
{
	int i;

	for (i = 0; i < sizeof(struct flex_array_part); i++)
		if (part->elements[i] != FLEX_ARRAY_FREE)
			return 0;
	return 1;
} 
int rpl_flex_array_shrink(struct flex_array *fa)
{
	struct flex_array_part *part;
	int part_nr;
	int ret = 0;

	if (!fa->total_nr_elements || !fa->element_size)
		return 0;
	if (elements_fit_in_base(fa))
		return ret;
	for (part_nr = 0; part_nr < FLEX_ARRAY_NR_BASE_PTRS; part_nr++) {
		part = fa->parts[part_nr];
		if (!part)
			continue;
		if (part_is_free(part)) {
			fa->parts[part_nr] = NULL;
			kfree(part);
			ret++;
		}
	}
	return ret;
}  

随着在FlexArray的各个part存入元素, FlexArray占用的空间(自身空间加上额外空间)会越来越大, 这时,可以通过以上函数将FlexArray收缩.收缩的原理是, 如果一个part中没有元素了,那么就可以释放该part.如何判断没有元素了呢, 自然就是所有内存都变为 0x6c了.

你可能感兴趣的:(openvswitch)