OOM 분석 프로세스가 Linux에 나타납니다.

배경

리눅스 커널에는 OOM 킬러(Out-Of-Memory killer)라는 메커니즘이 있는데, 시스템이 메모리를 신청해야 하는데 신청할 수 없을 때 OOM 킬러는 현재 가장 큰 메모리를 점유하고 있는 프로세스를 확인하고 이를 종료해 해제한다. 메모리 보장 시스템을 업그레이드하고 정상 작동합니다.

일반적으로 애플리케이션의 메모리가 점차 증가하는 것은 확실히 비정상적이며, 이때 애플리케이션에 메모리 누수가 발생한 것으로 간주할 수 있으며, 시스템 메모리가 일정 수준까지 점유되면 OOM이 발생합니다. 시스템은 최적의 메모리를 찾게 되며, 메모리를 확보하기 위해 적절한 프로세스가 종료됩니다.

시스템이 종료하기에 가장 적합한 프로세스를 찾는 방법은 여기서 자세히 소개되지 않으며 OOM 발생 시 현장에서 분석하는 방법만 소개됩니다.

또한 반드시 프로세스에 메모리 누수가 발생했다는 의미는 아니며 커널 드라이버, 심각한 메모리 조각화 등으로 인해 OOM이 발생할 수도 있습니다.

현장에서

시스템이 OOM을 트리거하면 다음과 같은 현장 정보가 인쇄됩니다.

[41311.854276] udevd invoked oom-killer: gfp_mask=0x27080c0(GFP_KERNEL_ACCOUNT|__GFP_ZERO|__GFP_NOTRACK), nodemask=0, order=1, oom_score_adj=-1000
[41311.873871] COMPACTION is disabled!!!
[41311.878101] CPU: 0 PID: 1069 Comm: udevd Not tainted 4.9.191 #147
[41311.885079] Hardware name: arm
[41311.889103] [<c010d3cc>] (unwind_backtrace) from [<c010a51c>] (show_stack+0x10/0x14)
[41311.897984] [<c010a51c>] (show_stack) from [<c01b2be8>] (dump_header.constprop.4+0x7c/0x1c8)
[41311.907480] [<c01b2be8>] (dump_header.constprop.4) from [<c0176404>] (oom_kill_process+0xf0/0x4d0)
[41311.917732] [<c0176404>] (oom_kill_process) from [<c0176cac>] (out_of_memory+0x348/0x3f0)
[41311.927687] [<c0176cac>] (out_of_memory) from [<c017ae5c>] (__alloc_pages_nodemask+0x884/0x928)
[41311.937826] [<c017ae5c>] (__alloc_pages_nodemask) from [<c0116428>] (copy_process.part.3+0x160/0x1574)
[41311.948461] [<c0116428>] (copy_process.part.3) from [<c011798c>] (_do_fork+0xb0/0x358)
[41311.957436] [<c011798c>] (_do_fork) from [<c0117ca4>] (sys_fork+0x20/0x28)
[41311.966475] [<c0117ca4>] (sys_fork) from [<c0106bc0>] (ret_fast_syscall+0x0/0x54)
[41311.974953] Mem-Info:
[41311.977605] active_anon:1158 inactive_anon:25 isolated_anon:0
[41311.977605]  active_file:49363 inactive_file:59329 isolated_file:0
[41311.977605]  unevictable:0 dirty:1 writeback:0 unstable:0
[41311.977605]  slab_reclaimable:1446 slab_unreclaimable:1541
[41311.977605]  mapped:988 shmem:26 pagetables:53 bounce:0
[41311.977605]  free:2975 free_pcp:174 free_cma:0
[41312.014748] Node 0 active_anon:4632kB inactive_anon:100kB active_file:197452kB inactive_file:237316kB unevictable:0kB isolated(anon):0kB isolated(file):0kB mapped:3952kB dirty:4kB writeback:0kB shmem:104kB writeback_tmp:0kB unstable:0kB pages_scanned:0 all_unreclaimable? no
[41312.044605] Normal free:11900kB min:2832kB low:3540kB high:4248kB active_anon:4632kB inactive_anon:100kB active_file:197452kB inactive_file:237316kB unevictable:0kB writepending:4kB present:519680kB managed:504736kB mlocked:0kB slab_reclaimable:5784kB slab_unreclaimable:6164kB kernel_stack:592kB pagetables:212kB bounce:0kB free_pcp:692kB local_pcp:692kB free_cma:0kB
[41312.081972] lowmem_reserve[]: 0 0 0
[41312.086149] Normal: 1445*4kB (UMH) 629*8kB (UMH) 48*16kB (UMH) 4*32kB (MH) 3*64kB (M) 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 11900kB
[41312.101337] 108718 total pagecache pages
[41312.105844] 0 pages in swap cache
[41312.109615] Swap cache stats: add 0, delete 0, find 0/0
[41312.115499] Free swap  = 0kB
[41312.118813] Total swap = 0kB
[41312.122117] 129920 pages RAM
[41312.126429] 0 pages HighMem/MovableOnly
[41312.130968] 3736 pages reserved
[41312.134502] 0 pages cma reserved
[41312.138192] [ pid ]   uid  tgid total_vm      rss nr_ptes nr_pmds swapents oom_score_adj name
[41312.148942] [  999]     0   999      306      133       4       0        0             0 adbd
[41312.158846] [ 1026]     0  1026      208      128       3       0        0             0 powerkey_daemon
[41312.169696] [ 1028]     0  1028      174      127       3       0        0             0 swupdate-progre
[41312.180456] [ 1044]     0  1044      262      199       3       0        0             0 sh
[41312.189932] [ 1066]     0  1066      330      181       4       0        0             0 dbus-daemon
[41312.201026] [ 1069]     0  1069      352      228       4       0        0         -1000 udevd
[41312.210849] [ 1109]     0  1109     2033      216       8       0        0             0 wifi_deamon
[41312.221334] [ 1129]     0  1129      575      167       4       0        0             0 wpa_supplicant
[41312.233161] [ 1159]     0  1159     5664     1681      17       0        0             0 playerdemo
[41312.243587] Out of memory: Kill process 1159 (playerdemo) score 13 or sacrifice child
[41312.253545] Killed process 1159 (playerdemo) total-vm:22656kB, anon-rss:3680kB, file-rss:3044kB, shmem-rss:0kB
Killed
root@ARM:/# 

