Study · 운영체제 · OOM · 메모리

OOM Killer 가 보는 것은 달라고 한 양이 아니라 실제로 붙여 둔 양이다

장면

메모리가 정말 없을 때 커널은 무엇을 기준으로 죽일 프로세스를 고를까?

$ dmesg | grep -i "killed process"
[12345.678] Out of memory: Killed process 4242 (java) total-vm:...
$ cat /proc/<pid>/oom_score
# oom_score 가 높은 프로세스부터 죽는다. <pid> 자리에는 볼 프로그램 번호를 넣는다

아래 재생기의 ▶를 누르면 한 스텝씩 진행된다. 금색 테두리가 지금 이야기하는 곳이고, 캡션이 무슨 일이 일어났는지 말해 준다. 굵게 밑줄 친 말은 누르면 뜻이 나온다. 빨간 딱지는 그때 벌어지는 일의 이름이고, 캡션 바로 위 회색 글자는 지금 스텝이 속한 묶음의 이름이다.

카드 아래 회색 줄은 그 일이 실제로 적혀 있는 소스의 파일 이름 : 줄 번호와 함수 이름이다. 글 안에 (:669)처럼 줄 번호만 적힌 것은 바로 앞에 적은 파일의 그 줄이라는 뜻이다. 캡션 한가운데 박힌 긴 영어 이름과 괄호 속 파일 · 줄 번호도 같다 — 건너뛰고 한글만 읽어도 뜻이 통하게 써 두었다.

장면 · 붙일 것이 없을 때 하나를 고른다 0 / 11

▶ 를 누르면 물리 메모리가 정말 없을 때 커널이 무엇을 보고 죽일 프로세스를 고르는지 본다. 격자 32칸이 이 기계의 물리 메모리 전체이고, 누구에게 몇 칸씩 주었는지는 그림을 위한 배치다.

키보드 · ← → 한 스텝 · Space 재생/멈춤 · Home 처음

OOM Killer 는 가장 많이 달라고 한 것을 죽이지 않는다. 실제로 붙여 둔 양으로 점수를 매겨 고르고, 죽인 뒤 그 점수의 재료를 기록에 남긴다.

어떻게 확인했나

소스 어디를 보고 적었는지와 무엇을 돌려서 쟀는지를 남긴 자리다. 처음 읽을 때는 건너뛰어도 된다.

  • oom_badness(mm/oom_kill.c:199)가 더하는 것은 셋뿐이다 — 지금 붙어 있는 페이지 수(RSS) + 스왑으로 나간 페이지 수 + 페이지 표가 먹은 바이트를 페이지 크기로 나눈 값(:228-229). mm->total_vm(VmSize)은 이 함수에 나오지 않는다. 그래서 「가장 많이 쓴 것이 죽는다」는 「가장 많이 붙여 둔 것이 죽는다」로 읽어야 맞다 — 달라고만 해 둔 양은 점수가 아니다.
  • oom_score_adj 는 adj *= totalpages / 1000; points += adj;(mm/oom_kill.c:233-234)로 얹힌다. 1000 이면 전체 페이지 수만큼을 점수에 더하는 것이고, 최솟값 -1000(OOM_SCORE_ADJ_MIN, include/uapi/linux/oom.h:9)이면 점수를 재지 않고 LONG_MIN 을 돌려줘 후보에서 빠진다(:217-221).
  • 죽이기는 마지막 수단이다 — __alloc_pages_direct_reclaim(mm/page_alloc.c:4939)이 실패하고 재시도까지 다 떨어진 뒤에야 「Reclaim has failed us, start killing things」 주석 아래(:5029)에서 __alloc_pages_may_oom(:4044) → out_of_memory(mm/oom_kill.c:1103)로 간다.
  • 고르는 방식은 전수 조사다 — select_bad_process(mm/oom_kill.c:362)가 for_each_process 로 전부 돌며 oom_evaluate_task(:306)를 부르고, 지금까지의 최고점보다 낮으면 버린다(:339). 죽일 때는 SIGKILL(:943)을 보내고 dmesg 한 줄(:945)에 total-vm · anon-rss · pgtables · oom_score_adj 를 함께 찍는다.
  • /proc/<pid>/oom_score 는 같은 oom_badness 를 그 자리에서 돌려(fs/proc/base.c:590) 0~2000 범위로 늘인 값이다(:596). 커널이 고를 때 쓰는 함수와 내가 읽는 숫자가 같은 출처다.
  • 메모리 2GiB 로 한도를 건 컨테이너에서 계속 먹였더니 1,984MB 를 지나 죽었고 끝난 코드가 137(= 128 + SIGKILL 9)이었다. cgroup 의 memory.events 에 oom 1 · oom_kill 1 이 찍혔고, dmesg 가 남긴 표가 oom_badness 의 재료를 그대로 보여 준다 — total_vm 524991 · rss 523040 · pgtables_bytes 4243456 · oom_score_adj 0. 점수에 들어가는 것은 rss 와 pgtables 이고 total_vm 은 아니다.
‹ 이전 목차로 ›