Table of Contents
4、接下来就是比较关键的常见buffer pool池初始化。
1、只是调用rte_mempool_create_empty创建mempool结构体所需要的内存
2、填充mempool结构填充mempool对象缓冲头elt_list;
3、遍历所有buffer区,初始化vlib_buffer_t结构。
目前阅读最新的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页面
------不懂机制?并将每页的映射情况,挂接到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在收发包的处理》