zl程序教程

您现在的位置是:首页 >  其他

当前栏目

VPPinfra---vec简介

2023-02-19 12:21:04 时间

本文主要介绍vec函数的基本使用及内存分布情况,以及在工作中遇到的一些坑来分享一下。

基本介绍

Vectoe支持动态调整数组的大小和支持用户自定义头部,在vppinfra 其他数据结构中(pool heap hash)都能看到有使用,是最基本的数据结构。vectorj基础函数库也一直在更新变化,目前已经支持多numa。有些api接口也进行一些性能上优化,加入SIMA技术。 Vec基本的内存分布

                 User header (optional, uword aligned)
                   Alignment padding (if needed)
                   Vector length in elements
 User's pointer -> Vector element 0
                   Vector element 1
                   ...
                   Vector element N-1
#define vec_add2(V,P,N)           vec_add2_ha(V,P,N,0,0)

vec的相关api返回的V指向向量第0个元素的指针。 为了避免内存分配器的抖动,通常不会直接把内存释放掉,而是将V的长度置位0,但是保留其已分配的内存,以供下次使用。下面介绍典型使用二种对齐模式:

1、缺省对齐模式及no header
 这种模式在vpp源码中使用比较多的。大致结构如下:
#define vec_add2_ha(V,P,N,H,A)              \
do {                    \
  word _v(n) = (N);                \
  word _v(l) = vec_len (V);              \
  V = _vec_resize ((V), _v(n), (_v(l) + _v(n)) * sizeof ((V)[0]), (H), (A));  \
  P = (V) + _v(l);                \
} while (0)
/** \brief Add N elements to end of vector V,
    return pointer to new elements in P. (no header, unspecified alignment)

    @param V pointer to a vector
    @param P pointer to new vector element(s)
    @param N number of elements to add
    @return V and P (value-result macro parameters)
*/
#define vec_add2(V,P,N)           vec_add2_ha(V,P,N,0,0)
2、制定对齐长度及header长度

目前在最新的vpp代码中未找到使用的地方,在老版本16.9中临时存储发包的mbuf指针有使用,tx_vectors当成一个环形队列来使用,tx_ring_hdr_t存储环形队列使用情况;具体代码如下:

易犯的错

vec结构是最基础的类型,也是初学者很容易犯的错误:

Allocation only increases,Vector origin pointer may changer,store indexes(not pointers)!

1、vector 原始指针可能会改变,存储索引而不是指针。 主要时因为vec_add函数底层支持动态扩容(内存不足时,会进行3/2倍的扩容),扩容会改变原始v指针的指向,这点在使用中必须注意。 2、第一种说法也不是完全成立的。这种说法成立的前提是vector操作中不调用vec_del函数,否则也不能存储索引。 通过下面vec_del1的实现就可以确认;当del 索引不是最后一个时,处理逻辑是将最后一个赋值到当前需要删除的下标,并更新vec长度。由此可见存储索引也不是绝对安全的。

/** \brief Delete the element at index I

    @param V pointer to a vector
    @param I index to delete
*/
#define vec_del1(v,i)        \
do {            \
  uword _vec_del_l = _vec_len (v) - 1;    \
  uword _vec_del_i = (i);      \
  if (_vec_del_i < _vec_del_l)      \
    (v)[_vec_del_i] = (v)[_vec_del_l];    \
  _vec_len (v) = _vec_del_l;      \
  CLIB_MEM_POISON(vec_end(v), sizeof ((v)[0])); \
} while (0)

3、由于vec_del1删除处理逻辑,在遍历删除多个数据时,需要使用vec_foreach_backwards的方式来删除。避免出现漏删除的情况。

/** \brief Vector iterator */
#define vec_foreach(var,vec) for (var = (vec); var < vec_end (vec); var++)
/** \brief Vector iterator (reverse) */
#define vec_foreach_backwards(var,vec) \
for (var = vec_end (vec) - 1; var >= (vec); var--)

在vec_del1函数最后有个调用宏定义CLIB_MEM_POISON,特意跟踪了一下,最终调用的__asan_poison_memory_region,应该是一个内存越界的检测工具。

CLIB_MEM_POISON(vec_end(v), sizeof ((v)[0]))

#define ASAN_POISON_MEMORY_REGION(addr, size) \
  __asan_poison_memory_region((addr), (size)
#define CLIB_MEM_POISON(a, s)   ASAN_POISON_MEMORY_REGION((a), (s))