前言
《深入浅出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及其他模块了。