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