FlexArray
是ovs
中内核模块openvswitch.ko
中使用了一种数据结构, 意为灵活数组(Flexible Array
)
#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
中的字段意义如下:
FlexArray
中存放的每个元素的大小. FlexArray
每个元素大小是相同的, 在创建FlexArray
时确定FlexArray
中可以存放的元素的最大个数. 尽管是灵活数组, 但这个值在创建FlexArray
时确定,并且使用过程中不能改变.FlexArray
时确定, 一个part的大小为PAGE_SIZE, 比如在PAGE_SIZE = 4096 时, 如果要存放的元素大小为 256 Bytes, 则 elems_per_part = 4096 / 256 = 16, 即每个part能容纳16个元素FlexArray
还具有一个特性, 当 ( 元素最大个数 * 元素大小 <= 限定值 ) 时, FlexArray
将不再使用额外的part空间, 转而使用FlexArray
自己的空间.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空间是需要时才申请.
/*
向 @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的对应位置.
读取元素在rpl_flex_array_get中实现, 比较简单, 同样是计算位置后取出
#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
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了.