接着上一节,我们来梳理一下mempool create 流程。
dpdk_config() /* 配置文件解析dpdk相关参数*/
|————— rte_eal_init() /*dpdk 初始化EAL环境*/
|——————dpdk_buffer_pools_create() /*buffer pool 创建*/
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);
这里有一点需要注意就是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内存管理及缓存使用,有了大致的了解。