1.问题现象描述
服务器数据库被oomkill掉,但是mem查看只占用了不到60%。
2.问题分析
2.1.oom现象分析
从下面的日志信息,可以看到chmod进程是在内核采用GFP_KERNEL|__GFP_COMP分配order=3也就是2的3次方,8个连续页的时候,因进入__alloc_pages_slowpath慢路径后仍然分配不到内存,导致oom产生并选择dmserver进程进行kill掉了。
Nov 29 03:30:02 c7dm kernel: [3148724.799309] chmod invoked oom-killer: gfp_mask=0x6040c0(GFP_KERNEL|__GFP_COMP), nodemask=(null), order=3, oom_score_adj=0
Nov 29 03:30:02 c7dm kernel: [3148724.799311] chmod cpuset=/ mems_allowed=0
Nov 29 03:30:02 c7dm kernel: [3148724.799318] CPU: 6 PID: 3558577 Comm: chmod Kdump: loaded Not tainted 4.19.90-24.4.v2101.ky10.x86_64 #1
Nov 29 03:30:02 c7dm kernel: [3148724.799319] Hardware name: sangfor acloud/acloud, BIOS 1.13.0-20201211_142035 04/01/2014
Nov 29 03:30:02 c7dm kernel: [3148724.799319] Call Trace:
Nov 29 03:30:02 c7dm kernel: [3148724.799329] dump_stack+0x66/0x8b
Nov 29 03:30:02 c7dm kernel: [3148724.799333] dump_header+0x6e/0x299
Nov 29 03:30:02 c7dm kernel: [3148724.799335] oom_kill_process+0x259/0x280
Nov 29 03:30:02 c7dm kernel: [3148724.799337] ? oom_badness+0x23/0x130
Nov 29 03:30:02 c7dm kernel: [3148724.799339] out_of_memory+0x110/0x4f0
Nov 29 03:30:02 c7dm kernel: [3148724.799341] __alloc_pages_slowpath+0x9c4/0xd10
Nov 29 03:30:02 c7dm kernel: [3148724.799344] __alloc_pages_nodemask+0x245/0x280
Nov 29 03:30:02 c7dm kernel: [3148724.799347] kmalloc_order+0x14/0x40
Nov 29 03:30:02 c7dm kernel: [3148724.799349] kmalloc_order_trace+0x1d/0xa0
Nov 29 03:30:02 c7dm kernel: [3148724.799351] ksys_getdents64+0xbf/0x2f0
Nov 29 03:30:02 c7dm kernel: [3148724.799354] ? f_dupfd+0x65/0x80
Nov 29 03:30:02 c7dm kernel: [3148724.799355] ? filldir+0xe0/0xe0
Nov 29 03:30:02 c7dm kernel: [3148724.799357] ? __x64_sys_getdents64+0x16/0x20
Nov 29 03:30:02 c7dm kernel: [3148724.799358] __x64_sys_getdents64+0x16/0x20
Nov 29 03:30:02 c7dm kernel: [3148724.799361] do_syscall_64+0x5b/0x1d0
Nov 29 03:30:02 c7dm kernel: [3148724.799364] entry_SYSCALL_64_after_hwframe+0x44/0xa9
Nov 29 03:30:02 c7dm kernel: [3148724.799368] RIP: 0033:0x7f42dbfcf357
现在来分析这一现像是否为正常现像(因为oom是内核的正常的机制),GFP_KERNEL的首先内存管理区为ZONE_NORMAL。从日志可以看出该系统一共有3个内存管理区,分别是ZONE_DMA、ZONE_DMA32和ZONE_NORMAL。接下来分析这个3个内存管理区的内存使用情况:
1)ZONE_DMA的空闲内存是11164k, 最低水位是16k,低水位是28k, 高水位是40k, 空闲内存远大于高水位,所以kswapd是不会启动回收的,但是Linux内核有一个对低端内存管理区的保护机制,lowmem_reserve中有体现,加上保护机制,最低水位为:16K + 63043*4KB=252188KB,这个数据比空闲内存11164KB要大了,所以ZONE_DMA是不能分配成功的.
Nov 29 03:30:02 c7dm kernel: [3148724.799379] Mem-Info:
Nov 29 03:30:02 c7dm kernel: [3148724.799408] active_anon:8949722 inactive_anon:903391 isolated_anon:0#012 active_file:2573150 inactive_file:3332422 isolated_file:0#012 unevictable:8 dirty:75 writeback:0 unstable:0#012 slab_reclaimable:202130 slab_unreclaimable:43363#012 mapped:53869 shmem:63501 pagetables:26140 bounce:0#012 free:4722 free_pcp:237 free_cma:0
Nov 29 03:30:02 c7dm kernel: [3148724.799412] Node 0 active_anon:35798888kB inactive_anon:3613564kB active_file:10292600kB inactive_file:13329688kB unevictable:32kB isolated(anon):0kB isolated(file):0kB mapped:215476kB dirty:300kB writeback:0kB shmem:254004kB shmem_thp: 0kB shmem_pmdmapped: 0kB anon_thp: 10831872kB writeback_tmp:0kB unstable:0kB all_unreclaimable? no
Nov 29 03:30:02 c7dm kernel: [3148724.799413] Node 0 DMA free:11164kB min:16kB low:28kB high:40kB active_anon:4148kB inactive_anon:0kB active_file:504kB inactive_file:0kB unevictable:0kB writepending:0kB present:15992kB managed:15908kB mlocked:0kB kernel_stack:0kB pagetables:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB
Nov 29 03:30:02 c7dm kernel: [3148724.799416] lowmem_reserve[]: 0 2689 2817 63043 63043
- ZONE_DMA32的空闲内存是5996kb, 最低水位是2880k,低水位5632k, 高水位是8384k, 虽然空闲内存大于最低水位,但是同上考虑到lowmem_reserve机制,最低水位为:2880K + 60354*4K=244296K,这个数据比空闲内存5996KB要大,所以ZONE_DMA32也是不能分配成功的。
Nov 29 03:30:02 c7dm kernel: [3148724.799419] Node 0 DMA32 free:5996kB min:2880kB low:5632kB high:8384kB active_anon:2010016kB inactive_anon:177756kB active_file:174884kB inactive_file:274552kB unevictable:0kB writepending:0kB present:3129168kB managed:2801456kB mlocked:0kB kernel_stack:120kB pagetables:1084kB bounce:0kB free_pcp:260kB local_pcp:0kB free_cma:0kB
Nov 29 03:30:02 c7dm kernel: [3148724.799423] lowmem_reserve[]: 0 0 128 60354 60354
- 最后ZONE Normal的空闲内存是1760kb, 最低水位是136k,低水位是264k, 高水位是392k, 空闲内存高于高水位,所以kswapd是不会启动回收的。
Nov 29 03:30:02 c7dm kernel: [3148724.799426] Node 0 Normal free:1760kB min:136kB low:264kB high:392kB active_anon:33786032kB inactive_anon:3437860kB active_file:10115236kB inactive_file:13053364kB unevictable:32kB writepending:252kB present:63963136kB managed:137940kB mlocked:32kB kernel_stack:27084kB pagetables:103476kB bounce:0kB free_pcp:720kB local_pcp:0kB free_cma:0kB
Nov 29 03:30:02 c7dm kernel: [3148724.799429] lowmem_reserve[]: 0 0 0 481810 481810
继续看ZONE Normal的内存分布,空闲的1760kb内存大多为低阶的内存页,最大只可提供order=4(4KB*2^4=64KB)的连续内存,看似可以满足chmod进程申请的order=3的连续内存页。但观察这些空闲内存的migratetype,我们可以发现空闲的order=3,4的连续内存页的migratetype都为“H”(即MIGRATE_HIGHATOMIC)。
Nov 29 03:30:02 c7dm kernel: [3148724.799431] Node 0 DMA: 5*4kB (ME) 2*8kB (E) 1*16kB (M) 2*32kB (ME) 0*64kB 2*128kB (ME) 2*256kB (ME) 2*512kB (ME) 1*1024kB (E) 2*2048kB (M) 1*4096kB (M) = 11124kB
Nov 29 03:30:02 c7dm kernel: [3148724.799439] Node 0 DMA32: 3*4kB (H) 2*8kB (H) 12*16kB (H) 32*32kB (H) 25*64kB (H) 15*128kB (H) 5*256kB (H) 1*512kB (H) 0*1024kB 0*2048kB 0*4096kB = 6556kB
Nov 29 03:30:02 c7dm kernel: [3148724.799445] Node 0 Normal: 86*4kB (MH) 54*8kB (H) 28*16kB (H) 10*32kB (H) 3*64kB (H) 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 1736kB
而chmod进程申请order=3的内存标志位为gfp_mask=0x6040c0(GFP_KERNEL|__GFP_COMP) ,其中GFP_KERNEL= (__GFP_RECLAIM | __GFP_IO | __GFP_FS),这便使得该次内存申请其默认申请migratetype为“E”(即MIGRATE_RECLAIMABLE)的内存,而无法申请migratetype都为“H”的内存页。
注:针对高阶page的申请导致的碎片一直以来就是一个令人担忧的问题,所以Linux社区创建了一个新的page type,MIGRATE_HIGHATOMIC,用于改善碎片化过多、高阶内存页申请无法通过的问题。只有高阶或具备同等级别(如GFP_HIGH、GFP_ATOMIC)的内存申请的才能从该pageblock中申请page。
2.2.内存使用情况分析
上述分析了OOM问题的现象,触发OOM的问题原因为Node 0 Normal区域空闲内存大于High水位、不会触发内存回收,但其也无法提供对应chmod进程申请的order=3、migratetype为“E”的连续内存页,这是内存碎片化的一种体现。
与此同时,客户反馈触发oom时系统内存memused不到60%,这点应该是从sar的日志中查看的。
图2
这里要说明的是%memused并非我们通常理解的系统已使用内存,使用man sar查看sar手册中关于sar -r内存统计信息项的描述我们可以看到kbmemused=total memory - kbmemfree - kbbuffers -kbcached -kb slab,具体见下图3。
图3
这意味memused统计的是系统总内存减去空闲内存和buff/cache/slab等缓存大小,%memused不到60%并不是说空闲内存还有40%,因为其减去了缓存的占用。这点从上面sar的kbmemfree和kbcached项目及oom-killer打印的mem-info中都可以看到,发生OOM时kbmemfree已经不多了,但仍有许多缓存,可由于Node 0 Normal区域的free内存大于High水位,这些缓存并不会被收回为空闲内存,接下来我们继续分析这种情况发生的原因。
2.3. 内存碎片化分析
之前我们分析了OOM问题产生的原因——Node 0 Normal区域发生内存碎片化,该区域剩余内存大于了内存回收High水位、不会进行内存回收,但又无法提供出对应的非低阶连续内存页。
同时我们也发现了两个问题,一是通常Node 0 Normal区域管理的内存远大于DMA、DMA32区域,这意味Noraml区域的内存回收水位也会同比的高于DMA、DMA32区域。但实际查看oom-killer时打印的meminfo信息我们会发现Normal区域的内存回收水位异常的小,甚至低于DMA32区域。二是默认情况Normal区域就是最高阶内存区域了,但其仍然具有lowmem_reserve保护机制。
Nov 29 03:30:02 c7dm kernel: [3148724.799413] Node 0 DMA free:11164kB min:16kB low:28kB high:40kB active_anon:4148kB inactive_anon:0kB active_file:504kB inactive_file:0kB unevictable:0kB writepending:0kB present:15992kB managed:15908kB mlocked:0kB kernel_stack:0kB pagetables:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB
Nov 29 03:30:02 c7dm kernel: [3148724.799416] lowmem_reserve[]: 0 2689 2817 63043 63043
Nov 29 03:30:02 c7dm kernel: [3148724.799419] Node 0 DMA32 free:5996kB min:2880kB low:5632kB high:8384kB active_anon:2010016kB inactive_anon:177756kB active_file:174884kB inactive_file:274552kB unevictable:0kB writepending:0kB present:3129168kB managed:2801456kB mlocked:0kB kernel_stack:120kB pagetables:1084kB bounce:0kB free_pcp:260kB local_pcp:0kB free_cma:0kB
Nov 29 03:30:02 c7dm kernel: [3148724.799423] lowmem_reserve[]: 0 0 128 60354 60354
Nov 29 03:30:02 c7dm kernel: [3148724.799426] Node 0 Normal free:1760kB min:136kB low:264kB high:392kB active_anon:33786032kB inactive_anon:3437860kB active_file:10115236kB inactive_file:13053364kB unevictable:32kB writepending:252kB present:63963136kB managed:137940kB mlocked:32kB kernel_stack:27084kB pagetables:103476kB bounce:0kB free_pcp:720kB local_pcp:0kB free_cma:0kB
Nov 29 03:30:02 c7dm kernel: [3148724.799429] lowmem_reserve[]: 0 0 0 481810 481810
带着这几个疑点进入内存管理分析,我们分析到是openEuler内核(银河麒麟高级服务器操作系统内核基于openEuler内核开发)在优化内存时引入了一个bug(commit eb761d6521c32c006a4987260394a61c6684fb35: mm: parallelize deferred struct page initialization within each node),该bug会导致zone的managed_pages统计出现错位,而水位线的计算与这是相关的,比如一个zone实际上10G内存,但因为统计错位了,所以可以计算时可能认为只有100M,从而以100M去计算水位线,导致水位非常低。
具体查看系统的/proc/zoneinfo我们可以发现Node 0 Normal区域的managed出现统计异常,其managed远小于spanned、present;而Node 0 Movable区域这个本不该管理内存的区域managed异常的大,对应的内存回收水位也极高,见下图4。这就是上述BUG导致的managed_pages统计出现错位的问题了。
图4
managed_pages统计出现错位,导致Noraml区域内存回收水位低得不正常。而较低的内存水位线将使得机器在长时间高内存压力运行下频繁触发内存回收,但因为linux内存回收只会将空闲内存回收至high水位就停止,这将导致每次触发内存回收也并未真正回收多少空闲内存。因此出现managed_pages统计错位的zone将一直维持在一个没有多少空闲内存可用的境地,不仅容易出现内存不足进程申请内存失败的情况,频繁、少量的内存回收也将导致该zone出现内存碎片化,这都使得机器更容易触发OOM。
3.问题总结
综上所述,本次OOM产生的原因为低版本内核存在managed_pages统计出现错位的BUG,该BUG会导致zone的managed_pages统计出现错位,水位线大小受到影响。而较低的内存水位线又将导致系统内存出现碎片化趋势,由此导致最后该zone空闲内存大于内存High水位线,但都为低阶连续内存,无法满足进程高阶连续内存页申请。
目前麒麟及openEuler都已在官网发布了内核修复公告,麒麟内核在SP1 23.30、SP2 25.22版本得到修复,SP3内核不存在这个BUG。
更新地址:KYBA-202212-1011 - 国产操作系统、麒麟操作系统——麒麟软件官方网站
图5
4.BUG影响范围
1)只是针对x86架构,机器长时间运行后都有概率出现,水位太低,长时间运行后碎片化严重,机器使用久了,所有申请大块内存的都会有问题。
2)启动信息中含有如图6打印的就存在影响,在arm64的机器中没有看到相应打印。arm64的机器没有走这一块代码,X86的走了这块代码。
图6
3)同时也体现在zone 内存信息显示不正常上。如图7所示,查看/proc/zoneinfo,发现Node 0,zone Movable显示不正常,spanned ,present为0,但是managed 很大。正常应spanned=present=managed=0,或者spanned>present>managed。
图7
5.后续计划及建议
建议升级V10-SP1系统内核到23.30及其以上版本解决,V10-SP2系统升级到25.22及其以上版本解决版本。