DPDK-内存管理分析二

前言

《DPDK-内存管理分析一》中分析了DPDK底层组织管理大页内存的heap、queue、elem及相关的alloc函数,但是对于提及的memzone还未做出分析,本文继续。

DPDK Version: 17.11.2

Date: 2018-06-21, Created by HRG

正文

本文根据实际使用经验结合rte_common_memzone.c等文件,分析memzone的相关实现。

首先说一下实际使用体验,memzone形式的内存块alooc时效率并不高,耗时较多。从代码实现来看,循环较多,确实memzone本身的机制就不是为了高效率alloc内存的。

看一下memzone的结构体, 包含了zone的name、起始IO addr、virt addr、长度、对应的大页大小等。

/**
 * A structure describing a memzone, which is a contiguous portion of
 * physical memory identified by a name.
 */
struct rte_memzone {

#define RTE_MEMZONE_NAMESIZE 32       /**< Maximum length of memory zone name.*/
	char name[RTE_MEMZONE_NAMESIZE];  /**< Name of the memory zone. */

	RTE_STD_C11
	union {
		phys_addr_t phys_addr;        /**< deprecated - Start physical address. */
		rte_iova_t iova;              /**< Start IO address. */
	};
	RTE_STD_C11
	union {
		void *addr;                   /**< Start virtual address. */
		uint64_t addr_64;             /**< Makes sure addr is always 64-bits */
	};
	size_t len;                       /**< Length of the memzone. */

	uint64_t hugepage_sz;             /**< The page size of underlying memory */

	int32_t socket_id;                /**< NUMA socket ID. */

	uint32_t flags;                   /**< Characteristics of this memzone. */
	uint32_t memseg_id;               /**< Memseg it belongs. */
} __attribute__((__packed__));

接下来,我们从rte_memzone_reserve()开始看起,用户程序会调用该函数申请memzone,此时不会指定align和bound,DPDK为提高内存读写效率,到处运用了内存对齐技术,但是暴露给客户的时候不会像他底层的实现那样需要到处留意,从这段就可以大概看到DPDK的封装确实很好,只暴露有必要暴露的。

const struct rte_memzone *
rte_memzone_reserve(const char *name, size_t len, int socket_id,
		    unsigned flags)
{
	return rte_memzone_reserve_thread_safe(name, len, socket_id,
					       flags, RTE_CACHE_LINE_SIZE, 0);
}

这里继续封装一层,上了一把锁,因此 memzone_reserve_aligned_thread_unsafe这个函数的实现将不会再考虑线程安全的问题了。

static const struct rte_memzone *
rte_memzone_reserve_thread_safe(const char *name, size_t len,
				int socket_id, unsigned flags, unsigned align,
				unsigned bound)
{
	rte_rwlock_write_lock(&mcfg->mlock);
	mz = memzone_reserve_aligned_thread_unsafe(
		name, len, socket_id, flags, align, bound);
	rte_rwlock_write_unlock(&mcfg->mlock);
	return mz;
}

继续分析 memzone_reserve_aligned_thread_unsafe()。首先检查memzone数量,这个最大值是用户编译DPDK前通过配置文件指定的,因此这里也可以看到,并不是DPDK绑定的所有大页内存都拿来做memzone了,还有其他的内存模块会使用到。

	/* no more room in config */
	if (mcfg->memzone_cnt >= RTE_MAX_MEMZONE) {
		RTE_LOG(ERR, EAL, "%s(): No more room in config\n", __func__);
		rte_errno = ENOSPC;
		return NULL;
	}

检查用户申请的name是否已经存在。这个函数里面的实现很简单,在memzone数组中一个一个memzone地找过去,一个一个比较这个name是否已经存在。这里就可以看到memzone的申请确实效率很低,不适合大数量多次数地申请,只适合对申请效率要求不高的程序,或者预先规划好在程序初始化过程中一次性把需要的memzone全部申请完。

	/* zone already exist */
	if ((memzone_lookup_thread_unsafe(name)) != NULL) {
		RTE_LOG(DEBUG, EAL, "%s(): memzone <%s> already exists\n",
			__func__, name);
		rte_errno = EEXIST;
		return NULL;
	}

如果用户不指定要求alloc的memzone的内存长度,DPDK会在所有heap中找个最大的memseg\elem给用户。find_heap_max_free_elem()这个函数效率更低,要每一个heap的每一个queue的每一个elem地遍历过去,全部遍历完了之后才能知道空闲的哪个elem才是长度最大的。

			requested_len = find_heap_max_free_elem(&socket_id, align);
			if (requested_len == 0) {
				rte_errno = ENOMEM;
				return NULL;
			}

如果用户指定了len,就以用户指定为准,如果没指定(即len=0),就以找到的最大长度来申请elem。

	/* allocate memory on heap */
	void *mz_addr = malloc_heap_alloc(&mcfg->malloc_heaps[socket], NULL,
			requested_len, flags, align, bound);

如果用户没有指定socket id的话,就到其他的heap中去申请一下内存,但这样存在一个问题,会出现跨socket访问内存的问题,这个对效率影响非常大,程序性能甚至会降到30%左右,直接打了3折。

	if ((mz_addr == NULL) && (socket_id == SOCKET_ID_ANY)) {
		/* try other heaps */
		for (i = 0; i < RTE_MAX_NUMA_NODES; i++) {
			if (socket == i)
				continue;
			mz_addr = malloc_heap_alloc(&mcfg->malloc_heaps[i],
					NULL, requested_len, flags, align, bound);
			if (mz_addr != NULL)
				break;
		}
	}

最后根据alloc到的elem和相关信息填写一下新的memzone,返回给用户。

	struct malloc_elem *elem = malloc_elem_from_data(mz_addr);

	/* fill the zone in config */
	mz = get_next_free_memzone();
	mcfg->memzone_cnt++;
	snprintf(mz->name, sizeof(mz->name), "%s", name);
	mz->iova = rte_malloc_virt2iova(mz_addr);
	mz->addr = mz_addr;
	mz->len = (requested_len == 0 ? elem->size : requested_len);
	mz->hugepage_sz = elem->ms->hugepage_sz;
	mz->socket_id = elem->ms->socket_id;
	mz->flags = 0;
	mz->memseg_id = elem->ms - rte_eal_get_configuration()->mem_config->memseg;
接下来看看memzone的释放流程。memset清空掉内存块后,最后调用rte_free。我们再下一篇文章再来分析这个rte_free的实现。
int
rte_memzone_free(const struct rte_memzone *mz)
{
	rte_rwlock_write_lock(&mcfg->mlock);

	idx = ((uintptr_t)mz - (uintptr_t)mcfg->memzone);
	idx = idx / sizeof(struct rte_memzone);

	addr = mcfg->memzone[idx].addr;
	if (addr == NULL)
		ret = -EINVAL;
	else if (mcfg->memzone_cnt == 0) {
		rte_panic("%s(): memzone address not NULL but memzone_cnt is 0!\n",
				__func__);
	} else {
		memset(&mcfg->memzone[idx], 0, sizeof(mcfg->memzone[idx]));
		mcfg->memzone_cnt--;
	}

	rte_rwlock_write_unlock(&mcfg->mlock);

	rte_free(addr);

	return ret;
}


猜你喜欢

转载自blog.csdn.net/u011272715/article/details/80767048