dpdk mempool之结构体学习

rte_mempool结构体

struct rte_mempool {
	/*
	 * Note: this field kept the RTE_MEMZONE_NAMESIZE size due to ABI
	 * compatibility requirements, it could be changed to
	 * RTE_MEMPOOL_NAMESIZE next time the ABI changes
	 */
	char name[RTE_MEMZONE_NAMESIZE]; /**< Name of mempool. */
	RTE_STD_C11
	union {
		void *pool_data;         /**< Ring or pool to store objects. */
		uint64_t pool_id;        /**< External mempool identifier. */
	};
	void *pool_config;               /**< optional args for ops alloc. */
	const struct rte_memzone *mz;    /**< Memzone where pool is alloc'd. */
	unsigned int flags;              /**< Flags of the mempool. */
	int socket_id;                   /**< Socket id passed at create. */
	uint32_t size;                   /**< Max size of the mempool. */
	uint32_t cache_size;
	/**< Size of per-lcore default local cache. */

	uint32_t elt_size;               /**< Size of an element. */
	uint32_t header_size;            /**< Size of header (before elt). */
	uint32_t trailer_size;           /**< Size of trailer (after elt). */

	unsigned private_data_size;      /**< Size of private data. */
	/**
	 * Index into rte_mempool_ops_table array of mempool ops
	 * structs, which contain callback function pointers.
	 * We're using an index here rather than pointers to the callbacks
	 * to facilitate any secondary processes that may want to use
	 * this mempool.
	 */
	int32_t ops_index;

	struct rte_mempool_cache *local_cache; /**< Per-lcore local cache */

	uint32_t populated_size;         /**< Number of populated objects. */
	struct rte_mempool_objhdr_list elt_list; /**< List of objects in pool */
	uint32_t nb_mem_chunks;          /**< Number of memory chunks */
	struct rte_mempool_memhdr_list mem_list; /**< List of memory chunks */

#ifdef RTE_LIBRTE_MEMPOOL_DEBUG
	/** Per-lcore statistics. */
	struct rte_mempool_debug_stats stats[RTE_MAX_LCORE];
#endif
}  __rte_cache_aligned;
  1. name

用来标识mem pool,创建mem pool的时候,dpdk会将其加入到rte_mempool_tailq链表里面,方便查找使用,查找的时候就是循环比较name,name相同就把mp返回. 同时这个name在创建mp的过程中,在其他衍生api需要名字的时候,也会复用这个name,比如ring的name参数.

/* search a mempool from its name */
struct rte_mempool *
rte_mempool_lookup(const char *name)
{
	struct rte_mempool *mp = NULL;
	struct rte_tailq_entry *te;
	struct rte_mempool_list *mempool_list;

	mempool_list = RTE_TAILQ_CAST(rte_mempool_tailq.head, rte_mempool_list);

	rte_rwlock_read_lock(RTE_EAL_MEMPOOL_RWLOCK);

	TAILQ_FOREACH(te, mempool_list, next) {
		mp = (struct rte_mempool *) te->data;
		if (strncmp(name, mp->name, RTE_MEMPOOL_NAMESIZE) == 0)
			break;
	}

	rte_rwlock_read_unlock(RTE_EAL_MEMPOOL_RWLOCK);

	if (te == NULL) {
		rte_errno = ENOENT;
		return NULL;
	}

	return mp;
}
  1. pool_data or pool_id
    用来保存创建ring时候得到的rte_ring,保存在mp->pool_data,内部使用,而外部想要获取到这个ring的句柄的话,直接使用pool_id.
static int
common_ring_alloc(struct rte_mempool *mp)
{
	int rg_flags = 0, ret;
	char rg_name[RTE_RING_NAMESIZE];
	struct rte_ring *r;

	ret = snprintf(rg_name, sizeof(rg_name),
		RTE_MEMPOOL_MZ_FORMAT, mp->name);
	if (ret < 0 || ret >= (int)sizeof(rg_name)) {
		rte_errno = ENAMETOOLONG;
		return -rte_errno;
	}

	/* ring flags */
	if (mp->flags & MEMPOOL_F_SP_PUT)
		rg_flags |= RING_F_SP_ENQ;
	if (mp->flags & MEMPOOL_F_SC_GET)
		rg_flags |= RING_F_SC_DEQ;

	/*
	 * Allocate the ring that will be used to store objects.
	 * Ring functions will return appropriate errors if we are
	 * running as a secondary process etc., so no checks made
	 * in this function for that condition.
	 */
	r = rte_ring_create(rg_name, rte_align32pow2(mp->size + 1),
		mp->socket_id, rg_flags);
	if (r == NULL)
		return -rte_errno;

	mp->pool_data = r;

	return 0;
}
  1. pool_config
    可选性,保存ops的配置参数,目前的dpdk版本,冒是没有用到。
  2. struct rte_memzone *mz
    用来保存mempool从哪里申请到的内存,释放的时候会用到。
