在使用DPDK或者SPDK的时候,需要在进程刚启动的时候使用rte_eal_init初始化Environment Abstract Layer,应用进程会通过这个函数告诉EAL为它映射多大的hugepages,这通常通过ealargs的-m参数来指定,就像下面这样:
char *ealargs[] = { argv[0], // name NULL, // core_mask(to be decided) "-n 4", // number of memory channels per processor socket "--proc-type=auto", // The type of process instance. "-m 512", }; // .... int rc = rte_eal_init(sizeof(ealargs) / sizeof(ealargs[0]), ealargs); if (rc < 0) { // error handles }
需要注意的是,此处-m指定的hugepages的大小需要小于系统可用的hugepages大小(系统可用的Hugepages大小可以通过 `cat /proc/meminfo | grep Hugepage`查看。
但是!在系统运行一段时间后,就算是-m指定的hugepages的大小小于系统可用的Hugepages大小,EAL初始化的时候依然会panic掉,输出类似于下面的日志:
EAL: Detected 64 lcore(s) EAL: Auto-detected process type: PRIMARY EAL: No free hugepages reported in hugepages-1048576kB EAL: Can only reserve 927 pages from 4096 requested Current CONFIG_RTE_MAX_MEMSEG=256 is not enough Please either increase it or request less amount of memory. PANIC in rte_eal_init(): Cannot init memory
本文简要介绍导致该问题的原因以及一些我尝试过的解法。
原因:通过阅读dpdk的代码可以发现,导致这个问题的原因,是EAL在初始化的时候,无法从操作系统的Hugepages中找到连续个数的大页面,这个连续个数,由宏RTE_MAX_MEMSEG,这个宏可以在编译dpdk的时候修改(修改config/common_base中的CONFIG_RTE_MAX_MEMSEG的值)。官方解释:These pages can be located anywhere in physical memory, and, although the DPDK EAL will attempt to allocate memory in contiguous blocks, it is possible that the pages will not be contiguous. In this case, the application is not able to allocate big memory pools. (自http://dpdk.readthedocs.io/en/v16.04/linux_gsg/build_sample_apps.html#running-a-sample-application)
解法一:
清空系统的hugepages,然后在重启应用进程:
rm -rf /dev/huagepages/*; sh ./start_my_process.sh
一般情况下,这个解法就能解决。但是在有些场景下,这个方法无法解决。
解法二:
重新mount hagepage,然后再重启server:
umount /dev/hugepages rm -rf /dev/hugepages mkdir -p /dev/hugepages mount -t hugetlbfs nodev /dev/hugepages
still not work
解法三:
源代码中看到EAL在初始化的时候,会使用/var/run/.rte_config和/var/run/.rte_hugepage_info来读取hugepage的信息,怀疑是EAL重启时如果这两个文件存在的话,直接从它们里面读取旧的hugepage信息,于是尝试umount hugepage的同时删除这个文件:
清理hugepages环境:
HUGE_MOUNT_POINT=`cat /proc/mounts | grep hugetlbfs | cut -d ' ' -f 2` # Removes hugepage filesystem. remove_mnt_huge() { echo "Unmounting ${HUGE_MOUNT_POINT} and removing directory" grep -s '${HUGE_MOUNT_POINT}' /proc/mounts > /dev/null if [ $? -eq 0 ] ; then sudo umount ${HUGE_MOUNT_POINT} fi sleep 2 if [ -d ${HUGE_MOUNT_POINT} ] ; then sudo rm -rf ${HUGE_MOUNT_POINT} fi } # Removes all reserved hugepages. clear_huge_pages() { echo > .echo_tmp for d in /sys/devices/system/node/node? ; do echo "echo 0 > $d/hugepages/hugepages-${HUGE_PAGE_SIZE}/nr_hugepages" >> .echo_tmp done echo "Removing currently reserved hugepages" sudo sh .echo_tmp rm -f .echo_tmp rm -rf /var/run/.rte_config rm -rf /var/run/.rte_hugepage_info remove_mnt_huge }
重建hugepage:
create_hugepages() { echo 8192 > /proc/sys/vm/nr_hugepages echo "Creating /mnt/huge and mounting as hugetlbfs" sudo mkdir -p /dev/hugepages grep -s '/dev/hugepages' /proc/mounts > /dev/null if [ $? -ne 0 ] ; then sudo mount -t hugetlbfs nodev /dev/hugepages fi }
解法四:
看到EAL在初始化的时候,使用RTE_MAX_MEMSEG来判断连续大页面个数,可以将RTE_MAX_MEMSEG更改成一个更宽松的值(大于默认值256)重新编译DPDK。
实际上,这不是一个好的解法,因为你无法知道RTE_MAX_MEMSEG调整成多少合适,而且,每次调整宏,都需要重新编译DPDK,这会给调试工作带来不必要的时间负担。
解法五:
上面的解法一、二、三其实都基于一个假设,那就是你使用dpdk的进程独占了操作系统的Hugepages,一旦这个假设不成立,那么解法一、二、三就都不一定能成功解掉这个问题。解法五脱离这个假设,从整个系统的角度去考虑Hugepages的问题。
我使用的基于3.10内核的操作系统,默认打开了透明大页面功能(Transparent Hugepages),这个功能会使操作系统看到有大页面存在的时候,会在应用进程或者内核进程申请大块内存的时候,优先为它们分配大页面,大页面无法分配时,才会分配传统的4KB页面,对透明大页面感兴趣的同学请移步上两篇博文。
使用下面的命令查看哪些进程占用了系统的大页面:
# grep -e AnonHugePages /proc/*/smaps | awk '{ if($2>4) print $0} ' | awk -F "/" '{print $0; system("ps -fp " $3)} '
如果操作系统打开了透明大页面功能,同时应用进程机器上有其他耗内存的应用,上面的命令会看到很多大页面都被耗内存应用占用了。这也就是为什么使用dpdk的进程在启动的时候无法分配出连续的大页面的原因,大页面不是某个进程独占的,它是一个系统资源,如果透明大页面功能被打开,那么大页面会为所有进程服务。
到这里,问题就迎刃而解了,要么关闭透明大页面功能,这需要评估关闭后对其他应用的影响;要么暂时停掉那些使用了很多大页面的进程,重启使用dpdk的进程后,再启动它们(这不是一个好的解法);要么为系统分配更多的大页面;要么使用dpdk的进程通过-m申请更少的大页面。
在有透明大页面功能的操作系统中,有个内核进程khugepaged,它也会定期地回收大页面,整理大页面。
关闭透明大页面的方法:
echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag
本文所有的讨论和解法,都是基于dpdk-16.07,更新或更旧的版本是否有这个问题,是否也能这么解,不得而知。