在使用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,更新或更舊的版本是否有這個問題,是否也能這么解,不得而知。