Rsync 在单个 50 GB 文件上触发 Linux OOM 杀手

dat*_*ess 67 rsync oom oom-killer

我在 server_A 上有一个 50 GB 的文件,我正在将它复制到 server_B。我跑

server_A$ rsync --partial --progress --inplace --append-verify 50GB_file root@server_B:50GB_file
Run Code Online (Sandbox Code Playgroud)

Server_B 有 32 GB RAM 和 2 GB 交换。它主要是空闲的,应该有很多空闲内存。它有足够的磁盘空间。在大约 32 GB 时,传输中止,因为远程端关闭了连接。

Server_B 现在已断开网络。我们要求数据中心重新启动它。当我查看崩溃前的内核日志时,我看到它使用了 0 字节的交换空间,并且进程列表使用了很少的内存(rsync 进程被列为使用 600 KB 的 RAM),但是 oom_killer 是变得疯狂,日志中的最后一件事是它杀死了 metalog 的内核读取器进程。

这是内核 3.2.59,32 位(因此任何进程都不能映射超过 4 GB)。

就好像 Linux 更优先考虑缓存而不是长期运行的守护进程。是什么赋予了??我怎样才能阻止它再次发生?

这是 oom_killer 的输出:

Sep 23 02:04:16 [kernel] [1772321.850644] clamd invoked oom-killer: gfp_mask=0x84d0, order=0, oom_adj=0, oom_score_adj=0
Sep 23 02:04:16 [kernel] [1772321.850649] Pid: 21832, comm: clamd Tainted: G         C   3.2.59 #21
Sep 23 02:04:16 [kernel] [1772321.850651] Call Trace:
Sep 23 02:04:16 [kernel] [1772321.850659]  [<c01739ac>] ? dump_header+0x4d/0x160
Sep 23 02:04:16 [kernel] [1772321.850662]  [<c0173bf3>] ? oom_kill_process+0x2e/0x20e
Sep 23 02:04:16 [kernel] [1772321.850665]  [<c0173ff8>] ? out_of_memory+0x225/0x283
Sep 23 02:04:16 [kernel] [1772321.850668]  [<c0176438>] ? __alloc_pages_nodemask+0x446/0x4f4
Sep 23 02:04:16 [kernel] [1772321.850672]  [<c0126525>] ? pte_alloc_one+0x14/0x2f
Sep 23 02:04:16 [kernel] [1772321.850675]  [<c0185578>] ? __pte_alloc+0x16/0xc0
Sep 23 02:04:16 [kernel] [1772321.850678]  [<c0189e74>] ? vma_merge+0x18d/0x1cc
Sep 23 02:04:16 [kernel] [1772321.850681]  [<c01856fa>] ? handle_mm_fault+0xd8/0x15d
Sep 23 02:04:16 [kernel] [1772321.850685]  [<c012305a>] ? do_page_fault+0x20e/0x361
Sep 23 02:04:16 [kernel] [1772321.850688]  [<c018a9c4>] ? sys_mmap_pgoff+0xa2/0xc9
Sep 23 02:04:16 [kernel] [1772321.850690]  [<c0122e4c>] ? vmalloc_fault+0x237/0x237
Sep 23 02:04:16 [kernel] [1772321.850694]  [<c08ba7e6>] ? error_code+0x5a/0x60
Sep 23 02:04:16 [kernel] [1772321.850697]  [<c08b0000>] ? cpuid4_cache_lookup_regs+0x372/0x3b2
Sep 23 02:04:16 [kernel] [1772321.850700]  [<c0122e4c>] ? vmalloc_fault+0x237/0x237
Sep 23 02:04:16 [kernel] [1772321.850701] Mem-Info:
Sep 23 02:04:16 [kernel] [1772321.850703] DMA per-cpu:
Sep 23 02:04:16 [kernel] [1772321.850704] CPU    0: hi:    0, btch:   1 usd:   0
Sep 23 02:04:16 [kernel] [1772321.850706] CPU    1: hi:    0, btch:   1 usd:   0
Sep 23 02:04:16 [kernel] [1772321.850707] CPU    2: hi:    0, btch:   1 usd:   0
Sep 23 02:04:16 [kernel] [1772321.850709] CPU    3: hi:    0, btch:   1 usd:   0
Sep 23 02:04:16 [kernel] [1772321.850711] CPU    4: hi:    0, btch:   1 usd:   0
Sep 23 02:04:16 [kernel] [1772321.850713] CPU    5: hi:    0, btch:   1 usd:   0
Sep 23 02:04:16 [kernel] [1772321.850714] CPU    6: hi:    0, btch:   1 usd:   0
Sep 23 02:04:16 [kernel] [1772321.850716] CPU    7: hi:    0, btch:   1 usd:   0
Sep 23 02:04:16 [kernel] [1772321.850718] Normal per-cpu:
Sep 23 02:04:16 [kernel] [1772321.850719] CPU    0: hi:  186, btch:  31 usd:  70
Sep 23 02:04:16 [kernel] [1772321.850721] CPU    1: hi:  186, btch:  31 usd: 116
Sep 23 02:04:16 [kernel] [1772321.850723] CPU    2: hi:  186, btch:  31 usd: 131
Sep 23 02:04:16 [kernel] [1772321.850724] CPU    3: hi:  186, btch:  31 usd:  76
Sep 23 02:04:16 [kernel] [1772321.850726] CPU    4: hi:  186, btch:  31 usd:  29
Sep 23 02:04:16 [kernel] [1772321.850728] CPU    5: hi:  186, btch:  31 usd:  61
Sep 23 02:04:16 [kernel] [1772321.850731] CPU    7: hi:  186, btch:  31 usd:  17
Sep 23 02:04:16 [kernel] [1772321.850733] HighMem per-cpu:
Sep 23 02:04:16 [kernel] [1772321.850734] CPU    0: hi:  186, btch:  31 usd:   2
Sep 23 02:04:16 [kernel] [1772321.850736] CPU    1: hi:  186, btch:  31 usd:  69
Sep 23 02:04:16 [kernel] [1772321.850738] CPU    2: hi:  186, btch:  31 usd:  25
Sep 23 02:04:16 [kernel] [1772321.850739] CPU    3: hi:  186, btch:  31 usd:  27
Sep 23 02:04:16 [kernel] [1772321.850741] CPU    4: hi:  186, btch:  31 usd:   7
Sep 23 02:04:16 [kernel] [1772321.850743] CPU    5: hi:  186, btch:  31 usd: 188
Sep 23 02:04:16 [kernel] [1772321.850744] CPU    6: hi:  186, btch:  31 usd:  25
Sep 23 02:04:16 [kernel] [1772321.850746] CPU    7: hi:  186, btch:  31 usd: 158
Sep 23 02:04:16 [kernel] [1772321.850750] active_anon:117913 inactive_anon:9942 isolated_anon:0
Sep 23 02:04:16 [kernel] [1772321.850751]  active_file:106466 inactive_file:7784521 isolated_file:0
Sep 23 02:04:16 [kernel] [1772321.850752]  unevictable:40 dirty:0 writeback:61 unstable:0
Sep 23 02:04:16 [kernel] [1772321.850753]  free:143494 slab_reclaimable:128312 slab_unreclaimable:4089
Sep 23 02:04:16 [kernel] [1772321.850754]  mapped:6706 shmem:308 pagetables:915 bounce:0
Sep 23 02:04:16 [kernel] [1772321.850759] DMA free:3624kB min:140kB low:172kB high:208kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB isolated(anon):0kB isolate
d(file):0kB present:15808kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:0kB slab_reclaimable:240kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB writeback_tm
p:0kB pages_scanned:0 all_unreclaimable? yes
Sep 23 02:04:16 [kernel] [1772321.850763] lowmem_reserve[]: 0 869 32487 32487
Sep 23 02:04:16 [kernel] [1772321.850770] Normal free:8056kB min:8048kB low:10060kB high:12072kB active_anon:0kB inactive_anon:0kB active_file:248kB inactive_file:388kB unevictable:0kB isolated(anon)
:0kB isolated(file):0kB present:890008kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:0kB slab_reclaimable:513008kB slab_unreclaimable:16356kB kernel_stack:1888kB pagetables:3660kB unstable:0
kB bounce:0kB writeback_tmp:0kB pages_scanned:1015 all_unreclaimable? yes
Sep 23 02:04:16 [kernel] [1772321.850774] lowmem_reserve[]: 0 0 252949 252949
Sep 23 02:04:16 [kernel] [1772321.850785] lowmem_reserve[]: 0 0 0 0
Sep 23 02:04:16 [kernel] [1772321.850788] DMA: 0*4kB 7*8kB 3*16kB 6*32kB 4*64kB 6*128kB 5*256kB 2*512kB 0*1024kB 0*2048kB 0*4096kB = 3624kB
Sep 23 02:04:16 [kernel] [1772321.850795] Normal: 830*4kB 80*8kB 0*16kB 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 1*4096kB = 8056kB
Sep 23 02:04:16 [kernel] [1772321.850802] HighMem: 13*4kB 14*8kB 2*16kB 2*32kB 0*64kB 0*128kB 2*256kB 2*512kB 3*1024kB 0*2048kB 136*4096kB = 561924kB
Sep 23 02:04:16 [kernel] [1772321.850809] 7891360 total pagecache pages
Sep 23 02:04:16 [kernel] [1772321.850811] 0 pages in swap cache
Sep 23 02:04:16 [kernel] [1772321.850812] Swap cache stats: add 0, delete 0, find 0/0
Sep 23 02:04:16 [kernel] [1772321.850814] Free swap  = 1959892kB
Sep 23 02:04:16 [kernel] [1772321.850815] Total swap = 1959892kB
Sep 23 02:04:16 [kernel] [1772321.949081] 8650736 pages RAM
Sep 23 02:04:16 [kernel] [1772321.949084] 8422402 pages HighMem
Sep 23 02:04:16 [kernel] [1772321.949085] 349626 pages reserved
Sep 23 02:04:16 [kernel] [1772321.949086] 7885006 pages shared
Sep 23 02:04:16 [kernel] [1772321.949087] 316864 pages non-shared
Sep 23 02:04:16 [kernel] [1772321.949089] [ pid ]   uid  tgid total_vm      rss cpu oom_adj oom_score_adj name
            (rest of process list omitted)
