FD.io VPP:vlib buffer pool(vlib_buffer) 内存初始化

Table of Contents

vlib buffer创建过程

vlib_buffer相关内存初始化

1、函数一开始就查询numa的个数

 2、遍历numa节点来初始化

3、查询系统大页大小。

4、接下来就是比较关键的常见buffer pool池初始化。

mempool create 流程

vpp mempool ops操作设置

dpdk_buffer_pool_init函数处理逻辑

1、只是调用rte_mempool_create_empty创建mempool结构体所需要的内存

2、填充mempool结构填充mempool对象缓冲头elt_list;

3、遍历所有buffer区,初始化vlib_buffer_t结构。

4、物理网卡存在时,映射DMA页面

参考文章

推荐阅读


 

目前阅读最新的vpp(20-1,显然目前的版本20.08+)代码,报文存储区域rte_mbuf已经不再通过DPDK结构来申请了,目前是在vpp自己管理的物理内存上直接申请而来。下面就简单了解下buffer创建的过程。

 

vlib buffer创建过程

vlib_buffer相关内存初始化


vlib_buffer初始化接口在文件\src\vlib\main.c上,由函数vlib_main->vlib_buffer_main_init 来完成vlib_buffer内存的初始化及创建vlib buffer pool 及其对应的缓存。

1、函数一开始就查询numa的个数

当前主要是适配了多numa节点场景,希望vlib_buffer内存不要跨numa节点访问,从而提升转发性能。

查询当前环境numa的使用情况,当前环境是单numa。

root@ubuntu:~# lscpu | grep "NUMA node"
NUMA node(s):        1
NUMA node0 CPU(s):   0-11
[root@localhost ~]# cat /sys/devices/system/node/online
0
[root@localhost ~]# cat /sys/devices/system/node/has_memory
0

 2、遍历numa节点来初始化

     由函数vlib_buffer_main_init_numa_node来完成。首先是计算buffer_size的大小。由三部分组成:ext_hdr_size (也就是rte_mbuf),vlib_buffer_t、及data数据部分大小

u32 buffer_size = CLIB_CACHE_LINE_ROUND (bm->ext_hdr_size +
             sizeof (vlib_buffer_t) +
             vlib_buffer_get_default_data_size
             (vm));

ext_hdr_size:是在plugins/dpdk.so 加载的时候完成对__vlib_buffer_external_hdr_size赋值,在函数vlib_buffer_main_init最开头完成赋值。

extern u16 __vlib_buffer_external_hdr_size;
#define VLIB_BUFFER_SET_EXT_HDR_SIZE(x) \
static void __clib_constructor \
vnet_buffer_set_ext_hdr_size() \
{ \
  if (__vlib_buffer_external_hdr_size) \
    clib_error ("buffer external header space already set"); \
  __vlib_buffer_external_hdr_size = CLIB_CACHE_LINE_ROUND (x); \
}
/*1、在文件F:src\plugins\dpdk\buffer.c中,在加载dpdk.so时完成对全局变量的
* __vlib_buffer_external_hdr_size的赋值。*/
VLIB_BUFFER_SET_EXT_HDR_SIZE (sizeof (struct rte_mempool_objhdr) +
            sizeof (struct rte_mbuf));
/*2、在vlib_buffer_main_init函数中完成ext_hdr_size 赋值*/            
bm->ext_hdr_size = __vlib_buffer_external_hdr_size;

default_data_size:默认是2048 可以由配置文件startup.conf中指定。

这里时early config,在buffer main init前面完成配置文件解析。

# buffers {
        ## Increase number of buffers allocated, needed only in scenarios with
        ## large number of interfaces and worker threads. Value is per numa node.
        ## Default is 16384 (8192 if running unpriviledged)
        # buffers-per-numa 128000
        ## Size of buffer data area
        ## Default is 2048
        # default data-size 2048
# }
/*这里时early config,在buffer main init前面完成配置文件解析*/
VLIB_EARLY_CONFIG_FUNCTION (vlib_buffers_configure, "buffers");

3、查询系统大页大小。

     vlib_buffer的内存是在大页上申请的。当前环境大页大小是1G。所以buffers-per-numa的数量最小是按照大页的大小来分配的。

[20:37:15]root:flowpp$ cat /proc/meminfo  | grep Hugepagesize
Hugepagesize:    1048576 kB
/*   创建大页内存 */
  n_pages = (buffers_per_numa - 1) / (pagesize / buffer_size) + 1;
  error = vlib_physmem_shared_map_create (vm, (char *) name,
            n_pages * pagesize,
            min_log2 (pagesize), numa_node,
            &physmem_map_index);