위의 정보를 볼 때 문제가 다시 발생하기 때문에 겁이 나곤 했습니다. 지금은 두렵지만 적어도 먼저 무슨 일이 일어나고 있는지 이해할 수 있으니 하나씩 분석해 보겠습니다.

트리거 소스

[41311.854276] udevd invoked oom-killer: gfp_mask=0x27080c0(GFP_KERNEL_ACCOUNT|__GFP_ZERO|__GFP_NOTRACK), nodemask=0, order=1, oom_score_adj=-1000

위의 로그 라인에서 udevd 프로세스가 oom-killer 메커니즘을 트리거한다는 것을 알 수 있습니다. 트리거되면 4KB(페이지 크기) * 2^order 메모리(order=1, 즉 8KB)에 적용되며 메모리는 크지는 않습니다. , 비교적 정상입니다. 메모리 적용 시 플래그는 (GFP_KERNEL_ACCOUNT|__GFP_ZERO|__GFP_NOTRACK)로 매우 만족스럽고 특이한 점은 없습니다. oom_score_adj는 oom-killer가 해당 프로세스를 종료하기로 결정할 때의 가중치입니다. 값이 클수록 종료되기 쉽습니다. 읽어.

申请内存的标志信息如下:

__GFP_ZERO:返回已经帮忙清0的内存。

__GFP_NOTRACK:避免使用kmemcheck进行跟踪。

GFP_KERNEL:典型的内核内部分配。调用者需要ZONE_NORMAL或一个较低的区域来直接访问,但可以直接回收。