rte_mempool_create
    rte_mempool_create_empty
        mz = rte_memzone_reserve
mp = mz->addr;
mp->mz = mz;       
  1. flags

mp使用的flag,主要有以下几种:
#define MEMPOOL_F_NO_SPREAD 0x0001
如果设置这个,dpdk这在创建mp的时候,会关闭内存对其。
#define MEMPOOL_F_NO_CACHE_ALIGN 0x0002
如果设置这个,dpdk这在创建mp的时候,会关闭内存cache align对其操作。
#define MEMPOOL_F_SP_PUT 0x0004
#define MEMPOOL_F_SC_GET 0x0008
这两个用来标识使用的ring是单生产还是单消费者,如果同时设置,则表示既是单生产又是单消费,
如果单独设置MEMPOOL_F_SP_PUT,则认为是单生产多消费模型,使用ring_sp_mc ops
如果单独设置MEMPOOL_F_SC_GET,则认为是多生产单消费模型,使用ring_mp_sc ops
如果单独设置MEMPOOL_F_SP_PUT and MEMPOOL_F_SC_GET,则认为是单生产单消费模型,使用ring_sp_sc ops
都没有设置,则认为是多生产多消费模型,使用ring_mp_mc
#define MEMPOOL_F_POOL_CREATED 0x0010 /**< Internal: pool is created. */
这个是内部使用,表示mp是否已经创建
#define MEMPOOL_F_NO_IOVA_CONTIG 0x0020
IOVA应该是iO virtual address,我对这个标志的理解是,如果我们不想要iova地址连续,则配置这个flag即可。

  1. socket_id标识从哪个socket申请内存
  2. size用来设置创建的mp里面存放的obj数量
  3. cache_size用来设置local cache的大小
    这里有两个限制:
    cache_size不能大于RTE_MEMPOOL_CACHE_MAX_SIZE
    cache_size的1.5倍不能大于总的个数size
  4. elt_size、header_size、trailer_size
    这三个变量表示一个mp里面obj的大小,如下图所示,
    header是rte_mempool_objhdr的大小,需要cache line对其,
    elt_size是obj真正的数据大小,需要8字节对齐,
    tailer是struct rte_mempool_objtlr的大小,再加上DIMM的ranks和channel对齐需要填充的字节数,保证每个elt能达到ranks和channel对齐。
    在这里插入图片描述
  5. private_data_size保存私有数据,dpdk网卡驱动用来保存一下结构体,主要是pktmbuf的room空间和私有数据。
sizeof(struct rte_pktmbuf_pool_private)
struct rte_pktmbuf_pool_private {
	uint16_t mbuf_data_room_size; /**< Size of data space in each mbuf. */
	uint16_t mbuf_priv_size;      /**< Size of private area in each mbuf. */
};
  1. ops_index注册ops的index
    上面介绍了mp注册ops的流程MEMPOOL_REGISTER_OPS,当创建mp的时候,
    会根据name来选择指定的ops,rte_mempool_set_ops_byname会保存对应ops的index到mp->ops_index里面。
rte_mempool_create
	if ((flags & MEMPOOL_F_SP_PUT) && (flags & MEMPOOL_F_SC_GET))
		ret = rte_mempool_set_ops_byname(mp, "ring_sp_sc", NULL);
	else if (flags & MEMPOOL_F_SP_PUT)
		ret = rte_mempool_set_ops_byname(mp, "ring_sp_mc", NULL);
	else if (flags & MEMPOOL_F_SC_GET)
		ret = rte_mempool_set_ops_byname(mp, "ring_mp_sc", NULL);
	else
		ret = rte_mempool_set_ops_byname(mp, "ring_mp_mc", NULL);

int
rte_mempool_set_ops_byname(struct rte_mempool *mp, const char *name,
	void *pool_config)
{
	struct rte_mempool_ops *ops = NULL;
	unsigned i;

	/* too late, the mempool is already populated. */
	if (mp->flags & MEMPOOL_F_POOL_CREATED)
		return -EEXIST;

	for (i = 0; i < rte_mempool_ops_table.num_ops; i++) {
		if (!strcmp(name,
				rte_mempool_ops_table.ops[i].name)) {
			ops = &rte_mempool_ops_table.ops[i];
			break;
		}
	}

	if (ops == NULL)
		return -EINVAL;

	mp->ops_index = i;
	mp->pool_config = pool_config;
	return 0;
}

这里之所以使用rte_mempool_ops_table的index而不直接保存函数地址,主要原因是考虑到主从进程的原因,主从进程堆栈是独立的,他们之间通信只能通过共享内存,而rte_mempool_ops_table的注册是在每个进程启动的时候就完成的,而创建mp只在主进程完成,所以这里直接共享index,从进程就可以直接使用,所以这里直接使用index而不用函数地址。

  1. local_cache 本地mp cache的指针,在创建mp的时候,会给所有core都创建一个,也就是说,是一个数组指针,每个core自己访问自己的地址即可。
