DPDK-大页内存使用分析

前言

《深入浅出DPDK》 2.7.4章节介绍大页内存使用方法如下:

#echo > 1024 /sys/kernel/mm/hugepages/hupages-2048KB/nr_hugepages 

#mkdir /mnt/huge

#mount -t hugetlbfs nodev /mnt/huge

DPDK程序运行时,会使用mmap ()系统调用把大野映射到用户态的虚拟地址空间,然后就可正常使用了。

本文通过源码分析DPDK在初始化过程中针对大页内存这一块进行的处理细节。

DPDK Version: 17.11.2

Date:2018-06-18, Created by HRG


正文

代码流程主要分两块,1、获取用户的大页内存配置;2、根据配置初始化并映射相关的大页内存

eal.c rte_eal_init()

primary进程调用eal_hugepage_info_init函数,获取用户配置的大页大小和页数。

	if (internal_config.no_hugetlbfs == 0 &&
			internal_config.process_type != RTE_PROC_SECONDARY &&
			eal_hugepage_info_init() < 0) {
		rte_eal_init_alert("Cannot get hugepage information.");
		rte_errno = EACCES;
		rte_atomic32_clear(&run_once);
		return -1;
	}

初始化内存模块,这里实现了大页内存到用户空间的映射,后面进程就可以正常使用大页内存了。

	if (rte_eal_memory_init() < 0) {
		rte_eal_init_alert("Cannot init memory\n");
		rte_errno = ENOMEM;
		return -1;
	}

eal_hugepage_info.c eal_hugepage_info_init()

最多支持3种大小的Page:2M、4M、1G ?忘了在哪段代码看到了,具体哪三个不记得了。


	//本函数参照下面这个命令进行说明
	//echo > 1024 /sys/kernel/mm/hugepages/hupages-2048KB/nr_hugepages 

	//打开用户echo配置对应的路径
	//static const char sys_dir_path[] = "/sys/kernel/mm/hugepages";
	dir = opendir(sys_dir_path);

	//循环读取路径名,因为用户可能echo了多次配置了多种大页大小
	for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir)) {
		struct hugepage_info *hpi;

		//通过文件夹前缀确定是否是配置的大页
		//const char dirent_start_text[] = "hugepages-";
		if (strncmp(dirent->d_name, dirent_start_text,
			    dirent_start_len) != 0)
			continue;

		//最多支持3种不同大小的大页
		//#define MAX_HUGEPAGE_SIZES 3
		if (num_sizes >= MAX_HUGEPAGE_SIZES)
			break;

		hpi = &internal_config.hugepage_info[num_sizes];

		//获取用户配置的大页大小
		hpi->hugepage_sz =
			rte_str_to_size(&dirent->d_name[dirent_start_len]);

		//读取/proc/mounts下的配置,检查pagesize,返回对应的mountpoint
		//根据命令可知mountpoint应该是/mnt/huge
		//#mount -t hugetlbfs nodev /mnt/huge
		hpi->hugedir = get_hugepage_dir(hpi->hugepage_sz);

		//这里open了挂载路径,没有close,要在外面调用本函数之后close掉
		hpi->lock_descriptor = open(hpi->hugedir, O_RDONLY);

		//清除掉前面生成的文件,只能清除其他程序未使用或者已经释放的文件
		if (clear_hugedir(hpi->hugedir) == -1)
			break;

		//不管用户配了多少种大小的大页
		//这里以最后一个为准覆盖了第一个的hpi的num_pages数组
		//等下面再进行排序操作
		//get_num_hugepages这个函数,感觉取的是hupages-2048KB路径下尚待映射的大页数?
		//(比如别的程序已经映射了一部分或者前面残留?)
		hpi->num_pages[0] = get_num_hugepages(dirent->d_name);

		num_sizes++;
	}
	closedir(dir);

	internal_config.num_hugepage_sizes = num_sizes;

	//按大页大小从达到小排序
	qsort(&internal_config.hugepage_info[0], num_sizes,
	      sizeof(internal_config.hugepage_info[0]), compare_hpi);

eal_common_memory.c rte_eal_memory_init

如果是primary进程就Init hugepage,否则attach就行。

	const int retval = rte_eal_process_type() == RTE_PROC_PRIMARY ?
			rte_eal_hugepage_init() :
			rte_eal_hugepage_attach();

eal_memory.c rte_eal_hugepage_init

第一次调用mmap,map_all_hugepages()通过控制mmap的第一个参数addr,保证mmap出来的virt addr是连续的。

这里的virt addr其实连续不连续没什么用?反正只是用来转换出对应的phy addr。

这次mmap没有考虑用户传给DPDK的baseaddr。

		/* 第一次mmap: map all hugepages available */
		pages_old = hpi->num_pages[0];
		pages_new = map_all_hugepages(&tmp_hp[hp_offset], hpi,
					      memory, 1); //注意最后一个参数传的是1

第一次mmap之后,获取virt addr对应的phy addr。

		if (phys_addrs_available) {
			/* find physical addresses for each hugepage */
			if (find_physaddrs(&tmp_hp[hp_offset], hpi) < 0) {
				RTE_LOG(DEBUG, EAL, "Failed to find phys addr "
					"for %u MB pages\n",
					(unsigned int)(hpi->hugepage_sz / 0x100000));
				goto fail;
			}
		}