GFP_KERNEL_ACCOUNT:与GFP_KERNEL相同,不同的是分配被记为kmemcg。

스택

[41311.878101] CPU: 0 PID: 1069 Comm: udevd Not tainted 4.9.191 #147
[41311.885079] Hardware name: arm
[41311.889103] [<c010d3cc>] (unwind_backtrace) from [<c010a51c>] (show_stack+0x10/0x14)
[41311.897984] [<c010a51c>] (show_stack) from [<c01b2be8>] (dump_header.constprop.4+0x7c/0x1c8)
[41311.907480] [<c01b2be8>] (dump_header.constprop.4) from [<c0176404>] (oom_kill_process+0xf0/0x4d0)
[41311.917732] [<c0176404>] (oom_kill_process) from [<c0176cac>] (out_of_memory+0x348/0x3f0)
[41311.927687] [<c0176cac>] (out_of_memory) from [<c017ae5c>] (__alloc_pages_nodemask+0x884/0x928)
[41311.937826] [<c017ae5c>] (__alloc_pages_nodemask) from [<c0116428>] (copy_process.part.3+0x160/0x1574)
[41311.948461] [<c0116428>] (copy_process.part.3) from [<c011798c>] (_do_fork+0xb0/0x358)
[41311.957436] [<c011798c>] (_do_fork) from [<c0117ca4>] (sys_fork+0x20/0x28)
[41311.966475] [<c0117ca4>] (sys_fork) from [<c0106bc0>] (ret_fast_syscall+0x0/0x54)

위 정보는 OOM 발생 시 CPU의 스택 정보를 나타냅니다. 위에서 보면 메모리 적용 시 OOM이 발생하는 것을 대략적으로 알 수 있습니다. 인쇄된 스택 위치도 매우 일반적입니다. 장치 드라이버가 아닌 커널 함수입니다. .. 큰 문제는 없을 겁니다. 계속해서 아래로 가세요.

相信内核不相信驱动:看堆栈信息时,确认最后堆栈的位置,确认是内核的常规函数还是某些驱动的函数,如果是驱动的,需要看看该驱动是否刚好有问题。

메모리 정보

스택을 읽은 후 OOM이 발생했을 때의 시스템 메모리 정보인데 매우 중요합니다.

[41311.977605] active_anon:1158 inactive_anon:25 isolated_anon:0
[41311.977605]  active_file:49363 inactive_file:59329 isolated_file:0
[41311.977605]  unevictable:0 dirty:1 writeback:0 unstable:0
[41311.977605]  slab_reclaimable:1446 slab_unreclaimable:1541
[41311.977605]  mapped:988 shmem:26 pagetables:53 bounce:0
[41311.977605]  free:2975 free_pcp:174 free_cma:0

anon, 스택, 힙 등 프로세스에 속하는 데이터입니다.

active_anon: 활성 메모리, 1158페이지, 4632kB;

inactive_anon: 비활성 메모리, 25페이지, 100kB;

ised_anon: anon lru에서 페이지를 일시적으로 격리합니다.

캐시 메모리는 현재 메모리에 있는 디스크 데이터를 저장합니다.

active_file: 활성 메모리, 49363페이지, 즉 197452kB;

inactive_file: 비활성 메모리, 59329페이지, 237316kB;

고립_파일:

slab_reclaimable: 시스템 회수 가능 메모리, 1446페이지, 5784kB;

slab_unreclaimable: 시스템 회수 불가능한 메모리, 1541 페이지, 6164kB;

mapped: 매핑된 파일 페이지, 988페이지, 3952kB(예: 공유 메모리, 동적 라이브러리, mmap 등이 계산됨)

shmem: 메모리 및 tmpfs 메모리 공유, free 명령의 공유와 동일, 26페이지, 104kB;

페이지 테이블: 가상 주소를 물리적 주소로 변환하는 데 사용되는 페이지 테이블 메모리, 53페이지, 212kB;

되튐:

무료: 여유 메모리, 2975페이지, 11900kB;