struct rte_mempool_cache {
	uint32_t size;	      /**< Size of the cache */
	uint32_t flushthresh; /**< Threshold before we flush excess elements */
	uint32_t len;	      /**< Current cache count */
	/*
	 * Cache is allocated to this size to allow it to overflow in certain
	 * cases to avoid needless emptying of cache.
	 */
	void *objs[RTE_MEMPOOL_CACHE_MAX_SIZE * 3]; /**< Cache objects */
} __rte_cache_aligned;

struct rte_mempool_cache包含以下几个信息, size总的大小,flushthresh是冲洗多余元素之前的阈值,len是当前cache存在的数量,objs指针数组保存obj,最大支持512*3个。

  1. populated_size和elt_list
    elt_list是用来存放obj的链表,从上面可以知道,一个obj在内存上的分布式 hdr+obj+tail,
    而hdr的定义是struct rte_mempool_objhdr,而tail的定义为struct rte_mempool_objtlr
    populated_size 理论上是小于等于 mp->size。
    mp会将申请到的obj存放到elt list里面,而populated_size表示已经加入elt list的数量。
    释放mp的时候也需要把相应的obj从elt list里面释放出来,具体的函数如下:
static void
mempool_add_elem(struct rte_mempool *mp, __rte_unused void *opaque,
		 void *obj, rte_iova_t iova)
{
	struct rte_mempool_objhdr *hdr;
	struct rte_mempool_objtlr *tlr __rte_unused;

	/* set mempool ptr in header */
	hdr = RTE_PTR_SUB(obj, sizeof(*hdr));
	hdr->mp = mp;
	hdr->iova = iova;
	STAILQ_INSERT_TAIL(&mp->elt_list, hdr, next);
	mp->populated_size++;

#ifdef RTE_LIBRTE_MEMPOOL_DEBUG
	hdr->cookie = RTE_MEMPOOL_HEADER_COOKIE2;
	tlr = __mempool_get_trailer(obj);
	tlr->cookie = RTE_MEMPOOL_TRAILER_COOKIE;
#endif
}

static void
rte_mempool_free_memchunks(struct rte_mempool *mp)
{
	struct rte_mempool_memhdr *memhdr;
	void *elt;

	while (!STAILQ_EMPTY(&mp->elt_list)) {
		rte_mempool_ops_dequeue_bulk(mp, &elt, 1);
		(void)elt;
		STAILQ_REMOVE_HEAD(&mp->elt_list, next);
		mp->populated_size--;
	}

	while (!STAILQ_EMPTY(&mp->mem_list)) {
		memhdr = STAILQ_FIRST(&mp->mem_list);
		STAILQ_REMOVE_HEAD(&mp->mem_list, next);
		if (memhdr->free_cb != NULL)
			memhdr->free_cb(memhdr, memhdr->opaque);
		rte_free(memhdr);
		mp->nb_mem_chunks--;
	}
}
  1. nb_mem_chunks和mem_list
    这两个成员表示的意思和13基本一致,但是保存的对象却不一样,13保存的是obj的内存,而mem list保存的是内存块大小,这里dpdk把objsize这么大的内存按照物理内存连续去申请,如果能直接申请到objsize这么大的内存,物理上连续的话,则nb_mem_chunks等于1,否则就按照page大小去申请,把obj*size分为page大小的n等分,至少保证每个内存块在物理内存上市连续的,具体代码如下:
rte_mempool_create
	if (rte_mempool_populate_default(mp) < 0)
		goto fail;

rte_mempool_populate_default
		for (mz_id = 0, n = mp->size; n > 0; mz_id++, n -= ret)
  这里的for循环处理逻辑大致如下:
  调用rte_mempool_ops_calc_mem_size计算一次申请的内存块的大小,第一次直接全部申请,就是把pg_shift设置为0,表示直接mem_size = total_elt_sz * obj_num;
  如果能把这里得到的大小从rte_memzone_reserve_aligned申请到内存,及申请完成,
  否则如果系统设置了try_contig,则把申请内存调整为按照页大小来申请,下面调整rte_mempool_ops_calc_mem_size的pg_shift输入参数,
  		if (mz == NULL && try_contig && min_chunk_size <= pg_sz) {
			try_contig = false;
			flags &= ~RTE_MEMZONE_IOVA_CONTIG;

			mem_size = rte_mempool_ops_calc_mem_size(mp, n,
					pg_shift, &min_chunk_size, &align);
			if (mem_size < 0) {
				ret = mem_size;
				goto fail;
			}

			mz = rte_memzone_reserve_aligned(mz_name, mem_size,
				mp->socket_id, flags, align);
		}
以此类推,知道申请全部的men位置

简单的说,nb_mem_chunks的数量要远小于populated_size,一个nb_mem_chunks保护n个obj
而一个populated_size只有一个obj。

猜你喜欢

转载自blog.csdn.net/yaochuh/article/details/88397276