比如:当我们把buffers-per-numa的数量设置成5000时,show buffers 看到的仍是229824。

4、接下来就是比较关键的常见buffer pool池初始化。

/*创建bm->buffer_pools 并对相关结构进行初始化*/
 vec_add2_aligned (bm->buffer_pools, bp, 1, CLIB_LOG2_CACHE_LINE_BYTES);
  bp->start = start; /*大页内存起始地址*/
  bp->size = size;   /* 大页内存的大小*/
  bp->index = bp - bm->buffer_pools;
  bp->buffer_template.buffer_pool_index = bp->index;
  bp->buffer_template.ref_count = 1;
  bp->physmem_map_index = physmem_map_index;
  bp->name = format (0, "%s%c", name, 0);
  bp->data_size = data_size;
  bp->numa_node = m->numa_node;
  /*创建buffer_pools中每个worker核对应的pool cache,这里可以看到不会使用dpdk的ring队列。*/
  vec_validate_aligned (bp->threads, vec_len (vlib_mains) - 1,
      CLIB_CACHE_LINE_BYTES);
 /* 预先分配buffer 索引的内存。*/
  vec_validate_aligned (bp->buffers, m->n_pages * n_alloc_per_page,
      CLIB_CACHE_LINE_BYTES);
  /* 初始化未0*/
  vec_reset_length (bp->buffers);

  clib_spinlock_init (&bp->lock);
/* 遍历需要的大页的数量和 每页存储的多少个vlib_buffer的大小。
   获得buffer索引填充到 bp->buffers。*/
  for (j = 0; j < m->n_pages; j++)
    for (i = 0; i < n_alloc_per_page; i++)
      {
  u8 *p;
  u32 bi;

  p = m->base + (j << m->log2_page_size) + i * alloc_size;
  p += bm->ext_hdr_size;

  vlib_buffer_copy_template ((vlib_buffer_t *) p, &bp->buffer_template);

  bi = vlib_get_buffer_index (vm, (vlib_buffer_t *) p);
  /* 获取buffer 索引 填充到 bp->buffers中。*/
  vec_add1_aligned (bp->buffers, bi, CLIB_CACHE_LINE_BYTES);
  vlib_get_buffer (vm, bi);
      }
  bp->n_buffers = vec_len (bp->buffers);     
      

总结:到此vlib_buffer 所需要的内存已初始化完成,并且根据大页数量和内存内存大小来填充到bp->buffers区域中。下一节再将bp->buffers 和dpdk的 memepool结构进行关联,因为 vpp收发包都是依赖dpdk 的pmd驱动。

 

mempool create 流程


dpdk_config()  /* 配置文件解析dpdk相关参数*/
     |————— rte_eal_init() /*dpdk 初始化EAL环境*/ 
     |——————dpdk_buffer_pools_create() /*buffer pool 创建*/

vpp mempool ops操作设置

      vpp注册字节mempool 操作函数,后续创建mempool时,会通过name=“vpp”,索引到mempool ops的索引,设置操作入队与出队的操作接口。所以这里并不会使用dpdk的ring队列(应该是从vpp-19.04版本伴随着dpdk增加了ops操作接口后,修改成这样的。)

/*
*dpdk_buffer_pools_create (vlib_main_t * vm) 函数的开始有创建vpp自己的ops操作
*这里存在cache和no_cache两种。
 */ 
 struct rte_mempool_ops ops = { };
  strncpy (ops.name, "vpp", 4);
  ops.alloc = dpdk_ops_vpp_alloc;
  ops.free = dpdk_ops_vpp_free;
  ops.get_count = dpdk_ops_vpp_get_count;
  ops.enqueue = CLIB_MARCH_FN_POINTER (dpdk_ops_vpp_enqueue);
  ops.dequeue = CLIB_MARCH_FN_POINTER (dpdk_ops_vpp_dequeue);
  rte_mempool_register_ops (&ops);
  strncpy (ops.name, "vpp-no-cache", 13);
  ops.get_count = dpdk_ops_vpp_get_count_no_cache;
  ops.enqueue = CLIB_MARCH_FN_POINTER (dpdk_ops_vpp_enqueue_no_cache);
  ops.dequeue = dpdk_ops_vpp_dequeue_no_cache;
  rte_mempool_register_ops (&ops);

dpdk_buffer_pool_init函数处理逻辑

   这里有一点需要注意就是rte_mempool创建的时候有传入cache的大小(默认512大小)。而vpp注册的memepool ops函数中,也有相应大小的cache(最大支持4* 256)。这里应该有两级缓存。都是基于线程的。