Sep 23 02:04:16 [kernel] [1772321.949656] [14579]     0 14579      579      171   5       0             0 rsync
Sep 23 02:04:16 [kernel] [1772321.949662] [14580]     0 14580      677      215   5       0             0 rsync
Sep 23 02:04:16 [kernel] [1772321.949669] [21832]   113 21832    42469    37403   0       0             0 clamd
Sep 23 02:04:16 [kernel] [1772321.949674] Out of memory: Kill process 21832 (clamd) score 4 or sacrifice child
Sep 23 02:04:16 [kernel] [1772321.949679] Killed process 21832 (clamd) total-vm:169876kB, anon-rss:146900kB, file-rss:2712kB
Run Code Online (Sandbox Code Playgroud)

这是以非 root 用户身份重复我的 rsync 命令后的“顶部”输出:

top - 03:05:55 up  8:43,  2 users,  load average: 0.04, 0.08, 0.09
Tasks: 224 total,   1 running, 223 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0% us,  0.0% sy,  0.0% ni, 99.9% id,  0.0% wa,  0.0% hi,  0.0% si
Mem:  33204440k total, 32688600k used,   515840k free,   108124k buffers
Swap:  1959892k total,        0k used,  1959892k free, 31648080k cached
Run Code Online (Sandbox Code Playgroud)

