一次linux平台X86架构下DPDK应用启动的OOM

问题背景是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.

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值