dpdk mempool之代码流程
对于创建mempool的流程,dpdk的运行逻辑是:
rte_mempool_create_empty
rte_mempool_set_ops_byname
rte_mempool_populate_default
rte_mempool_obj_iter
所以本篇就主要分为这四个函数来讲解其处理逻辑和工作流程。
rte_mempool_create_empty
创建一个空的mp,输入参数分别为:
const char *name mp的名字
unsigned n obj的数量,在这由于是创建空的mp,所以这里只是把n保存到mp的size而已,不申请实际obj内存
unsigned elt_size obj的大小
unsigned cache_size local cache的数量
unsigned private_data_size 私有数据空间大小
int socket_id 向指定socket下的DDR申请内存
unsigned flags mp的flag
- 判断rte_mempool和rte_mempool_cache是否cache line对齐,如果不是,则在编译的时候报错;
- 获取rte_mempool_tailq链表头,申请struct rte_tailq_entry *te节点,用来保存已经申请到的mp,方便查找系统已经申请到的mp;
- cache_size合法性判断,cache_size不能大于RTE_MEMPOOL_CACHE_MAX_SIZE,同时cache_size的1.5倍不能大于总的obj个数n
- 使用rte_mempool_calc_obj_size计算obj的大小, obj的大小至少等于sz->header_size + sz->elt_size + sz->trailer_size同时需要考虑cache line和channels/ranks的对齐
- 计算一个空mp需要的大小mempool_size,
mempool_size = sizeof(struct rte_mempool)+
(sizeof(struct rte_mempool_cache) * RTE_MAX_LCORE) +
private_data_size - 使用rte_memzone_reserve申请mp需要的内存,同时初始化mp的变量,mp->local_cache设置为 mp+sizeof(struct rte_mempool)
到此为止,rte_mempool_create_empty已经完成.
rte_mempool_set_ops_byname
使用名字给mp获取ops,这里的ops初始化在dpdk mempool之概念学习已经介绍过了,这里就不赘述了.
rte_mempool_populate_default
填充mp,说白了就是申请obj,填充到mp里面,这里是连接rte_mempool_create_empty的作用。
- 初始化ops,前面对mp的ops操作都是注册和变量赋值,还没有真正调用函数,所以真正调用函数就是在这个函数里面实现。
mempool_ops_alloc_once
ops->alloc
.alloc = common_ring_alloc,
common_ring_alloc
rte_ring_create
大致调用逻辑如下,就是使用初始化mp的ring,将其保存到mp->pool_data里面。
-
输入参数合理性判断,比如mp->socket_id和mp->nb_mem_chunks
-
申请mem_size,一个mem_size包含n个obj的大小,最后使用rte_mempool_populate_iova给每一个memhdr和obj赋值,将其设置到mp对应的链表里面同时把obj enqueue到ring里面去,允许用户获取。
rte_mempool_populate_iova
rte_mempool_ops_populate
rte_mempool_op_populate_default
mempool_add_elem
rte_mempool_ops_enqueue_bulk
这个时候用户去从mp申请资源,走的通用接口如下:
rte_mempool_generic_get
get的时候,会优先从对应mp的cache里面获取obj,如下所示,
先从自己的core的cache拿到cache控制块&mp->local_cache[lcore_id],然后在__mempool_generic_get的时候,
判断cache->len当前的容量是否满足获取的个数,不够的话,再往cache里面增加obj,
最后再从cache里面获取obj。
static __rte_always_inline int
rte_mempool_get_bulk(struct rte_mempool *mp, void **obj_table, unsigned int n)
{
struct rte_mempool_cache *cache;
cache = rte_mempool_default_cache(mp, rte_lcore_id());
return rte_mempool_generic_get(mp, obj_table, n, cache);
}
static __rte_always_inline int
__mempool_generic_get(struct rte_mempool *mp, void **obj_table,
unsigned int n, struct rte_mempool_cache *cache)
{
int ret;
uint32_t index, len;
void **cache_objs;
/* No cache provided or cannot be satisfied from cache */
if (unlikely(cache == NULL || n >= cache->size))
goto ring_dequeue;
cache_objs = cache->objs;
/* Can this be satisfied from the cache? */
if (cache->len < n) {
/* No. Backfill the cache first, and then fill from it */
uint32_t req = n + (cache->size - cache->len);
/* How many do we require i.e. number to fill the cache + the request */
ret = rte_mempool_ops_dequeue_bulk(mp,
&cache->objs[cache->len], req);
if (unlikely(ret < 0)) {
/*
* In the offchance that we are buffer constrained,
* where we are not able to allocate cache + n, go to
* the ring directly. If that fails, we are truly out of
* buffers.
*/
goto ring_dequeue;
}
cache->len += req;
}
/* Now fill in the response ... */
for (index = 0, len = cache->len - 1; index < n; ++index, len--, obj_table++)
*obj_table = cache_objs[len];
cache->len -= n;
__MEMPOOL_STAT_ADD(mp, get_success, n);
return 0;
ring_dequeue:
/* get remaining objects from ring */
ret = rte_mempool_ops_dequeue_bulk(mp, obj_table, n);
if (ret < 0)
__MEMPOOL_STAT_ADD(mp, get_fail, n);
else
__MEMPOOL_STAT_ADD(mp, get_success, n);
return ret;
}
rte_mempool_generic_put
put操作和get操作相反,这里就不多说了。
rte_mempool_obj_iter
主要是运行用户自定义函数,初始化obj