free_pcp: CPU가 차지하는 캐시 메모리, 174페이지, 696kB;

free_cma: 사용 가능한 cma 메모리 크기;

文件页:内存回收也就是系统释放掉可以回收的内存,比如缓存和缓冲区就属于可回收内存。在内存管理中,通常叫文件页,大部分文件页都可以直接回收,后续有需要再重磁盘重新读取即可(如属于进程的代码段等)。

脏页:被应用程序修改过,并暂时还没有写入磁盘的数据,得先写入磁盘才可以进行内存释放。

文件映射页:除了缓存和缓冲区,通过内存映射获取的文件映射页,也是一种常见的文件页。它也可以被释放掉,下次再访问的时候,从文件重新读取。

匿名页:应用程序动态分配的堆内存称为匿名页(Anonymous Page)。
[41312.014748] Node 0 active_anon:4632kB inactive_anon:100kB active_file:197452kB inactive_file:237316kB unevictable:0kB isolated(anon):0kB isolated(file):0kB mapped:3952kB dirty:4kB writeback:0kB shmem:104kB writeback_tmp:0kB unstable:0kB pages_scanned:0 all_unreclaimable? no
[41312.044605] Normal free:11900kB min:2832kB low:3540kB high:4248kB active_anon:4632kB inactive_anon:100kB active_file:197452kB inactive_file:237316kB unevictable:0kB writepending:4kB present:519680kB managed:504736kB mlocked:0kB slab_reclaimable:5784kB slab_unreclaimable:6164kB kernel_stack:592kB pagetables:212kB bounce:0kB free_pcp:692kB local_pcp:692kB free_cma:0kB
[41312.081972] lowmem_reserve[]: 0 0 0
[41312.086149] Normal: 1445*4kB (UMH) 629*8kB (UMH) 48*16kB (UMH) 4*32kB (MH) 3*64kB (M) 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 11900kB
[41312.101337] 108718 total pagecache pages
[41312.105844] 0 pages in swap cache
[41312.109615] Swap cache stats: add 0, delete 0, find 0/0
[41312.115499] Free swap  = 0kB
[41312.118813] Total swap = 0kB
[41312.122117] 129920 pages RAM
[41312.126429] 0 pages HighMem/MovableOnly
[41312.130968] 3736 pages reserved
[41312.134502] 0 pages cma reserved

위 정보는 앞서 소개한 내용과 유사하며, OOM 발생 시 다양한 메모리 정보를 더욱 명확하게 확인할 수 있습니다. 위에서 보면 OOM 중에 active_file + inactive_file이 총 424MB, 시스템의 전체 메모리가 129920 pages RAM512MB라는 것을 알 수 있는데, 당연히 이 active_file + inactive_file이 시스템 OOM의 원인이 됩니다.

다른

[41312.138192] [ pid ]   uid  tgid total_vm      rss nr_ptes nr_pmds swapents oom_score_adj name
[41312.148942] [  999]     0   999      306      133       4       0        0             0 adbd
[41312.158846] [ 1026]     0  1026      208      128       3       0        0             0 powerkey_daemon
[41312.169696] [ 1028]     0  1028      174      127       3       0        0             0 swupdate-progre
[41312.180456] [ 1044]     0  1044      262      199       3       0        0             0 sh
[41312.189932] [ 1066]     0  1066      330      181       4       0        0             0 dbus-daemon
[41312.201026] [ 1069]     0  1069      352      228       4       0        0         -1000 udevd
[41312.210849] [ 1109]     0  1109     2033      216       8       0        0             0 wifi_deamon
[41312.221334] [ 1129]     0  1129      575      167       4       0        0             0 wpa_supplicant
[41312.233161] [ 1159]     0  1159     5664     1681      17       0        0             0 playerdemo
[41312.243587] Out of memory: Kill process 1159 (playerdemo) score 13 or sacrifice child
[41312.253545] Killed process 1159 (playerdemo) total-vm:22656kB, anon-rss:3680kB, file-rss:3044kB, shmem-rss:0kB
Killed
root@ARM:/#

