1,预留大页内存
检查CPU对大页的支持:对于2M大页:/proc/cpu . flags 上需要有pse标识,对于4M大页:/proc/cpu . flags 上需要有pdpe1gb标识。
编译内核时打开 CONFIG_HUGETLB_PAGE 和 CONFIG_HUGETLBFS 以开启内核的大页功能。
boot 时预留大页,即在/boot/grub2/grub.cfg中添加linux内核的启动参数:任何类型的大页都可以以这种方式预留,对于2M大页可以仅需指定:hugepages=1024,对于其他类型的大页需要同时指定hugepagesz 和 hugepages:"default_hugepagesz=1G hugepagesz=1G hugepages=4"。
启动后预留大页,echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages,亦可以使用echo 1024 > /proc/sys/vm/nr_hugepages。仅2M大页可以以这种方式预留。
预留的大页会平均分布在各个NUMA上。
2,挂载大页内存
mkdir /mnt/huge mount -t hugetlbfs nodev /mnt/huge
如果不是2M大页,则需要指定选项:mount -t hugetlbfs -o pagesize=1GB nodev /mnt/huge。
3,使用大页
3.1,dpdk对大页的使用
example:
fd = open("/mnt/huge/test", O_CREAT | O_RDWR); addr = mmap(0, MAP_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); munmap(addr, MAP_LENGTH); close(fd); unlink("/mnt/huge/test");
3.2,libhugetlbfs对大页的使用
需要将应用程序与库libhugetlb链接在一起。libhugetlb库对malloc()/free()等常用的内存相关的库函数进行了重载,以使得应用程序的数据可以放置在采用大页面的内存区域中,以提高内存性能。
4,查看大页信息
# cat /proc/meminfo |grep Huge AnonHugePages: 1196032 kB HugePages_Total: 60 #总共有多少大页 HugePages_Free: 57 #空闲的大页数 HugePages_Rsvd: 0 #保留的大页数 HugePages_Surp: 0 Hugepagesize: 1048576 kB #大页的大小,这里为1GB
5,参考文档
https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt
6,DPDK 大页 源码分析
6.1,eal_hugepage_info_init
每一种类型的大页的相关信息存储在internal_config.hugepage_info[xxx]数组中的一个元素内:
hugepage_sz:大页大小
hugedir:大页的挂载位置
num_pages[xxx]:在各个NUMA上大页的页数
lock_descriptor:大页挂载位置的文件描述符
遍历/sys/kernel/mm/hugepages/下每个目录项,每一项都是一种大页类型 huge page size:
比如上图表示系统只有一种类型的大页即1G大页。下一步就是确定它挂载在哪里。这可以通过读取/proc/mounts 再过滤 hugetlbfs得到。一项mount point里的options域如果包含pagesize字段则指明了大页的大小pagesize,如果没有包含pagesize字段则表明它使用了系统默认的大页大小 cat /proc/meminfo|grep Hugepagesize。
由上我们得到了每一种大页类型的挂载位置。读取/sys/kernel/mm/hugepages/hugepages-<page size>/nr_hugepages可以得到某种类型大页的总页数读取/sys/kernel/mm/hugepages/hugepages-<page size>/resv_hugepages可得到某种类型大页的预留页数用总页数减去预留页数就得到当前可用页数。
6.2,rte_eal_config_create
内存配置用结构体struct rte_mem_config描述,它以二进制的形式存放在文件/var/run/.rte_config中,初始化时再用mmap把这个文件映射到内存中来,映射的内存位置记录在rte_config.mem_config->mem_cfg_addr。
6.3,rte_eal_hugepage_init
本函数的主要目的是在/mnt/huge目录下创建若干个rtemap_xx文件,并为每个rtemap_xx文件做mmap映射,保证mmap后的虚拟地址与实际的物理地址是一样的。
用配置的内存大小internal_config.memory除以大页大小就得到需要创建的rtemap_xx文件个数,每一个rtemap_xx文件表示一个大页,用一个struct hugepage_file结构体表示:
orig_va : rtemap_xx文件mmap的虚拟地址
final_va : 第二次mmap映射rtemap_xx文件的虚拟地址
size : 大页大小
file_id : 页序号,就是rtemap_xx中的xx
filepath : 文件路径,即/dev/hugepages/rtemap_xx
map_all_hugepages(struct hugepage_file *hugepg_tbl, struct hugepage_info *hpi, int orig)为某种类型的大页下的每一个page创建rtemap_xx文件,并将其mmap当前进程空间中来。
fd = open("/dev/hugepages/rtemap_xx", O_CREAT | O_RDWR, 0600); hugepg_tbl [idx].orig_va = mmap(vma_addr, hugepage_sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, 0); flock(fd, LOCK_SH | LOCK_NB) vma_addr = (char *)vma_addr + hugepage_sz
(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂
更多DPDK相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加qun免费
领取,关注我持续更新哦! !
这里首先创建大页文件 "/dev/hugepages/rtemap_xx",然后将大页文件 "/dev/hugepages/rtemap_xx" mmap映射到当前进程空间中来,注意这里使用了MAP_POPULATE标识,表示mmap时就为其分配大页物理 内存,而不是等到page fault时再分配。flock在文件上设置共享的flock。上面的mmap输入了一个vma_addr,第一次调用mmap时vma_addr为NULL表示让系统选择起始虚拟地址,而后续的调用vma_addr为上一页page末尾地址,从而让所有的大页映射在一块连续的虚拟地址空间内。
find_physaddrs():mmap映射的大页内存的虚拟地址存放在hugepg_tbl [idx].orig_va,物理地址则存放在hugepg_tbl[i].physaddr,这是通过查找当前进程自己的页表/proc/self/pagemap得到的。
find_numasocket():获取每一个大页内存所在的NUMA node并存放在hugepg_tbl[i].socket_id。读取"/proc/self/numa_maps",当上一步获得的虚拟地址hugepg_tbl[i].orig_va等于"/proc/self/numa_maps"表项的虚拟地址时(第一个字段),字段N0=1 或 N1=1 就表示这个大页内存所在的NUMA node。
linux-EjSopu:/mnt/sdb/f00403445/software # cat /proc/33878/numa_maps | grep rtemap_0 7f2580000000 bind:0 file=/dev/hugepages/rtemap_0 huge dirty=1 N0=1 kernelpagesize_kB=1048576
map_all_hugepages (orig = 0):对struct hugepage_file *hugepage数组进行排序,按照物理内存地址从小到大的顺序排列。如果2个大页在物理地址空间上是连续的,则我们希望他们在映射的虚拟地址空间上也是连续的。 由此需要再调用一次map_all_hugepages将物理上连续的大页映射成虚拟地址空间上也连续的大页。
遍历大页,看看物理上连续的大页有多少个,得到一个vma_len即相应的连续虚拟地址空间的大小这些连续物理页的第一页的虚拟地址由内核选择,后续物理页的虚拟地址通过mmap()的第一个参数来指定这些物理地址空间和虚拟地址空间都连续的一片内存区称之为一个block。重映射之后大页新的虚拟地址保存在hugepg_tbl[i].final_va。
由上可知每一个rtemap_xx文件被映射了2次,这2次映射在不同的虚拟地址空间上,但它们使用的都是同一个物理大页。这里需要调用unmap_all_hugepages_orig()解除每个大页第一次映射的虚拟地址空间而仅保留第二次映射的虚拟地址空间。
计算每个socket上包含多少个hugepage,信息保存在internal_config.hugepage_info[xx].num_pages[socket]中前面的步骤中已将所有的大页映射到了当前进程的虚拟地址空间,但是当前进程只请internal_config.memory大小的内存,所以可以释放超出请求部分的大页内存。calc_num_pages_per_socket()用于计算每个socket需要多少个大页。unmap_unneeded_hugepages()则会释放每个socket上超出请求部分的大页。
每一种类型的大页对应一个struct hugepage_info结构每一个大页对应一个struct hugepage_file结构它们是一对多的关系。为了计算每种类型的大页在各个NUMA上的分布即internal_config.hugepage_info[ x ].num_pages[socket]只需遍历所有大页的struct hugepage_file *hp_file->socket_id。
create_shared_memory() -> copy_hugepages_to_shared_mem():为/var/run/.rte_hugepage_info文件mmap一段nr_hugepages * sizeof(struct hugepage_file)大小的内存块,并将前面创建的hugepage_file数组中的所有内容,都copy到这一块内存中,这片内存区域的地址保存在全局变量g_hugepage_table.hugepg_tbl中。
物理地址空间和虚拟地址空间都连续,同属一个socket,同属一个大页类型的多个大页组成一个memory segment,对应一个struct rte_memseg结构。