OOM Killer는 희생시킬 프로세스를 고르기 위해서 각 프로세스에 점수를 매기는 과정을 진행
OOM Killer는 메모리가 부족해졌을 때 프로세스 중 oom_score가 가장 높은 점수를 받은 순서대로 프로세스를 죽임
oom_score 점수는 기본적으로 프로세스에게 할당된 메모리의 양으로 점수가 메겨짐 → 해당 프로세스로부터 fork()된 자식 프로세스들의 메모리도 추가됨
오래 돌고 있는 프로세스의 경우 oom_score 점수를 낮춰주고, 프로세스 순위가 낮은 프로세스는 oom_score 점수를 높여서 사용할 필요가 있음
중요하게 취급되어지는 프로세스들(superuser에 의해서 실행되거나, 하드웨어와 직접적인 관련이 있는)은 값을 크게 낮춰줘서 OOM Killer를 통해서 kill되지 않도록 해야함
각 서비스의 oom_score를 확인하는 방식 (PID → 프로세스 ID)
# 기본 형식 $ cat /proc/<pid>/oom_score oom_socre 출력 # 19862 프로세스의 oom_score 확인 $ cat /proc/19862/oom_score 12
※ nice
- 프로세스의 우선 순위를 낮춰주는 점수를 의미
oom_score_adj, oom_score, oom_adj 커널 파라미터
1. oom_score_adj
- 앱이 포그라운드(foreground)에 있는지, 백그라운드(background)에 있는지로 결정
- 정확한 값은 앱이 백그라운드 서비스를 가지고 있는지, 얼마나 오랫동안 포그라운드에 빠져 있는지에 따라 달라짐
- oom_score_adj 는 -1000 ~ 1000 의 값을 가지며, 낮은 값 일수록 우선순위에서 밀려남
- 프로세스가 OOM Killer 에 의해 죽지 않길 원한다면 oom_score 를 조정하는게 아니라 oom_score_adj 값을 변경해야함
2. oom_score
- 기본적으로 프로세스에게 할당된 메모리의 양으로 점수가 메겨짐
3. oom_adj
- -17 ~ 15 의 값을 가지며, 낮은 값 일수록 우선순위에서 밀려남
- -17이면 OOM을 완전히 비활성화시킴
/proc/<pid>/oom_adj /proc/<pid>/oom_score /proc/<pid>/oom_score_adj
OOM Killer의 대상에서 벗어나기
- OOM Killer를 끄는 것은 불가능
- 서비스의 안정성을 위해서 꺼지면 안 될 프로세스도 존재 → 특정한 작업을 하고 있던 worker 프로세스
- OOM Killer를 직접적으로 끌 수는 없지만, OOM Killer의 scoring를 통해 OOM Killer가 멈추는 대상은 벗어날수 있음
- /proc//oom_adj 값을 -17로 설정 → -17 값은 OOM_DISABLE의 상수 값
$ echo -17 > /proc/<pid>/oom_adj
- /proc/oom_scoring_adj 값을 -1000으로 설정
- -1000 값으로 지정하는 것은 OOM Scoring을 비활성화한다기보다는 충분히 낮은 값을 주어서 OOM Killer에 벗어남
$ echo -1000 > /proc/<pid>/oom_adj
관련해서 리눅스에서 실행(kill) 하는 순서
- alloc_pages
- out_of_memory
- select_bad_process
- oom_evaluate_task
- oom_badness
OOM Scoring과 관련된 함수들
1. select_bad_process
bad 프로세스를 선택
select_bad_process 함수 내용
static void select_bad_process(struct oom_control *oc) { if (is_memcg_oom(oc)) mem_cgroup_scan_tasks(oc->memcg, oom_evaluate_task, oc); else { struct task_struct *p; // RCU: Read-Copy Update rcu_read_lock(); for_each_process(p) if (oom_evaluate_task(p, oc)) break; rcu_read_unlock(); } oc->chosen_points = oc->chosen_points * 1000 / oc->totalpages; }
2. oom_evaluate_task
oom 선정된 프로세스가 OOM Killer를 통해 멈춰되 되는지 검증
oom_evaluate_task 함수 내용
static int oom_evaluate_task(struct task_struct *task, void *arg) { struct oom_control *oc = arg; unsigned long points; if (oom_unkillable_task(task, NULL, oc->nodemask)) goto next; /* * This task already has access to memory reserves and is being killed. * Don't allow any other task to have access to the reserves unless * the task has MMF_OOM_SKIP because chances that it would release * any memory is quite low. */ if (!is_sysrq_oom(oc) && tsk_is_oom_victim(task)) { if (test_bit(MMF_OOM_SKIP, &task->signal->oom_mm->flags)) goto next; goto abort; } /* * If task is allocating a lot of memory and has been marked to be * killed first if it triggers an oom, then select it. */ if (oom_task_origin(task)) { points = ULONG_MAX; goto select; } points = oom_badness(task, NULL, oc->nodemask, oc->totalpages); if (!points || points < oc->chosen_points) goto next; /* Prefer thread group leaders for display purposes */ if (points == oc->chosen_points && thread_group_leader(oc->chosen)) goto next; select: if (oc->chosen) put_task_struct(oc->chosen); get_task_struct(task); oc->chosen = task; oc->chosen_points = points; next: return 0; abort: if (oc->chosen) put_task_struct(oc->chosen); oc->chosen = (void *)-1UL; return 1; }
3. oom_badness
kill 했을 때 가장 높은 메모리를 확보할 수 있는 task 인 경우, 높은 점수를 return 한다는 개념
oom_badness() 함수가 리턴해주는 점수가 가장 높은 task가 bad_process로 선정 되어 죽게 됨
oom_score_adj 라는 값을 oom_badness 에서 LONG_MIN 을 리턴하기 위해 사용 → LONG_MIN 을 리턴한다는 개념은 낮은 점수를 줘서 kill 하지 못하게 하겠다는 의미
RSS (프로세스가 사용하고 있는 물리 메모리) + 프로세스의 스왑 메모리 + (프로세스의 pagetable / page_size) 의 값이 프로세스 (task)의 점수 (point) 가 됨
해당 코드만 보면 프로세스가 점유하고 있는 메모리가 클 경우 score가 높아진다고 이해 가능
oom_badness 함수 내용
/** * oom_badness - heuristic function to determine which candidate task to kill * @p: task struct of which task we should calculate * @totalpages: total present RAM allowed for page allocation * * The heuristic for determining which task to kill is made to be as simple and * predictable as possible. The goal is to return the highest value for the * task consuming the most memory to avoid subsequent oom failures. */ long oom_badness(struct task_struct *p, unsigned long totalpages) { long points; long adj; if (oom_unkillable_task(p)) return LONG_MIN; p = find_lock_task_mm(p); if (!p) return LONG_MIN; /* * Do not even consider tasks which are explicitly marked oom * unkillable or have been already oom reaped or the are in * the middle of vfork */ adj = (long)p->signal->oom_score_adj; if (adj == OOM_SCORE_ADJ_MIN || test_bit(MMF_OOM_SKIP, &p->mm->flags) || in_vfork(p)) { task_unlock(p); return LONG_MIN; } /* * The baseline for the badness score is the proportion of RAM that each * task's rss, pagetable and swap space use. */ points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) + mm_pgtables_bytes(p->mm) / PAGE_SIZE; task_unlock(p); /* Normalize to oom_score_adj units */ adj *= totalpages / 1000; points += adj; return points; }
4. get_mm_rss 함수
- get_mm_rss() 함수를 사용하여 여유 메모리(free memory)가 얼마나 되는지 추정
static inline unsigned long get_mm_rss(struct mm_struct *mm) { return get_mm_counter(mm, MM_FILEPAGES) + get_mm_counter(mm, MM_ANONPAGES) + get_mm_counter(mm, MM_SHMEMPAGES); }
5. get_mm_counter 함수
get_mm_counter 함수를 통해 swap 메모리(swap memory)가 얼마나 되는지 추정
static inline unsigned long get_mm_counter(struct mm_struct *mm, int member) { long val = atomic_long_read(&mm->rss_stat.count[member]); #ifdef SPLIT_RSS_COUNTING /* * counter is updated in asynchronous manner and may go to minus. * But it's never be expected number for users. */ if (val < 0) val = 0; #endif return (unsigned long)val; }
'OS(운영체제) > 리눅스(Linux)' 카테고리의 다른 글
[cache] Write Through와 Write Back 비교 (0) | 2022.07.18 |
---|---|
프로세스가 사용하는 메모리의 양 확인 정보 → VIRT, RES, SHR (0) | 2022.07.18 |
Network Ring Buffer Size 설정하기 (0) | 2022.07.15 |
CentOS iso 파일의 특정 버전과 Centos 버전의 EOL(End Of Life) (0) | 2022.07.03 |
kickstart Centos7 ISO 파일 만들기(UEFI 사용 버전) (0) | 2022.07.03 |