위 정보는 시스템이 어떤 프로세스를 종료해야 하는지 계산할 때의 정보로, 최종적으로 점수가 가장 높은 프로세스가 종료되며, 종료된 프로세스의 메모리 정보가 제한된다. playerdemo가 차지하는 메모리는 정상입니다.

분석하다

OOM이 발생하면 다음 단계에 따라 분석할 수 있습니다.

  1. 먼저 OOM이 실행될 때 메모리 적용에 대한 정보를 살펴보겠습니다.
    예를 들어 위의 __alloc_pages_nodemask를 통해 적용되는 gfp_mask 플래그는 비교적 일반적입니다.
    동시에, 애플리케이션의 크기는 order=18KB이며, 이는 정상적인 대용량 메모리가 아닙니다.
  2. 현장의 메모리 정보 분포를 살펴보세요.
    OOM이 발생할 때 메모리가 어떻게 분배되는지 살펴봅니다.
    slab_unreclaimable 예외인 경우 회수 불가능한 메모리가 비교적 큰 것이므로 슬래브 메모리 누수에 따라 문제를 해결할 수 있습니다.
    애플리케이션 RSS 예외인 경우 애플리케이션 메모리 누수를 따릅니다.
    다른 사람들은 메모리 조각화를 고려할 수 있습니다.

슬래브 메모리 누수

에이징 테스트 중에 시스템의 slab_unreclaimable이 계속 증가하는 것으로 확인되면 일반적으로 시스템에 메모리 누수가 있는 것으로 이해할 수 있습니다. 이때 다음 작업을 수행할 수 있습니다.

커널은 CONFIG_SLUB_DEBUG 및 CONFIG_SLUB_DEBUG_ON의 두 가지 구성을 구성한 후 다음 패치를 추가해야 합니다.