以下是 sysctl vm 参数:

# sysctl -a | grep '^vm'
vm.overcommit_memory = 0
vm.panic_on_oom = 0
vm.oom_kill_allocating_task = 0
vm.oom_dump_tasks = 1
vm.overcommit_ratio = 50
vm.page-cluster = 3
vm.dirty_background_ratio = 1
vm.dirty_background_bytes = 0
vm.dirty_ratio = 0
vm.dirty_bytes = 15728640
vm.dirty_writeback_centisecs = 500
vm.dirty_expire_centisecs = 3000
vm.nr_pdflush_threads = 0
vm.swappiness = 60
vm.lowmem_reserve_ratio = 256   32      32
vm.drop_caches = 0
vm.min_free_kbytes = 8192
vm.percpu_pagelist_fraction = 0
vm.max_map_count = 65530
vm.laptop_mode = 0
vm.block_dump = 0
vm.vfs_cache_pressure = 100
vm.legacy_va_layout = 0
vm.stat_interval = 1
vm.mmap_min_addr = 4096
vm.vdso_enabled = 2
vm.highmem_is_dirtyable = 0
vm.scan_unevictable_pages = 0
Run Code Online (Sandbox Code Playgroud)

the*_*bit 182

因此,让我们阅读 oom-killer 输出,看看可以从中学到什么。

