1. 程式人生 > >openVswitch(OVS)原始碼分析--FlexArray

openVswitch(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的空間結構如下圖所示

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了.