diff --git a/mm/slub.c b/mm/slub.c
index 1600670e89ca..6f494b995092 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -4524,6 +4524,7 @@ static int list_locations(struct kmem_cache *s, char *buf,
        unsigned long *map = kmalloc(BITS_TO_LONGS(oo_objects(s->max)) *
                                     sizeof(unsigned long), GFP_KERNEL);
        struct kmem_cache_node *n;
+       bool print_buf = false;

        if (!map || !alloc_loc_track(&t, PAGE_SIZE / sizeof(struct location),
                                     GFP_TEMPORARY)) {
@@ -4551,8 +4552,16 @@ static int list_locations(struct kmem_cache *s, char *buf,
        for (i = 0; i < t.count; i++) {
                struct location *l = &t.loc[i];

-               if (len > PAGE_SIZE - KSYM_SYMBOL_LEN - 100)
-                       break;
+               if (len > PAGE_SIZE - KSYM_SYMBOL_LEN - 100) {
+                       print_buf = true;
+                       pr_err("%s\n", buf);
+                       len = 0;
+               }
+
+               /* ignore minority data */
+               if (l->count < 100)
+                       continue;
+
                len += sprintf(buf + len, "%7ld ", l->count);

                if (l->addr)
@@ -4591,6 +4600,12 @@ static int list_locations(struct kmem_cache *s, char *buf,

                len += sprintf(buf + len, "\n");
        }
+       if (print_buf) {
+               pr_info("%s\n", buf);
+               len = sprintf(buf, "sysfs node buffer size is PAGE_SIZE.");
+               len += sprintf(buf + len, "The message is more than 1 page.\n");
+               len += sprintf(buf + len, "Please get the message by kmsg\n");
+       }

        free_loc_track(&t);
        kfree(map);

위 패치는 메모리 노드 정보를 볼 때 불완전한 인쇄 문제를 방지하고 더 적은 수의 응용 프로그램이 있는 메모리 개체도 무시합니다.

위의 패치를 추가한 후 테스트를 시작할 수 있습니다. 테스트 전 cat /proc/slabinfoslabinfo를 실행하여 기록한 후 Aging 테스트를 시작하고, 충분한 시간 동안 테스트가 완료된 후 다시 실행한 후 slabinfo 전후를 비교한다. cat /proc/slabinfonum_objs 값에 주의 ​​서로 다른 슬래브 개체의 전후에 뚜렷한 차이가 있는 경우 개체 사용에 메모리 누수가 있는 것입니다(충분한 테스트 시간은 시스템 메모리 누수가 명확하게 확인된 시간을 의미합니다).

위의 연산을 통해 kmalloc-128 객체의 애플리케이션 릴리즈에 누수가 있는 것을 발견했다고 가정하고, 이를 실행하여 cat /sys/kernel/slab/kmalloc-128/alloc_callskmalloc-128에 어떤 함수가 적용되었는지 확인할 수 있습니다. 의심되는 물체(테스트 전 비교도 가능) 사례).

이전 기초에서는 어떤 커널 함수의 빈번한 호출이 메모리 누수를 유발하는지 기본적으로 이해했지만 누가 이 함수를 호출했는지는 충분히 명확하지 않습니다. 이때 호출된 메모리 함수에 dump_stack()에 대한 호출을 추가하면 함수 호출 시 스택이 인쇄되어 누가 호출되는지 알 수 있습니다.(dump_stack() 함수의 호출 함수가 호출되기 전에 몇 번이나 입력되는지 등의 판단 조건을 추가할 수 있습니다. 시스템의 정상적인 작동에 영향을 미칠 수 있는 지나치게 큰 인쇄 스택을 방지하기 위해 스택이 인쇄됩니다.

여기에 왔을 때 어떤 드라이버의 함수 호출이 메모리 누수를 일으켰는지 이미 알고 있었고 실제 상황을 추적하여 문제를 해결했습니다.

애플리케이션 메모리 누수

OOM이 발생하면 현장의 애플리케이션이 종료될 때 많은 양의 메모리를 점유하는지 먼저 확인합니다.(rss 참조) 그렇다면 애플리케이션 메모리 누수이며 이는 valgrind 도구를 통해 확인할 수 있습니다.

valgrind 도구 사용 시 컴파일된 실행 프로그램을 스트리핑할 수 없습니다. 컴파일하여 매개변수를 추가합니다 -g. 동시에 valgrind --leak-check=full --show-reachable=yes TEST_CMD분석에 사용할 수 있습니다. /proc/PID/fd/가 계속 증가하는지, 할당된 메모리가 사용 후 제 시간에 출시됩니다.

커널 메모리 누수

커널 메모리 누수는 kmemleak 도구를 사용하여 확인할 수 있습니다. 메모리 누수 확인을 위한 커널 도구인 kmemleak를 확인해 보세요 .

기타 OOM 상황

OOM이 발생하면 시스템의 slab_unreclaimable이 정상이고, 프로그램이 점유하고 있는 rss도 정상이라면, 메모리 조각화로 인해 시스템이 메모리 신청 시 메모리를 할당할 수 없게 되는 것으로 볼 수 있다. 시스템의 노드 정보를 보면 알 수 있는데 /proc/pagetypeinfo, 시스템 메모리가 심하게 단편화되면 모든 여유 메모리가 순서=0으로 집중됩니다(즉, 가장 큰 여유 연속 메모리 블록이 1페이지임).

이 경우 실제로 커널의 압축 기능을 사용하는 커널의 CONFIG_COMPACTION 구성이 활성화되어 있지 않은지 확인하세요. 디스크 조각 모음과 유사합니다. 깨진 페이지를 연속 공간으로 이동하면 메모리의 연속 섹션이 남게 됩니다. 이동 가능한 페이지만 조각 모음할 수 있다는 점에 유의하세요.

이 문서의 예제 로그는 CONFIG_COMPACTION을 활성화하지 않은 커널로 인해 발생한 메모리 조각화가 심각하여 OOM을 트리거한다는 것입니다.

흥미로운 기사

Linux의 익명 페이지에 대한 액세스 분석

おすすめ

転載: blog.csdn.net/weixin_41944449/article/details/126296731