根据phy addr的大小对所有hugepage进行排序。

		qsort(&tmp_hp[hp_offset], hpi->num_pages[0],
		      sizeof(struct hugepage_file), cmp_physaddr);

phy addr排序后 重新mmap一遍,以便得到virt addr和phy addr尽可能连续的共享内存。

我们知道每一个hugepage都是一块物理连续的内存。但是假如用户配了1024个2M的page,那能够保证这1024个page全都是连续的吗?从DPDK代码看,这是做不到的。后面也可以看到,DPDK会重新整理一下连续的代码片段(memseg)以便后续使用。

这次mmap时,会在phy addr连续的基础上,尽量也保证virt addr也是连续的,同时,本次mmap,会尽量保证virt addr在用户传进来的baseaddr基础上增长。

		/* remap all hugepages */
		if (map_all_hugepages(&tmp_hp[hp_offset], hpi, NULL, 0) !=
		    hpi->num_pages[0]) {
			RTE_LOG(ERR, EAL, "Failed to remap %u MB pages\n",
					(unsigned)(hpi->hugepage_sz / 0x100000));
			goto fail;
		}

把第一次mmap的unmap掉。

		/* unmap original mappings */
		if (unmap_all_hugepages_orig(&tmp_hp[hp_offset], hpi) < 0)
			goto fail;

前面针对每一个page都有一个对应的hugepage file结构体保存对应的virt addr/phy addr等信息,现在完成page的mmap后要创建一块共享内存保存这些信息,以便后续primary进程或者secondary进程使用。这块共享内存路径是/var/run/.rte_hugepage_info,DPDK运行后可以cat一下看里面有什么内容。

	/* create shared memory */
	hugepage = create_shared_memory(eal_hugepage_info_path(),
			nr_hugefiles * sizeof(struct hugepage_file));

.....

	/*
	 * copy stuff from malloc'd hugepage* to the actual shared memory.
	 * this procedure only copies those hugepages that have final_va
	 * not NULL. has overflow protection.
	 */
	if (copy_hugepages_to_shared_mem(hugepage, nr_hugefiles,
			tmp_hp, nr_hugefiles) < 0) {
		RTE_LOG(ERR, EAL, "Copying tables to shared memory failed!\n");
		goto fail;
	}

上面分析过DPDK也不能保证申请的1024个2M的page是物理内存连续的,下面这段代码就是将这些不全连续的内存整理起来,将连续的page看待成memory segment,即内存片段,保存在全局的mcfg->memseg数组中。

/* first memseg index shall be 0 after incrementing it below */
	j = -1;
	for (i = 0; i < nr_hugefiles; i++) {
		new_memseg = 0;

		/* if this is a new section, create a new memseg */
		if (i == 0)
			new_memseg = 1;
		else if (hugepage[i].socket_id != hugepage[i-1].socket_id)
			new_memseg = 1;
		else if (hugepage[i].size != hugepage[i-1].size)
			new_memseg = 1;

#ifdef RTE_ARCH_PPC_64
		/* On PPC64 architecture, the mmap always start from higher
		 * virtual address to lower address. Here, both the physical
		 * address and virtual address are in descending order */
		else if ((hugepage[i-1].physaddr - hugepage[i].physaddr) !=
		    hugepage[i].size)
			new_memseg = 1;
		else if (((unsigned long)hugepage[i-1].final_va -
		    (unsigned long)hugepage[i].final_va) != hugepage[i].size)
			new_memseg = 1;
#else
		else if ((hugepage[i].physaddr - hugepage[i-1].physaddr) !=
		    hugepage[i].size)
			new_memseg = 1;
		else if (((unsigned long)hugepage[i].final_va -
		    (unsigned long)hugepage[i-1].final_va) != hugepage[i].size)
			new_memseg = 1;
#endif

		if (new_memseg) {
			j += 1;
			if (j == RTE_MAX_MEMSEG)
				break;

			mcfg->memseg[j].iova = hugepage[i].physaddr;
			mcfg->memseg[j].addr = hugepage[i].final_va;
			mcfg->memseg[j].len = hugepage[i].size;
			mcfg->memseg[j].socket_id = hugepage[i].socket_id;
			mcfg->memseg[j].hugepage_sz = hugepage[i].size;
		}
		/* continuation of previous memseg */
		else {
#ifdef RTE_ARCH_PPC_64
		/* Use the phy and virt address of the last page as segment
		 * address for IBM Power architecture */
			mcfg->memseg[j].iova = hugepage[i].physaddr;
			mcfg->memseg[j].addr = hugepage[i].final_va;
#endif
			mcfg->memseg[j].len += mcfg->memseg[j].hugepage_sz;
		}
		hugepage[i].memseg_id = j;
	}

以上,初始化hugepage完成并保存相应的信息后,后续DPDK就可以将这些hugepage当作普通的共享内存使用了,至于DPDK怎么管理这些内存,就涉及到memzone及其他模块了。






猜你喜欢

转载自blog.csdn.net/u011272715/article/details/80721236
今日推荐