问题背景是X86板子上搭载DPDK的中间件启动后再起应用程序会出现OOM(Out Of Memory)
软件环境是linux 4.9.154 版本
中间件封装了DPDK
内存大小为8GB
kernel启动阶段会配置两个1G的大页供应用程序使用
cmdline参数: hugepagesz=1G hugepages=2
不加载application时用户态启动阶段会fork出4个进程在不同CPU上并行运行
加载application后经过top命令monitor内存使用率,发现在启动阶段会有boost然后又会恢复。
那么这个boost是怎么回事呢
内存的相关信息可以cat /proc/meminfo
该文件的源代码为fs/proc/meminfo.c:meminfo_proc_show() 其中有如下代码
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
"AnonHugePages: %8lu kB\n"
#endif
AnonHugePages值的打印为 106496kB
可以看出THP(透明大页)是打开了的
如下为不加载application时候的 /proc/meminfo
加载application后业务进程数会加倍,因此PageTables大小会超过2G
MemTotal: 7805672 kB
MemFree: 3047816 kB
MemAvailable: 3008244 kB
Buffers: 17428 kB
Cached: 995728 kB
SwapCached: 0 kB
Active: 615344 kB
Inactive: 341812 kB
Active(anon): 533864 kB
Inactive(anon): 269208 kB
Active(file): 81480 kB
Inactive(file): 72604 kB
Unevictable: 530908 kB
Mlocked: 530908 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 24 kB
Writeback: 0 kB
AnonPages: 471920 kB
Mapped: 186304 kB
Shmem: 859076 kB
Slab: 72920 kB
SReclaimable: 30476 kB
SUnreclaim: 42444 kB
KernelStack: 7844 kB
PageTables: 1038864 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 2854260 kB
Committed_AS: 2142024 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 0 kB
VmallocChunk: 0 kB
HardwareCorrupted: 0 kB
AnonHugePages: 106496 kB
ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 14504 kB
DirectMap2M: 2766848 kB
DirectMap1G: 5242880 kB
THP 倾向于创建2MB 的内存映射,即使必要的情况下,也不愿意把它们拆分回去,存在如下两个问题。
1. 如果应用程序需要访问几个字节的内容,传统上只会消耗一个4KB 的物理内存页,THP 开启的情况下,4KB 页将扩张到2MB,内存占用量增大512倍。网站提供了THP的BUG案例 https://bugzilla.kernel.org/show_bug.cgi?id=93111
2. 在频繁映射内存的情况下, THP会进行大量的拆分/合并内存区域的工作,alloc memory会延后,CPU使用率会增高。
由于在多个亲缘进程并行运行的场景下会有大量的写时复制工作,因此THP打开出现memory的boost实属正常现象。
需要做如下处理以关闭THP
echo never > /sys/kernel/mm/transparent_hugepage/enabled
接下来再看 /proc/meminfo 发现 PageTables 项依然有1G之多
我们用pmap命令查看一个业务进程发现虚拟空间大小
total 134796924K
其中有如下地址空间
00006a00c0000000 132120576K r---- [ anon ]
再用strace命令追踪发现起一个进程的时候会调用 mmap 映射32G虚拟空间总共调用四次
mmap(0x6a0040000000, 34359738368, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)
并且在之后的代码中有调用mlock
查看代码为mlockall(MCL_CURRENT)
这样就可以确认已经为mmap分配的虚拟空间准备好了虚拟页表
这时候主进程将会占用((32GB * 4 )/ 4KB)*8 = 256 MB 页表空间
我们知道,linux创建进程采用了写时复制的技术,只有write的时候才会复制一份物理空间,不然的话亲缘进程会共享物理地址。
那么当调用fork函数创建进程的时候这个页表空间是否会复制呢?
跟踪调用关系 fork -> _do_fork -> copy_process -> copy_mm -> dup_mm -> dup_mmap -> copy_page_range
fork函数确实会复制页表,这样256MB * 4 = 1GB也合理解释了PageTables过大的原因
如何解决这个问题呢?
我们在新版本的DPDK文档发现memory的配置方式更改了,需要在启动阶段加参数 --legacy-mem 减少启动阶段的匿名映射
https://doc.dpdk.org/guides-18.05/rel_notes/release_18_05.html
再来跟踪一下legacy的DPDK初始化代码,发现会以MAP_POPULATE参数调用mmap为文件映射通过预读的方式准备好页表
即使实际上不使用该虚拟空间,所需的虚拟页表依然存在,幸好占用的空间只有几十兆字节
梳理下用户态创建task的函数
最终都会调用kernel态的do_fork函数,如果参数有CLONE_VM那么到copy_mm的时候不会复制页表。
pthread_create函数调用clone的时候会在libc传入参数CLONE_VM
vfork用户态没有参数,但是在内核态会传入CLONE_VM
fork用户态无参,内核态仅仅设置SIGCHLD并没有CLONE_VM
所以只有fork函数会复制页表
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
/* can not support in nommu mode */
return -EINVAL;
#endif
}
SYSCALL_DEFINE0(vfork)
{
return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
0, NULL, NULL, 0);
}
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int __user *, child_tidptr,
unsigned long, tls)
{
return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);
}
ps:
启动阶段加上如下函数可继续减小 PageTables
mlockall(MCL_CURRENT | MCL_ONFAULT)
http://man7.org/linux/man-pages/man2/mlock.2.html
MCL_ONFAULT (since Linux 4.4)
Used together with MCL_CURRENT, MCL_FUTURE, or both.
Mark all current (with MCL_CURRENT) or future (with
MCL_FUTURE) mappings to lock pages when they are faulted
in. When used with MCL_CURRENT, all present pages are
locked, but mlockall() will not fault in non-present
pages. When used with MCL_FUTURE, all future mappings
will be marked to lock pages when they are faulted in,
but they will not be populated by the lock when the
mapping is created. MCL_ONFAULT must be used with either
MCL_CURRENT or MCL_FUTURE or both.