在分析 OOM 杀手日志时,重要的是查看是什么触发了它。您日志的第一行为我们提供了一些线索:

[内核] [1772321.850644] clamd 调用 oom-killer: gfp_mask=0x84d0, order=0

order=0告诉我们正在请求多少内存。内核的内存管理只能管理 2 的幂的页数,所以 clamd 请求了 2 0页内存或 4KB。

GFP_MASK(获取空闲页面掩码)的最低两位构成所谓的区域掩码, 告诉分配器从哪个区域获取内存

Flag            value      Description
                0x00u      0 implicitly means allocate from ZONE_NORMAL
__GFP_DMA       0x01u      Allocate from ZONE_DMA if possible
__GFP_HIGHMEM   0x02u      Allocate from ZONE_HIGHMEM if possible
Run Code Online (Sandbox Code Playgroud)

内存区域是一个主要出于兼容性原因而创建的概念。在简化的视图中,x86 内核有三个区域:

Memory range   Zone       Purpose 

0-16 MB        DMA        Hardware compatibility (devices)
16 - 896 MB    NORMAL     space directly addressable by the Kernel, userland 
> 896 MB       HIGHMEM    userland, space addressable by the Kernel via kmap() calls
Run Code Online (Sandbox Code Playgroud)

在您的情况下,区域掩码为 0,这意味着 clamd 正在从 ZONE_NORMAL.

其他标志正在解决

/*
 * Action modifiers - doesn't change the zoning
 *
 * __GFP_REPEAT: Try hard to allocate the memory, but the allocation attempt
 * _might_ fail.  This depends upon the particular VM implementation.
 *
 * __GFP_NOFAIL: The VM implementation _must_ retry infinitely: the caller
 * cannot handle allocation failures.
 *
 * __GFP_NORETRY: The VM implementation must not retry indefinitely.
 */
#define __GFP_WAIT      0x10u   /* Can wait and reschedule? */
#define __GFP_HIGH      0x20u   /* Should access emergency pools? */
#define __GFP_IO        0x40u   /* Can start physical IO? */
#define __GFP_FS        0x80u   /* Can call down to low-level FS? */
#define __GFP_COLD      0x100u  /* Cache-cold page required */
#define __GFP_NOWARN    0x200u  /* Suppress page allocation failure warning */
#define __GFP_REPEAT    0x400u  /* Retry the allocation.  Might fail */
#define __GFP_NOFAIL    0x800u  /* Retry for ever.  Cannot fail */
#define __GFP_NORETRY   0x1000u /* Do not retry.  Might fail */
#define __GFP_NO_GROW   0x2000u /* Slab internal usage */
#define __GFP_COMP      0x4000u /* Add compound page metadata */
#define __GFP_ZERO      0x8000u /* Return zeroed page on success */
#define __GFP_NOMEMALLOC 0x10000u /* Don't use emergency reserves */
#define __GFP_NORECLAIM  0x20000u /* No realy zone reclaim during allocation */
Run Code Online (Sandbox Code Playgroud)

