本文共 20789 字,大约阅读时间需要 69 分钟。
今天遇到GPseg 的postgre进程被OS kill -6,查看系统的日志发现 page allocation failure.。
当时看到的服务器的内存情况如下图:
报错信息:
Jun 11 10:03:49 P1QMSSDW10 kernel: postgres: page allocation failure. order:1, mode:0x20 可以看出来是GFP_ATOMIC类型的申请,且order = 1(page = 2 )Jun 11 10:03:49 P1QMSSDW10 kernel: Pid: 9234, comm: postgres Tainted: G W --------------- 2.6.32-504.el6.x86_64 #1Jun 11 10:03:49 P1QMSSDW10 kernel: Call Trace:Jun 11 10:03:49 P1QMSSDW10 kernel:[ ] ? __alloc_pages_nodemask+0x74a/0x8d0Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? kmem_getpages+0x62/0x170Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? fallback_alloc+0x1ba/0x270Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? cache_grow+0x2cf/0x320Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? ____cache_alloc_node+0x99/0x160Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? kmem_cache_alloc+0x11b/0x190Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? sk_prot_alloc+0x48/0x1c0Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? sk_clone+0x22/0x2e0Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? inet_csk_clone+0x16/0xd0Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? tcp_create_openreq_child+0x23/0x470Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? tcp_v4_syn_recv_sock+0x4d/0x310Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? tcp_check_req+0x226/0x460Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? tcp_v4_do_rcv+0x35b/0x490Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? mod_timer+0x144/0x220Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? tcp_v4_rcv+0x522/0x900Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? ip_local_deliver_finish+0x0/0x2d0Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? ip_local_deliver_finish+0xdd/0x2d0Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? ip_local_deliver+0x98/0xa0Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? ip_rcv_finish+0x12d/0x440Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? ip_rcv+0x275/0x350Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? __netif_receive_skb+0x4ab/0x750Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? netif_receive_skb+0x58/0x60Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? napi_skb_finish+0x50/0x70Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? napi_gro_receive+0x39/0x50Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? ixgbe_clean_rx_irq+0x26a/0xc90 [ixgbe]Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? ixgbe_poll+0x453/0x7e0 [ixgbe]Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? net_rx_action+0x103/0x2f0Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? __do_softirq+0xc1/0x1e0Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? handle_IRQ_event+0x60/0x170Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? __do_softirq+0x11f/0x1e0Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? call_softirq+0x1c/0x30Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? do_softirq+0x65/0xa0Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? irq_exit+0x85/0x90Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? do_IRQ+0x75/0xf0Jun 11 10:03:49 P1QMSSDW10 kernel: [ ] ? ret_from_intr+0x0/0x11Jun 11 10:14:22 P1QMSSDW10 abrt[56116]: Not saving repeating crash in '/opt/greenplum/greenplum-db-4.3.27.0/bin/postgres'Jun 11 10:14:22 P1QMSSDW10 abrt[56118]: Not saving repeating crash in '/opt/greenplum/greenplum-db-4.3.27.0/bin/postgres'Jun 11 10:14:35 P1QMSSDW10 abrt[56119]: Saved core dump of pid 56117 (/opt/greenplum/greenplum-db-4.3.27.0/bin/postgres) to /var/spool/abrt/ccpp-2020-06-11-10:14:22-56117 (1550864384 bytes)
对这段page allocation failer 的解释
1)从第一行log“order:1, mode:0x20”可以看出来是GFP_ATOMIC类型的申请,且order = 1(page = 2 )
2)第一次内存申请尝试
在__alloc_pages_nodemask()里,首先调用 get_page_from_freelist() 尝试第一次申请,使用的标志位是 ALLOC_WMARK_LOW|ALLOC_CPUSET,它会对每个zone都做 zone_watermark_ok()的检查,使用的就是传进的watermark[low]阈值。 在zone_watermark_ok()里会考虑z->lowmem_reserve[],导致在normal上的申请不会落到低端zone。比如对于DMA32:free pages = 380032KB = 95008 pages < low(1000KB = 250 pages) + lowmem_reserve[normal](94940) = 95190
所以就认为DMA32也不平不ok,同理更用不了DMA的内存。
而对于normal自己内存来说,free pages = 56552 KB = 14138 pages,也不用考虑lowmem_reserve(0),但这时还会考虑申请order(1),减去order 0的12544个page后只剩 14138 - 12544 = 1594,也小于 low / 2 = (48788KB=12197pages) / 2 = 6098 pages。 所以初次申请尝试失败,进入__alloc_pages_slowpath() 尝试进行更为积极一些的申请。3)第二次内存申请尝试
__alloc_pages_slowpath()首先是通过 gfp_to_alloc_flags() 修改alloc_pages,设上更为强硬的标志位。这块根据原来的GFP_ATOMIC会设上 ALLOC_WMARK_MIN | ALLOC_HARDER | ALLOC_HIGH。但注意的是不会设上 ALLOC_NO_WATERMARKS 标志位。这个标志位不再判断zone的水位限制,属于优先级最高的申请,可以动用所有的reserve内存,但条件是(!in_interrupt() && ((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE)))),即要求不能在中断上下文,且是正在进行回收(例如kswapd)或者正在退出的进程。之后进入拿着新的alloc_pages重新进入get_page_from_pagelist() 尝试第二次申请,虽然有了 ALLOC_HARDER和ALLOC_HIGH,但是不幸的是在3个zone的zone_watermark_ok检查中还是都无法通过,例如对于DMA32:
free pages = 380032KB = 95008 pages因为设上了ALLOC_HIGH 所以会将得到的watermark[min]减半,即min = min/2 = 800K / 2 = 400K = 100pages而又因为设上了ALLOC_HARDER,会再将min砍去1/4,即min = 3 * min / 4 = 100 pages * 3 / 4 = 75 pages即便如此,min(75 pages) + lowmem_reserve[normal](94940) = 95015,仍大于free pages,仍认为无法分配内存,同理DMA也不不成功,而normal中 free pages里连续8K的页太少也无法满足分配
第二次失败后,由于没有ALLOC_NO_WATERMARK也不会进入__alloc_pages_high_priority 进行最高优先级的申请,同时由于是GFP_ATOMIC类型的分配不能阻塞回收或者进入OOM,因此就以申请失败告终。
遇到此种情况可以适当调高 min_free_kbytes 使kswapd较早启动回收,使系统一直留有较多的空闲内存,同时可以适度降低 lowmem_reserve_ratio(可选),使得内存不足的情况下(主要是normal zone)可以借用DMA32/DMA的内存救急(注意不能也不能过低)。来自
查看coreDump reason
ccpp-2020-06-11-10:14:22-56117]# more reason Process /opt/greenplum/greenplum-db-4.3.27.0/bin/postgres was killed by signal 6 (SIGABRT)
又是kill -6,已绝望,上次。
--------------------------update 2020年7月24日17:06:48
7/21 master 上发生kill -6 ???
7/22
同时10号节点也发生kill -6
数据库瞬断。 发生时取查看vmsta,看到的都是free 的内存先是到达低峰,Process 被OS kill后free 被腾出,可是bffer 和cache还有很多,为何不可用呢?
meminfo的解释Buffers %luRelatively temporary storage for raw disk blocks that shouldn't get tremendously larCached %luIn-memory cache for files read from the disk (the page cache). Doesn't include SwapC...SReclaimable %lu (since Linux 2.6.19)Part of Slab, that might be reclaimed, such as caches.SUnreclaim %lu (since Linux 2.6.19)Part of Slab, that cannot be reclaimed on memory pressure.Buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。Cached 是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘SReclaimable 是 Slab 的一部分。Slab 包括两部分,其中的可回收部分,用SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录。有两个问题:第一个问题,Buffer 的文档没有提到这是磁盘读数据还是写数据的缓存,而在很多网络搜索的结果中都会提到 Buffer 只是对将要写入磁盘数据的缓存。那反过来说,它会不会也缓存从磁盘中读取的数据呢?第二个问题,文档中提到,Cache 是对从文件读取数据的缓存,那么它是不是也会缓存写文件的数据呢?
看到德哥的这篇文章前来拜读。
Linux把物理内存划分为三个层次来管理:存储节点(Node)、管理区(Zone)和页面(Page)。
每一个Node,系统又将其分为多个Zone,x86架构下,node被分为ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM,而64位x86架构下有ZONE_DMA、ZONE_DMA32、ZONE_NORMAL。
它们之间的是树状的包含关系:
位于低位的ZONE,内存较少。
对于x86架构的系统来说,物理内存被划分为:
类型 | 地址范围 |
---|---|
ZONE_DMA | 前16MB内存 |
ZONE_NORMAL | 16MB – 896MB |
ZONE_HIGHMEM | 896 MB以上 |
ZONE_DMA位于低端的内存空间,用于某些旧的ISA设备。ZONE_NORMAL的内存直接映射到Linux内核线性地址空间的高端部分。
对于x86_64架构的系统来说:
类型 | 地址范围 |
---|---|
ZONE_DMA | 前16MB内存 |
ZONE_DMA32 | 16MB – 4G |
ZONE_NORMAL | 4G以上 |
和x86相比,多了ZONE_DMA32,少了ZONE_HIGHMEM.
分配物理内存时,kernel由high zone到low zone依次查找是否有足够的内存可以分配,找到可用的内存后映射到虚拟地址上。
但是低位的ZONE内存较少,所以如果低位的ZONE被占满之后,就算剩余的物理内存很大,还是会出现oom的情况。对于linux2.6来说,oom之后会根据score杀掉一个进程(oom的话题这里不展开了)。
这就引入了对低位ZONE的保护措施,lowmem_reserve_ratio。
如果操作系统的内存很多(比如64GB以上),可以将低位的ZONE的保护加到最大。
例如
sysctl -w vm.lowmem_reserve_ratio='1 1 1'echo "vm.lowmem_reserve_ratio='1 1 1'" >/etc/sysctl.conf
一台192G内存的机器,X86_64 OS,DMA=16MB,DMA32=4GB,NORMAL=剩余的内存。
那么当设置了vm.lowmem_reserve_ratio='1 1 1'后,每一个ZONE的protection数组将变成如下,数组中的对应位置的值,表示当对应的高级ZONE来这个ZONE申请内存时,这个ZONE的剩余内存必须大于这个保护值。
#free -m total used free shared buffers cachedMem: 193031 190099 2931 0 288 168877-/+ buffers/cache: 20933 172098Swap: 0 0 0#cat /proc/zoneinfo |grep prodma: protection: (0, 498744, 49625144, 49625144)dma32: protection: (0, 0, 49126400, 49126400)normal: protection: (0, 0, 0, 0)由于normal已经是最高的ZONE,所以没有更高的ZONE会来它这里申请内存,它的保护值都是0,表示都可以申请.
如DMA, 498744*4K表示DMA32来它这里申请的保护值,当DMA ZONE的剩余内存大于这个保护值时,才允许DMA32来它这里申请内存。
49625144*4K表示NORMAL来它这里申请的保护值,当DMA ZONE的剩余内存大于这个保护值时,才允许NORMAL来它这里申请内存。
这些值是怎么计算的? 根据对应保护位的高位ZONE的内存大小,除以被保护的系数。 如DMA的保护系数为1,即vm.lowmem_reserve_ratio的第一个元素值,那么DMA32来DMA申请内存的保护值=DMA32的内存大小/1,NORMAL来DMA申请内存的保护值=NORMAL的内存大小/1 。
每个ZONE的内存大小(单位4K):
#cat /proc/zoneinfo |grep spandma: spanned 4095dma32: spanned 1044480normal: spanned 49807360
例如,某台主机,发生了一些这样的报错,导致进程被OOM
Dec 20 17:14:57 server4 kernel: postmaster: page allocation failure. order:0, mode:0xd0 Dec 20 17:14:57 server4 kernel: [] __alloc_pages+0x2e1/0x2f7 Dec 20 17:14:57 server4 kernel: [ ] __get_free_pages+0x18/0x24 Dec 20 17:14:57 server4 kernel: [ ] kmem_getpages+0x1c/0xbb Dec 20 17:14:57 server4 kernel: [ ] cache_grow+0xab/0x138 Dec 20 17:14:57 server4 kernel: [ ] cache_alloc_refill+0x165/0x19d Dec 20 17:14:57 server4 kernel: [ ] kmem_cache_alloc+0x51/0x57 Dec 20 17:14:57 server4 kernel: [ ] mempool_alloc+0xb2/0x135 Dec 20 17:14:57 server4 kernel: [ ] autoremove_wake_function+0x0/0x2d Dec 20 17:14:57 server4 kernel: [ ] autoremove_wake_function+0x0/0x2d Dec 20 17:14:57 server4 kernel: [ ] bio_alloc+0x15/0x168 Dec 20 17:14:57 server4 kernel: [ ] sync_page_io+0x25/0xa2 Dec 20 17:14:57 server4 kernel: [ ] write_disk_sb+0x5a/0x86 Dec 20 17:15:01 server4 kernel: [ ] sync_sbs+0x22/0x2f Dec 20 17:15:01 server4 kernel: [ ] md_update_sb+0x84/0xc6 Dec 20 17:15:01 server4 kernel: [ ] md_write_start+0x5e/0x8c Dec 20 17:15:01 server4 kernel: [ ] make_request+0x22a/0x2b3 [raid1] Dec 20 17:15:01 server4 kernel: [ ] generic_make_request+0x18e/0x19e Dec 20 17:15:01 server4 kernel: [ ] submit_bio+0xca/0xd2 Dec 20 17:15:01 server4 kernel: [ ] test_set_page_writeback+0xad/0xe1 Dec 20 17:15:01 server4 kernel: [ ] swap_writepage+0x9a/0xa3 Dec 20 17:15:01 server4 kernel: [ ] pageout+0x8d/0xcc Dec 20 17:15:01 server4 kernel: [ ] shrink_list+0x207/0x3ed Dec 20 17:15:01 server4 kernel: [ ] __pagevec_release+0x15/0x1d Dec 20 17:15:01 server4 kernel: [ ] shrink_cache+0x1dd/0x34d Dec 20 17:15:01 server4 kernel: [ ] shrink_zone+0xa7/0xb6 Dec 20 17:15:01 server4 kernel: [ ] shrink_caches+0x4c/0x57 Dec 20 17:15:01 server4 kernel: [ ] try_to_free_pages+0xc3/0x1a7 Dec 20 17:15:01 server4 kernel: [ ] __alloc_pages+0x1fe/0x2f7 Dec 20 17:15:01 server4 kernel: [ ] __get_free_pages+0x18/0x24 Dec 20 17:15:01 server4 kernel: [ ] kmem_getpages+0x1c/0xbb Dec 20 17:15:01 server4 kernel: [ ] cache_grow+0xab/0x138
很可能就是低位内存不足导致的。
关于lowmem和highmem的定义在这里就不详细展开了,推荐两篇文章:
链接内讲的比较清楚,这里只说结论:
1. 当系统的物理内存 > 内核的地址空间范围时,才需要引入highmem概念。
2. x86架构下,linux默认会把进程的虚拟地址空间(4G)按3:1拆分,0~3G user space通过页表映射,3G-4G kernel space线性映射到进程高地址。就是说,x86机器的物理内存超过1G时,需要引入highmem概念。
3. 内核不能直接访问1G以上的物理内存(因为这部分内存没法映射到内核的地址空间),当内核需要访问1G以上的物理内存时,需要通过临时映射的方式,把高地址的物理内存映射到内核可以访问的地址空间里。
4. 当lowmem被占满之后,就算剩余的物理内存很大,还是会出现oom的情况。对于linux2.6来说,oom之后会根据score杀掉一个进程(oom的话题这里不展开了)。
5. x86_64架构下,内核可用的地址空间远大于实际物理内存空间,所以目前没有上面讨论的highmem的问题。
接下来的问题是,linux是如何实现highmem的概念的?
Linux把物理内存划分为三个层次来管理:存储节点(Node)、管理区(Zone)和页面(Page)。
在NUMA架构下,系统根据CPU的物理颗数,将物理内存分成对应的Node,这里也不展开了,可以参考
每一个Node,系统又将其分为多个Zone,x86架构下,node被分为ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM,而64位x86架构下有ZONE_DMA(ZONE_DMA32)和ZONE_NORMAL。它们之间的是树状的包含关系:
可以通过以下命令查看numa node信息:
$ numactl --hardware available: 2 nodes (0-1) node 0 cpus: 0 2 4 6 8 10 12 14 16 18 20 22 node 0 size: 8114 MB node 0 free: 2724 MB node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23 node 1 size: 8192 MB node 1 free: 818 MB node distances: node 0 1 0: 10 20 1: 20 10
可以通过以下命令查看zone信息,注意单位是page(4k):
$ cat /proc/zoneinfo Node 0, zone DMA pages free 3933 min 20 low 25 high 30 scanned 0 spanned 4095 present 3834
结合之前关于highmem的说明,对于x86架构的系统来说,物理内存被划分为:
类型 | 地址范围 |
---|---|
ZONE_DMA | 前16MB内存 |
ZONE_NORMAL | 16MB – 896MB |
ZONE_HIGHMEM | 896 MB以上 |
ZONE_DMA位于低端的内存空间,用于某些旧的ISA设备。ZONE_NORMAL的内存直接映射到Linux内核线性地址空间的高端部分。
对于x86_64架构的系统来说:
类型 | 地址范围 |
---|---|
ZONE_DMA | 前16MB内存 |
ZONE_DMA32 | 16MB – 4G |
ZONE_NORMAL | 4G以上 |
和x86相比,多了ZONE_DMA32,少了ZONE_HIGHMEM.
这里也不详细展开了,推荐两篇文章:
结论:
1. malloc属于glic的库函数,分配的是虚拟地址。
2. linux的malloc分配时,如果申请内存小于MMAP_THRESHOLD(默认128K),使用brk分配,否则使用mmap分配。
3. 通过brk分配的地址空间,当堆尾的空闲内存超过M_TRIM_THRESHOLD(默认128K)时,执行内存缩紧操作,这里指的也是虚拟地址。
4. 读写内存时,触发缺页中断,此时才会分配物理内存。
5. 分配物理内存时,kernel由high zone到low zone依次查找是否有足够的内存可以分配,找到可用的内存后映射到虚拟地址上。
6. 关于系统分配内存的详细介绍,可以参考:
这里主要是对vm.txt的解释,建议看原文
为什么要调整lowmem_reserve_ratio
在有高端内存的机器上,从低端内存域给应用层进程分配内存是很危险的,因为这些内存可以通过mlock()系统锁定,或者变成不可用的swap空间。
在有大量高端内存的机器上,缺少可以回收的低端内存是致命的。因此如果可以使用高端内存,Linux页面分配器不会使用低端内存。这意味着,内核会保护一定数量的低端内存,避免被用户空间锁定。“lowmem_reserve_ratio”参数可以调整内核对于lower zone的保护力度。
lowmem_reserve_ratio参数的含义
lowmem_reserve_ratio是一个数组,可以通过以下命令查看:
% cat /proc/sys/vm/lowmem_reserve_ratio 256 256 32
数组的长度=内存zone数量-1,其中每个数并不是绝对值,而是一个比例,代表1/256或1/32。
再次搬出zoneinfo,这里以zone_dma和zone_dma32举例:
$ cat /proc/zoneinfo Node 0, zone DMA pages free 3933 min 20 low 25 high 30 scanned 0 spanned 4095 present 3834 protection: (0, 3179, 7976, 7976) Node 0, zone DMA32 pages free 639908 min 4456 low 5570 high 6684 scanned 0 spanned 1044480 present 813848 protection: (0, 0, 4797, 4797) ……………………
linux尝试在zone中分配page时,会判断当前zone的page_free与高位zone的page_present的关系。
例如在dma中尝试申请dma32的page时,会计算一个protection值:
protection[dma,dma32] = zone_dma32.present/lowmem_reserve_ratio[dma(1)] = 813848/256 = 3179,这个结果对应上面DMA段中,protection数组的第二个元素。
然后需要比较zone_dma.free的值(3933) 与 protectiondma,dma32 + zone_dma.watermarkhigh的大小:
如果free>protection+watermark[high],则可以分配page;否则不能分配,内核继续查找下一个lower zone。
也就是说只有在higher zone内存不足时才会尝试从lower zone继续申请。
更详细的文档可以参考:
根据公式可以看出:
lowmem_reserve_ratio越大,低级的zone中被保护的内存就越小;
lowmem_reserve_ratio越小,低级的zone中被保护的内存就越大;
当lowmem_reserve_ratio=1(100%)时代表对low zone的最大保护强度。
For some specialised workloads on highmem machines it is dangerous for the kernel to allow process memory to be allocated from the "lowmem" zone. This is because that memory could then be pinned via the mlock() system call, or by unavailability of swapspace. And on large highmem machines this lack of reclaimable lowmem memory can be fatal. So the Linux page allocator has a mechanism which prevents allocations which _could_ use highmem from using too much lowmem. This means that a certain amount of lowmem is defended from the possibility of being captured into pinned user memory. (The same argument applies to the old 16 megabyte ISA DMA region. This mechanism will also defend that region from allocations which could use highmem or lowmem). The `lowmem_reserve_ratio' tunable determines how aggressive the kernel is in defending these lower zones. If you have a machine which uses highmem or ISA DMA and your applications are using mlock(), or if you are running with no swap then you probably should change the lowmem_reserve_ratio setting. The lowmem_reserve_ratio is an array. You can see them by reading this file. - % cat /proc/sys/vm/lowmem_reserve_ratio 256 256 32 - Note: # of this elements is one fewer than number of zones. Because the highest zone's value is not necessary for following calculation. But, these values are not used directly. The kernel calculates # of protection pages for each zones from them. These are shown as array of protection pages in /proc/zoneinfo like followings. (This is an example of x86-64 box). Each zone has an array of protection pages like this. - Node 0, zone DMA pages free 1355 min 3 low 3 high 4 : : numa_other 0 protection: (0, 2004, 2004, 2004) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pagesets cpu: 0 pcp: 0 : - These protections are added to score to judge whether this zone should be used for page allocation or should be reclaimed. In this example, if normal pages (index=2) are required to this DMA zone and watermark[WMARK_HIGH] is used for watermark, the kernel judges this zone should not be used because pages_free(1355) is smaller than watermark + protection[2] (4 + 2004 = 2008). If this protection value is 0, this zone would be used for normal page requirement. If requirement is DMA zone(index=0), protection[0] (=0) is used. zone[i]'s protection[j] is calculated by following expression. (i < j): zone[i]->protection[j] = (total sums of present_pages from zone[i+1] to zone[j] on the node) / lowmem_reserve_ratio[i]; (i = j): (should not be protected. = 0; (i > j): (not necessary, but looks 0) The default values of lowmem_reserve_ratio[i] are 256 (if zone[i] means DMA or DMA32 zone) 32 (others). As above expression, they are reciprocal number of ratio. 256 means 1/256. # of protection pages becomes about "0.39%" of total present pages of higher zones on the node. If you would like to protect more pages, smaller values are effective. The minimum value is 1 (1/1 -> 100%).
摘自
2.6内核的zone结构中一个成员变量 lowmem_reserve
struct zone { /* Fields commonly accessed by the page allocator */ /* zone watermarks, access with *_wmark_pages(zone) macros */ unsigned long watermark[NR_WMARK]; /* * We don't know if the memory that we're going to allocate will be freeable * or/and it will be released eventually, so to avoid totally wasting several * GB of ram we must reserve some of the lower zone memory (otherwise we risk * to run OOM on the lower zones despite there's tons of freeable ram * on the higher zones). This array is recalculated at runtime if the * sysctl_lowmem_reserve_ratio sysctl changes. */ unsigned long lowmem_reserve[MAX_NR_ZONES];
kernel在分配内存时,可能会涉及到多个zone,分配会尝试从zonelist第一个zone分配,如果失败就会尝试下一个低级的zone(这里的低级仅仅指zone内存的位置,实际上低地址zone是更稀缺的资源)。我们可以想像应用进程通过内存映射申请Highmem 并且加mlock分配,如果此时Highmem zone无法满足分配,则会尝试从Normal进行分配。这就有一个问题,来自Highmem的请求可能会耗尽Normal zone的内存,而且由于mlock又无法回收,最终的结果就是Normal zone无内存提供给kernel的正常分配,而Highmem有大把的可回收内存无法有效利用。
因此针对这个case,使得Normal zone在碰到来自Highmem的分配请求时,可以通过lowmem_reserve声明:可以使用我的内存,但是必须要保留lowmem_reserve[NORMAL]给我自己使用。
同样当从Normal失败后,会尝试从zonelist中的DMA申请分配,通过lowmem_reserve[DMA],限制来自HIGHMEM和Normal的分配请求。
/* * results with 256, 32 in the lowmem_reserve sysctl: * 1G machine -> (16M dma, 800M-16M normal, 1G-800M high) * 1G machine -> (16M dma, 784M normal, 224M high) * NORMAL allocation will leave 784M/256 of ram reserved in the ZONE_DMA * HIGHMEM allocation will leave 224M/32 of ram reserved in ZONE_NORMAL * HIGHMEM allocation will (224M+784M)/256 of ram reserved in ZONE_DMA * * TBD: should special case ZONE_DMA32 machines here - in those we normally * don't need any ZONE_NORMAL reservation */ #ifdef CONFIG_ZONE_DMA 256, #endif #ifdef CONFIG_ZONE_DMA32 256, #endif #ifdef CONFIG_HIGHMEM 32, #endif 32, };
如果不希望低级zone被较高级分配使用,那么可以设置系数为1,得到最大的保护效果
不过这个值的计算非常的奇怪,来自NORMAL的分配,lowmem_reserve[DMA] = normal_size / ratio,这里使用Normal zone size而不是DMA zone size,这点一直没有想明白。
此外,较新的内核源码目录中/Documentation/sysctl/vm.txt,对lowmem_reserve做了非常准确的描述。
GP 官方建议的OS 参数调整
The sysctl.conf parameters listed in this topic are for performance, optimization, and consistency in awide variety of environments. Change these settings according to your specific situation and setup.Set the parameters in the /etc/sysctl.conf file and reload with sysctl -p:# kernel.shmall = _PHYS_PAGES / 2 # See Shared Memory Pageskernel.shmall = 4000000000# kernel.shmmax = kernel.shmall * PAGE_SIZEkernel.shmmax = 500000000kernel.shmmni = 4096vm.overcommit_memory = 2 # See Segment Host Memoryvm.overcommit_ratio = 95 # See Segment Host Memorynet.ipv4.ip_local_port_range = 10000 65535 # See Port Settingskernel.sem = 500 2048000 200 40960kernel.sysrq = 1kernel.core_uses_pid = 1kernel.msgmnb = 65536kernel.msgmax = 65536kernel.msgmni = 2048net.ipv4.tcp_syncookies = 1net.ipv4.conf.default.accept_source_route = 0net.ipv4.tcp_max_syn_backlog = 4096net.ipv4.conf.all.arp_filter = 1net.core.netdev_max_backlog = 10000net.core.rmem_max = 2097152net.core.wmem_max = 2097152vm.swappiness = 10vm.zone_reclaim_mode = 0vm.dirty_expire_centisecs = 500vm.dirty_writeback_centisecs = 100vm.dirty_background_ratio = 0 # See System Memoryvm.dirty_ratio = 0vm.dirty_background_bytes = 1610612736vm.dirty_bytes = 4294967296
可以看到官方建议将vm.zone_reclaim_mode = 0 设置为0。
参考:
转载地址:http://vthqj.baihongyu.com/