i40e_recv_pkts() /*i40e PMD网卡收包函数*/
   |——rte_mbuf_raw_alloc(rxq->mp) /*从mempool池上获取rte_mbuf*/
        |-rte_mempool_get(mp, (void **)&m)
             /*首先通过线程ID从mempool的中获取的对应的线程缓存*/
             |-rte_mempool_default_cache(mp, rte_lcore_id())
             /* 先从cache中获取rte_mbuf,cache如果不够够,再通过ops获取*/
             |-rte_mempool_generic_get(mp, obj_table, n, cache)
                   /*cache中不够时,通过ops接口调用出队函数*/
                  |-ops = rte_mempool_get_ops(mp->ops_index);
                   ops->dequeue(mp, obj_table, n);
                   
/*接下来就是vpp的代码中的出队函数*/
CLIB_MULTIARCH_FN (dpdk_ops_vpp_dequeue) (struct rte_mempool * mp,
            void **obj_table, unsigned n)
    /* 这里有个特殊处理,每次取32个。具体意义是什么?*/
        |-n_alloc = vlib_buffer_alloc_from_pool (vm, bufs, batch_size,
               buffer_pool_index);
           |--/*通过buffer pool index找到对应buffer pool。*/
               bp = vec_elt_at_index (bm->buffer_pools, buffer_pool_index);
               /* 通过线程索引找到对应的缓存*/
               bpt = vec_elt_at_index (bp->threads, vm->thread_index);
               /* 获取缓存的中buffer的数量。*/
               len = vec_len (bpt->cached_buffers);
           |- /* 缓存不够时,再从全局大池中获取,有自旋锁。*/ 
              vlib_buffer_pool_get (vlib_main_t * vm, u8 buffer_pool_index, u32 * buffers,
          u32 n_buffers)

下面是dpdk_buffer_pool_init的函数处理逻辑;

1、只是调用rte_mempool_create_empty创建mempool结构体所需要的内存

分为三部分:mempool 头、基于core的buffer索引缓存区、pool私有数据。

2、填充mempool结构填充mempool对象缓冲头elt_list

将当前numa节点所有的mempool entry条目通过objhdr头串联起来;

下图是每个mempool entry的内存分布:

初始化rte_mbuf头。

  for (i = 0; i < bp->n_buffers; i++)
    {
      struct rte_mempool_objhdr *hdr;
      vlib_buffer_t *b = vlib_get_buffer (vm, bp->buffers[i]);
      struct rte_mbuf *mb = rte_mbuf_from_vlib_buffer (b);
      hdr = (struct rte_mempool_objhdr *) RTE_PTR_SUB (mb, sizeof (*hdr));
      hdr->mp = mp;
      hdr->iova = (iova_mode == RTE_IOVA_VA) ?
  pointer_to_uword (mb) : vlib_physmem_get_pa (vm, mb);
      STAILQ_INSERT_TAIL (&mp->elt_list, hdr, next);
      STAILQ_INSERT_TAIL (&nmp->elt_list, hdr, next);
      mp->populated_size++;
      nmp->populated_size++;
    }

  /* call the object initializers */
  rte_mempool_obj_iter (mp, rte_pktmbuf_init, 0);

3、遍历所有buffer区,初始化vlib_buffer_t结构。


for (i = 0; i < bp->n_buffers; i++)
    {
      vlib_buffer_t *b;
      b = vlib_buffer_ptr_from_index (buffer_mem_start, bp->buffers[i], 0);
      vlib_buffer_copy_template (b, &bp->buffer_template);
    }

4、物理网卡存在时,映射DMA页面

搞懂Linux零拷贝,DMA

------不懂机制?并将每页的映射情况,挂接到mempool 链表上。

STAILQ_INSERT_TAIL (&mp->mem_list, memhdr, next);

总结:粗略介绍了buffer pool的初始化流程、mempool 内存分布情况及收包的一些处理逻辑。对vpp buffer内存管理及缓存使用,有了大致的了解。

 

参考文章

vlib ----buffer pool 内存初始化(1)

vlib ----buffer pool 内存初始化(2)

FD.io VPP:探究分段场景下vlib_buf在收发包的处理

 

推荐阅读

ethtool 原理介绍和解决网卡丢包排查思路

DPDK之网卡收包流程

DPDK 网卡收包流程

FD.io VPP:探究分段场景下vlib_buf在收发包的处理

《深入浅出DPDK》读书笔记(二):网卡的读写数据操作

搞懂Linux零拷贝,DMA

《深入浅出DPDK》全书在线阅读(附录+推荐阅读)

深入理解 Cilium 的 eBPF(XDP)收发包路径:数据包在Linux网络协议栈中的路径

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页