根据Linux的MM文件,所以你requst先后为标志GFP_ZEROGFP_REPEATGFP_FSGFP_IOGFP_WAIT,因此是不是特别挑剔。

怎么了ZONE_NORMAL?可以在 OOM 输出中进一步找到一些通用统计信息:

[内核] [1772321.850770] 正常空闲:8056kB 最小值:8048kB 低:10060kB高:12072kB active_anon:0kB inactive_anon:0kB active_file:248kB inactive_file:388kB 存在隔离文件:8kB0kB 隔离:8kan0kB 隔离:8kan0kB

这里明显的是,free仅仅是从8Kmin和方式下low。这意味着您的主机的内存管理器有些困难,并且 kswapd 应该已经换出页面,因为它处于下图的黄色阶段: Linux 内存管理器图

此处提供了有关区域内存碎片的更多信息:

[内核] [1772321.850795] 正常:830*4kB 80*8kB 0*16kB 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*604kB = 8*604kB

基本上是说您有一个 4MB 的连续页面,其余页面主要分为 4KB 页面。

那么让我们回顾一下:

  • 你有一个用户态进程 ( clamd) 从中获取内存ZONE_NORMAL而非特权内存分配通常会从ZONE_HIMEM
  • 内存管理器在这个阶段应该已经能够为请求的 4K 页面提供服务,尽管您似乎有很大的内存压力 ZONE_NORMAL
  • 根据kswapd规则,系统应该事先看到一些分页活动,但没有任何东西被换出,即使在内存压力下ZONE_NORMAL,也没有明显的原因
  • 以上都没有给出明确的理由来说明为什么oom-killer被调用

所有这些看起来都很奇怪,但至少与John O'Gorman 出色的“理解 Linux 虚拟内存管理器”一书的第 2.5 节中描述的内容有关:

由于内核可用的地址空间 (ZONE_NORMAL) 的大小有限,因此内核支持 High Memory 的概念。[...] 为了访问 1GiB 和 4GiB 范围之间的内存,内核使用 kmap() 临时将页面从高端内存映射到 ZONE_NORMAL。[...]

这意味着要描述 1GiB 的内存,大约需要 11MiB 的内核内存。因此,使用 16GiB 时,会消耗 176MiB 的内存,给 ZONE_NORMAL 带来巨大压力。在考虑使用 ZONE_NORMAL 的其他结构之前,这听起来还不错。在最坏的情况下,即使是非常小的结构,例如页表条目 (PTE),也需要大约 16MiB。这使得 16GiB 成为 x86 上可用物理内存 Linux 的实际限制

(重点是我的)

由于 3.2 在内存管理方面比 2.6 有很多进步,这不是一个明确的答案,而是我首先要寻求的一个非常强烈的提示。通过使用mem=内核参数或从服务器中取出一半的 DIMM,将主机的可用内存减少到最多 16G 。

最终,使用 64 位内核

兄弟,现在是2015年。

  • 那是美丽的。SF还是有希望的。 (19认同)
  • 当我在上面说“*我不是专家*”时,**这**是我希望阅读的内容。+1000,如果我可以的话;+1 肯定的。多么棒的答案! (14认同)
  • @dataless 是的。我怀疑您的所有 ZONE_NORMAL 都充满了有关高端内存区域的元数据。当用户进程请求内存页面时,它很可能会请求 ZONE_HIGHMEM(如果 HIGHMEM 没有更多可用页面来为请求提供服务,但 NORMAL 有),则 MM 可能会将其升级为 ZONE_NORMAL),因此除非 ZONE_HIGHMEM 处于内存压力之下(你的不是), ZONE_NORMAL 将没有用户空间进程的页面。 (9认同)
  • [用拳头敲击键盘] 赏赐这个 wabbit (3认同)
  • @the-wabbit 热门网络问题。 (3认同)
  • 哇,干得漂亮。 (2认同)