out_of_memory()의 true 반환을 'SIGKILL 하나를 보냈다'로 읽으면 allocator의 재시도 동작을 설명할 수 없습니다. OOM handler는 이미 진행 중인 exit를 돕거나 notifier가 확보한 memory를 이용하고, 다른 victim의 처리를 기다리도록 할 수도 있습니다. 반환값은 원래 allocation의 성공도 아닙니다.
Disable과 notifier부터 확인합니다
OOM killer가 disable된 상태라면 일반 victim 선택을 진행하지 않습니다. suspend나 kexec 같은 상태 전환에서는 task와 memory를 다른 방식으로 통제해야 하기 때문입니다. 이는 oom_score_adj로 개별 task를 보호하는 것과 다른 전역 상태입니다.
global OOM에서는 registered notifier에게 memory를 내놓을 기회를 줄 수 있습니다. notifier가 양을 보고하고 manual invocation이 아니라면 새 victim을 고르지 않고 진전을 반환할 수 있습니다. memcg OOM과 동일한 경로가 아닙니다.
이미 나가는 current를 돕는 편이 나을 수 있습니다
current가 곧 memory를 반환할 상황이면 새 process를 죽이기보다 current를 OOM victim으로 표시하고 reaper에 맡길 수 있습니다. reserve 접근을 허용해 exit를 빨리 진행시키는 것입니다. 그러나 단순히 PF_EXITING 하나만 보고 충분하다고 판단하지는 않습니다.
task_will_free_mem()은 우선 유효한 mm가 있는지 봅니다. 이미 mm를 놓은 task에게 앞으로 memory를 많이 돌려줄 것이라고 기대하지 않습니다. core dump는 종료 과정이라도 오래 걸릴 수 있어 빠른 반환의 근거로 사용하지 않습니다. group exit 상태, 또는 마지막 thread가 exit 중이라는 상태를 확인합니다.
MMF_OOM_SKIP이 설정된 mm도 새로 얻을 양이 작거나 추가 처리가 유망하지 않은 상태일 수 있으므로 이 shortcut의 근거가 되지 않습니다. 이 flag는 항상 '전부 성공적으로 free했다'라는 완료증이 아닙니다.
같은 mm를 쓰는 다른 thread group도 봅니다
하나의 thread group이 죽어도 같은 mm를 다른 group이 쓰면 address space가 곧 사라진다고 확신할 수 없습니다. CLONE_VM으로 mm를 공유하면서 같은 thread group이 아닌 task를 만들 수 있습니다. kernel thread가 user mm를 빌려 쓰는 경우도 개념적으로 구분해야 합니다.
mm_users가 크다는 사실을 'kernel pin이 있다'고 단정해서는 안 됩니다. 이것은 address-space user 측 lifetime이고 mm_count는 descriptor lifetime입니다. task_will_free_mem()은 관련 sharer가 함께 종료 중인지 확인해 빠른 회수 가능성을 평가합니다.
for_each_process()는 모든 개별 thread를 평면적으로 순회하는 macro가 아닙니다. process leader를 순회하며 process_shares_mm() 같은 helper가 group 안의 thread를 확인합니다. leader가 exit하면서 mm를 놓았어도 다른 thread가 유지할 수 있으므로 task 하나의 mm field만 보는 것으로는 충분하지 않습니다.
Filesystem 제약을 kill로 우회하지 않습니다
global OOM에서 GFP가 filesystem 진입을 허용하지 않는 경우에는 OOM kill로 IO-less reclaim을 보상하려 하지 않는 branch가 있습니다. true를 반환해 caller가 다시 판단하도록 하는 관례와 실제 victim 생성은 다릅니다. memcg OOM은 이런 allocation context에서도 별도의 처리가 필요할 수 있어 예외입니다.
그다음 constraint를 계산하고 panic 정책·allocating-task 정책·normal selection을 적용합니다. 선택 결과가 없으면 global allocation에서는 더 이상 살아갈 수 없는 상황으로 panic할 수 있지만 manual·memcg 경로는 같은 결과가 아닙니다.
이미 선택된 victim은 scan을 중단시킬 수 있습니다
normal selection 중 아직 유효하게 reaping할 여지가 있는 기존 victim을 찾으면 새 victim 선택을 중단할 수 있습니다. chosen의 sentinel은 '후보가 전혀 없었다'와 다릅니다. 다른 victim에게 reserve를 더 나눠 주기 전에 기존 진전을 기다리려는 판단입니다.
v6.18의 evaluation callback은 일반적인 계속 진행에 0, abort에 1을 반환합니다. false가 scan 중단이라는 거꾸로 된 규칙으로 읽으면 전체 선택 흐름이 뒤집힙니다. out_of_memory()는 이런 sentinel도 progress로 취급할 수 있습니다.
OOM 관련 log를 해석할 때에는 handler 진입, victim 선택, signal 전송, exit, reaping, 원래 allocation 재시도를 별개 사건으로 보셔야 합니다. true 반환 하나만으로 어느 process의 RAM이 몇 byte 반환되었다고 계산할 수는 없습니다.
out_of_memory의 반환 지점을 하나씩 읽습니다
다음은 Linux v6.18의 전체 handler입니다. notifier·constraint·memcg branch를 포함한 official code이며 upstream notice와 GPL-2.0을 유지했습니다.
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/oom_kill.c
*
* Copyright (C) 1998,2000 Rik van Riel
* Thanks go out to Claus Fischer for some serious inspiration and
* for goading me into coding this file...
* Copyright (C) 2010 Google, Inc.
* Rewritten by David Rientjes
*
* The routines in this file are used to kill a process when
* we're seriously out of memory. This gets called from __alloc_pages()
* in mm/page_alloc.c when we really run out of memory.
*
* Since we won't call these routines often (on a well-configured
* machine) this file will double as a 'coding guide' and a signpost
* for newbie kernel hackers. It features several pointers to major
* kernel subsystems and hints as to where to find out what things do.
*/
/*
* Official Linux v6.18: mm/oom_kill.c
* https://github.com/torvalds/linux/blob/v6.18/mm/oom_kill.c#L1118
* Complete upstream scope, not an elided book-code reconstruction.
* See GPL-2.0.txt. Not a standalone translation unit.
*/
bool out_of_memory(struct oom_control *oc)
{
unsigned long freed = 0;
if (oom_killer_disabled)
return false;
if (!is_memcg_oom(oc)) {
blocking_notifier_call_chain(&oom_notify_list, 0, &freed);
if (freed > 0 && !is_sysrq_oom(oc))
/* Got some memory back in the last second. */
return true;
}
/*
* If current has a pending SIGKILL or is exiting, then automatically
* select it. The goal is to allow it to allocate so that it may
* quickly exit and free its memory.
*/
if (task_will_free_mem(current)) {
mark_oom_victim(current);
queue_oom_reaper(current);
return true;
}
/*
* The OOM killer does not compensate for IO-less reclaim.
* But mem_cgroup_oom() has to invoke the OOM killer even
* if it is a GFP_NOFS allocation.
*/
if (!(oc->gfp_mask & __GFP_FS) && !is_memcg_oom(oc))
return true;
/*
* Check if there were limitations on the allocation (only relevant for
* NUMA and memcg) that may require different handling.
*/
oc->constraint = constrained_alloc(oc);
if (oc->constraint != CONSTRAINT_MEMORY_POLICY)
oc->nodemask = NULL;
check_panic_on_oom(oc);
if (!is_memcg_oom(oc) && sysctl_oom_kill_allocating_task &&
current->mm && !oom_unkillable_task(current) &&
oom_cpuset_eligible(current, oc) &&
current->signal->oom_score_adj != OOM_SCORE_ADJ_MIN) {
get_task_struct(current);
oc->chosen = current;
oom_kill_process(oc, "Out of memory (oom_kill_allocating_task)");
return true;
}
select_bad_process(oc);
/* Found nothing?!?! */
if (!oc->chosen) {
dump_header(oc);
pr_warn("Out of memory and no killable processes...\n");
/*
* If we got here due to an actual allocation at the
* system level, we cannot survive this and will enter
* an endless loop in the allocator. Bail out now.
*/
if (!is_sysrq_oom(oc) && !is_memcg_oom(oc))
panic("System is deadlocked on memory\n");
}
if (oc->chosen && oc->chosen != (void *)-1UL)
oom_kill_process(oc, !is_memcg_oom(oc) ? "Out of memory" :
"Memory cgroup out of memory");
return !!oc->chosen;
}
disabled이면 false이고, global notifier가 memory 반환을 보고했으며 SysRq가 아니면 true입니다. current의 빠른 exit를 기대할 수 있을 때도 victim 표시·reaper 예약 뒤 true입니다. global의 filesystem 진입 금지 context도 새 kill 없이 true로 돌아갈 수 있습니다. 반면 normal selection의 마지막 반환은 !!oc->chosen입니다. NULL, 실제 task, abort sentinel이 서로 다른 선택 결과이지만 bool에서는 뒤의 둘이 모두 true가 됩니다.
이 tag는 filesystem 검사에서 !(gfp_mask & __GFP_FS)를 사용합니다. 고전 code의 추가적인 nonzero-mask guard까지 같은 조건이라고 가정하지 않습니다. 호출자가 어떤 GFP context를 전달하는지와 함께 version별로 읽어야 합니다.
constraint 계산 뒤 MEMORY_POLICY가 아니면 nodemask를 NULL로 정리합니다. 이때 constrained라는 사실 자체가 없어지는 것은 아닙니다. mempolicy와 cpuset은 뒤의 candidate helper가 다른 방식으로 검사하기 때문입니다. allocating-task 우선 경로도 oom_cpuset_eligible()를 통과해야 합니다.
빠른 exit라는 판단에는 세 함수가 참여합니다
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/oom_kill.c
*
* Copyright (C) 1998,2000 Rik van Riel
* Thanks go out to Claus Fischer for some serious inspiration and
* for goading me into coding this file...
* Copyright (C) 2010 Google, Inc.
* Rewritten by David Rientjes
*
* The routines in this file are used to kill a process when
* we're seriously out of memory. This gets called from __alloc_pages()
* in mm/page_alloc.c when we really run out of memory.
*
* Since we won't call these routines often (on a well-configured
* machine) this file will double as a 'coding guide' and a signpost
* for newbie kernel hackers. It features several pointers to major
* kernel subsystems and hints as to where to find out what things do.
*/
/*
* Official Linux v6.18: mm/oom_kill.c
* https://github.com/torvalds/linux/blob/v6.18/mm/oom_kill.c#L852
* Complete upstream scope, not an elided book-code reconstruction.
* See GPL-2.0.txt. Not a standalone translation unit.
*/
static inline bool __task_will_free_mem(struct task_struct *task)
{
struct signal_struct *sig = task->signal;
/*
* A coredumping process may sleep for an extended period in
* coredump_task_exit(), so the oom killer cannot assume that
* the process will promptly exit and release memory.
*/
if (sig->core_state)
return false;
if (sig->flags & SIGNAL_GROUP_EXIT)
return true;
if (thread_group_empty(task) && (task->flags & PF_EXITING))
return true;
return false;
}
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/oom_kill.c
*
* Copyright (C) 1998,2000 Rik van Riel
* Thanks go out to Claus Fischer for some serious inspiration and
* for goading me into coding this file...
* Copyright (C) 2010 Google, Inc.
* Rewritten by David Rientjes
*
* The routines in this file are used to kill a process when
* we're seriously out of memory. This gets called from __alloc_pages()
* in mm/page_alloc.c when we really run out of memory.
*
* Since we won't call these routines often (on a well-configured
* machine) this file will double as a 'coding guide' and a signpost
* for newbie kernel hackers. It features several pointers to major
* kernel subsystems and hints as to where to find out what things do.
*/
/*
* Official Linux v6.18: mm/oom_kill.c
* https://github.com/torvalds/linux/blob/v6.18/mm/oom_kill.c#L880
* Complete upstream scope, not an elided book-code reconstruction.
* See GPL-2.0.txt. Not a standalone translation unit.
*/
static bool task_will_free_mem(struct task_struct *task)
{
struct mm_struct *mm = task->mm;
struct task_struct *p;
bool ret = true;
/*
* Skip tasks without mm because it might have passed its exit_mm and
* exit_oom_victim. oom_reaper could have rescued that but do not rely
* on that for now. We can consider find_lock_task_mm in future.
*/
if (!mm)
return false;
if (!__task_will_free_mem(task))
return false;
/*
* This task has already been drained by the oom reaper so there are
* only small chances it will free some more
*/
if (mm_flags_test(MMF_OOM_SKIP, mm))
return false;
if (atomic_read(&mm->mm_users) <= 1)
return true;
/*
* Make sure that all tasks which share the mm with the given tasks
* are dying as well to make sure that a) nobody pins its mm and
* b) the task is also reapable by the oom reaper.
*/
rcu_read_lock();
for_each_process(p) {
if (!process_shares_mm(p, mm))
continue;
if (same_thread_group(task, p))
continue;
ret = __task_will_free_mem(p);
if (!ret)
break;
}
rcu_read_unlock();
return ret;
}
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/oom_kill.c
*
* Copyright (C) 1998,2000 Rik van Riel
* Thanks go out to Claus Fischer for some serious inspiration and
* for goading me into coding this file...
* Copyright (C) 2010 Google, Inc.
* Rewritten by David Rientjes
*
* The routines in this file are used to kill a process when
* we're seriously out of memory. This gets called from __alloc_pages()
* in mm/page_alloc.c when we really run out of memory.
*
* Since we won't call these routines often (on a well-configured
* machine) this file will double as a 'coding guide' and a signpost
* for newbie kernel hackers. It features several pointers to major
* kernel subsystems and hints as to where to find out what things do.
*/
/*
* Official Linux v6.18: mm/oom_kill.c
* https://github.com/torvalds/linux/blob/v6.18/mm/oom_kill.c#L493
* Complete upstream scope, not an elided book-code reconstruction.
* See GPL-2.0.txt. Not a standalone translation unit.
*/
bool process_shares_mm(const struct task_struct *p, const struct mm_struct *mm)
{
const struct task_struct *t;
for_each_thread(p, t) {
const struct mm_struct *t_mm = READ_ONCE(t->mm);
if (t_mm)
return t_mm == mm;
}
return false;
}
안쪽 helper는 core dump를 먼저 제외하고 group-exit 또는 thread_group_empty && PF_EXITING을 확인합니다. 바깥 helper는 안정적인 task->mm를 전제로 mm 존재·SKIP을 확인한 뒤, mm_users <= 1이면 빠른 긍정을 할 수 있습니다. 더 많은 user가 있으면 RCU 아래 다른 thread group의 mm 공유와 종료 상태를 검사합니다.
process_shares_mm()은 group 안에서 non-NULL mm를 가진 thread를 찾아 비교합니다. leader의 mm가 NULL이라는 이유만으로 group 전체를 mm 없는 process라고 보지 않습니다. 반대로 이 조회가 성공했다고 long-lived task reference나 mm descriptor reference를 자동으로 얻은 것도 아닙니다. 같은 signal handler라는 이유만으로 같은 thread group이라고 판정하지 않고 실제 same_thread_group() 조건을 사용합니다.
참고 자료
- Lorenzo Stoakes, The Linux Memory Manager, Early Access 2025, Chapter 13, The Out of Memory Killer.
- Linux v6.18 mm/oom_kill.c:
out_of_memory(),task_will_free_mem(),process_shares_mm(),oom_evaluate_task(). - Linux v6.18 include/linux/sched/signal.h: process·thread-group 순회.