RSS가 가장 큰 process가 살아남고 더 작은 process가 죽었다면 점수 계산이 잘못되었다고 생각하기 쉽습니다. 그러나 OOM은 먼저 이번 allocation에 도움이 될 후보 범위를 정하고, 제외 조건을 통과한 task 사이에서 점수를 비교합니다. 모든 process가 한 장의 전역 순위표에서 경쟁하는 것은 아닙니다.
Domain을 벗어난 process는 점수로 들어오지 못합니다
global OOM에서도 mempolicy·cpuset 조건에 따라 candidate eligibility가 제한될 수 있습니다. memcg OOM은 해당 group의 범위에서 scan하며 totalpages 기준도 달라집니다. nodemask와 task의 policy가 맞는다는 것은 그 process의 RSS가 모두 그 node에 물리적으로 놓였다는 뜻이 아닙니다.

global init와 PF_KTHREAD task는 ordinary victim 선택에서 제외합니다. 유효한 mm를 가진 thread를 찾을 수 없는 group도 점수를 계산할 기반이 없습니다. find_lock_task_mm()은 group 안에서 mm를 유지하는 thread를 찾아 task lock을 잡아 주지만, task struct의 장기 lifetime reference나 mm의 별도 reference까지 자동으로 모두 대신하는 함수는 아닙니다.
Badness는 RSS만의 함수가 아닙니다
ordinary oom_badness()는 resident base pages, swap entries, page-table memory를 합산합니다. 여기에 oom_score_adj를 이번 OOM의 totalpages 기준 base page 양으로 변환해 더합니다. 개념적으로는 다음과 같습니다. 실제 정수 연산은 totalpages / 1000을 먼저 수행하는 등 절삭이 있습니다.
base = resident_pages + swap_entries + page_table_pages
points = base + oom_score_adj * (totalpages / 1000)
예를 들어 totalpages가 1,000,000이고 base가 300,000, adj가 -400이면 points는 -100,000입니다. 음수도 signed comparison의 정상 값이며, 다른 후보가 더 낮으면 선택될 수 있습니다. 이것을 '40%까지 안전하게 쓸 수 있는 quota'로 해석해서는 안 됩니다.
기본 adj=0은 동일한 생존 확률을 주지 않습니다. base가 다르면 점수도 다릅니다. +1000도 eligibility를 무시하거나 언제나 무조건 먼저 죽이는 명령이 아닙니다. 값을 낮추는 권한 조건과 legacy oom_adj 호환 interface도 정책값을 읽을 때 별도로 고려해야 합니다.
-1000 보호의 범위를 정확히 봅니다
OOM_SCORE_ADJ_MIN=-1000은 ordinary badness 경로에서 특별히 제외하는 값입니다. MMF_OOM_SKIP, vfork child도 일반 후보에서 빠집니다. vfork child는 parent와 address space를 공유하며 부모가 별도로 평가되므로 독립적으로 free할 memory를 과대평가하지 않으려는 취지입니다.
vfork 중에는 parent가 child의 exit 또는 exec까지 기다리고, child는 parent의 address space를 빌려 사용합니다. ordinary fork처럼 새 page-table 사본을 가진 독립적인 memory 소유자로 점수화하면 같은 mm의 회수 가능성을 잘못 판단하게 됩니다. parent와 child 사이의 이 대기·공유 관계는 OOM victim 선정 뒤의 다른 shared-mm group kill과도 구별해야 합니다.
다만 shared-mm sharer를 함께 종료하는 후속 경로와 origin 우선 선택 같은 특수 branch는 ordinary badness 계산과 분리해야 합니다. memcg의 group kill에는 다시 자체적인 -1000 예외가 있습니다. '어떤 형태의 OOM 처리에서도 절대 죽지 않는다'는 일반 보증으로 쓰면 안 됩니다.
Scan은 가장 큰 점수 외의 종료 조건도 가집니다
select_bad_process()는 chosen_points를 최소값으로 시작하고 eligible task를 평가합니다. 더 적합한 task로 교체할 때 이전 task reference를 내리고 새 task reference를 올려 scan 이후에도 object가 남도록 합니다. RCU 순회 보호만으로 오래 보관하는 pointer lifetime을 해결하지 않습니다.
일반 pressure OOM에서 기존 victim이 아직 도움을 줄 수 있으면 scan을 abort하고 sentinel을 남깁니다. 이미 SKIP인 victim은 그 이유로 계속 기다리지 않습니다. 큰 allocation의 origin으로 표시된 task에는 normal badness보다 우선하는 특별 취급이 있을 수 있습니다. 이 순서를 빼고 점수식만 읽으면 결과를 설명하지 못합니다.
proc의 oom_score는 raw points가 아닙니다
/proc/PID/oom_score는 user-visible 정규화 interface입니다. system RAM·swap 기준의 badness를 1000 offset과 2/3 scaling을 사용해 표현하며 최소값 제외 같은 경우를 특별 처리합니다. memcg OOM의 domain-specific chosen_points를 그대로 내보내는 값이 아닙니다.
따라서 다른 시점에 읽은 oom_score를 당시 OOM log와 기계적으로 비교하거나, 이것을 확률로 읽어서는 안 됩니다. memory 사용량·policy·domain·동시 exit 상태가 달라질 수 있습니다. domain과 eligibility를 먼저 확인한 뒤 점수와 특수 branch를 읽는 순서가 더 정확합니다.
Domain 기준량과 candidate 검사는 별도 함수입니다
아래 official Linux v6.18 code는 domain을 정하는 전체 함수와 NUMA-enabled candidate helper입니다. CONFIG_NUMA가 꺼진 fallback helper는 true를 반환하지만, 여기 제시한 NUMA 경로는 thread group 안의 각 task에 대해 허용 node의 교집합을 검사합니다. 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#L252
* Complete upstream scope, not an elided book-code reconstruction.
* See GPL-2.0.txt. Not a standalone translation unit.
*/
static enum oom_constraint constrained_alloc(struct oom_control *oc)
{
struct zone *zone;
struct zoneref *z;
enum zone_type highest_zoneidx = gfp_zone(oc->gfp_mask);
bool cpuset_limited = false;
int nid;
if (is_memcg_oom(oc)) {
oc->totalpages = mem_cgroup_get_max(oc->memcg) ?: 1;
return CONSTRAINT_MEMCG;
}
/* Default to all available memory */
oc->totalpages = totalram_pages() + total_swap_pages;
if (!IS_ENABLED(CONFIG_NUMA))
return CONSTRAINT_NONE;
if (!oc->zonelist)
return CONSTRAINT_NONE;
/*
* Reach here only when __GFP_NOFAIL is used. So, we should avoid
* to kill current.We have to random task kill in this case.
* Hopefully, CONSTRAINT_THISNODE...but no way to handle it, now.
*/
if (oc->gfp_mask & __GFP_THISNODE)
return CONSTRAINT_NONE;
/*
* This is not a __GFP_THISNODE allocation, so a truncated nodemask in
* the page allocator means a mempolicy is in effect. Cpuset policy
* is enforced in get_page_from_freelist().
*/
if (oc->nodemask &&
!nodes_subset(node_states[N_MEMORY], *oc->nodemask)) {
oc->totalpages = total_swap_pages;
for_each_node_mask(nid, *oc->nodemask)
oc->totalpages += node_present_pages(nid);
return CONSTRAINT_MEMORY_POLICY;
}
/* Check this allocation failure is caused by cpuset's wall function */
for_each_zone_zonelist_nodemask(zone, z, oc->zonelist,
highest_zoneidx, oc->nodemask)
if (!cpuset_zone_allowed(zone, oc->gfp_mask))
cpuset_limited = true;
if (cpuset_limited) {
oc->totalpages = total_swap_pages;
for_each_node_mask(nid, cpuset_current_mems_allowed)
oc->totalpages += node_present_pages(nid);
return CONSTRAINT_CPUSET;
}
return CONSTRAINT_NONE;
}
// 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#L90
* Complete upstream scope, not an elided book-code reconstruction.
* See GPL-2.0.txt. Not a standalone translation unit.
*/
static bool oom_cpuset_eligible(struct task_struct *start,
struct oom_control *oc)
{
struct task_struct *tsk;
bool ret = false;
const nodemask_t *mask = oc->nodemask;
rcu_read_lock();
for_each_thread(start, tsk) {
if (mask) {
/*
* If this is a mempolicy constrained oom, tsk's
* cpuset is irrelevant. Only return true if its
* mempolicy intersects current, otherwise it may be
* needlessly killed.
*/
ret = mempolicy_in_oom_domain(tsk, mask);
} else {
/*
* This is not a mempolicy constrained oom, so only
* check the mems of tsk's cpuset.
*/
ret = cpuset_mems_allowed_intersects(current, tsk);
}
if (ret)
break;
}
rcu_read_unlock();
return ret;
}
memcg OOM은 mem_cgroup_get_max()를 기준량으로 쓰며 0이면 1을 사용합니다. global의 기본은 total RAM+swap입니다. mempolicy에 한정되면 해당 mask의 node-present pages와 swap, cpuset에 한정되면 current의 allowed nodes와 swap으로 기준을 바꿉니다. 이 값은 candidate가 실제 차지한 physical node별 RSS를 합산한 값이 아닙니다.
candidate helper는 mempolicy mask가 있으면 mempolicy domain을 검사하고, 그렇지 않으면 current와 candidate의 cpuset memory 허용 범위가 겹치는지 검사합니다. memcg scan은 이미 대상 group으로 한정되어 이 global cpuset gate와 별도로 진행합니다.
점수 계산은 lock과 제외 조건까지 포함합니다
// 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#L163
* Complete upstream scope, not an elided book-code reconstruction.
* See GPL-2.0.txt. Not a standalone translation unit.
*/
static bool oom_unkillable_task(struct task_struct *p)
{
if (is_global_init(p))
return true;
if (p->flags & PF_KTHREAD)
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#L134
* Complete upstream scope, not an elided book-code reconstruction.
* See GPL-2.0.txt. Not a standalone translation unit.
*/
struct task_struct *find_lock_task_mm(struct task_struct *p)
{
struct task_struct *t;
rcu_read_lock();
for_each_thread(p, t) {
task_lock(t);
if (likely(t->mm))
goto found;
task_unlock(t);
}
t = NULL;
found:
rcu_read_unlock();
return t;
}
// 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#L202
* Complete upstream scope, not an elided book-code reconstruction.
* See GPL-2.0.txt. Not a standalone translation unit.
*/
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 ||
mm_flags_test(MMF_OOM_SKIP, p->mm) ||
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;
}
find_lock_task_mm()은 찾은 task의 lock을 잡은 채 반환합니다. 호출자는 mm 사용량과 adjustment를 읽은 뒤 unlock해야 합니다. 실패한 thread의 lock은 loop 안에서 바로 반환하고, 찾지 못하면 NULL입니다. task의 장기 수명을 맡는 get/put과 이 lock 계약을 분리해서 읽으셔야 합니다.
oom_score_adj의 범위는 -1000부터 +1000이고 기본값은 0입니다. 예를 들어 adj=+20은 이번 기준량의 약 2%에 해당하는 page 수를 더하는 가산점이지 kill 확률 2% 증가가 아닙니다. -1000은 단순히 큰 음수 가산점을 주는 것과 달리 LONG_MIN 제외로 처리합니다. RSS만 큰 task, virtual range만 큰 task, swap·page table 부담이 큰 task의 결과가 다를 수 있습니다.
Scan의 교체와 abort에는 reference 정리가 있습니다
// 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#L365
* Complete upstream scope, not an elided book-code reconstruction.
* See GPL-2.0.txt. Not a standalone translation unit.
*/
static void select_bad_process(struct oom_control *oc)
{
oc->chosen_points = LONG_MIN;
if (is_memcg_oom(oc))
mem_cgroup_scan_tasks(oc->memcg, oom_evaluate_task, oc);
else {
struct task_struct *p;
rcu_read_lock();
for_each_process(p)
if (oom_evaluate_task(p, oc))
break;
rcu_read_unlock();
}
}
// 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#L309
* Complete upstream scope, not an elided book-code reconstruction.
* See GPL-2.0.txt. Not a standalone translation unit.
*/
static int oom_evaluate_task(struct task_struct *task, void *arg)
{
struct oom_control *oc = arg;
long points;
if (oom_unkillable_task(task))
goto next;
/* p may not have freeable memory in nodemask */
if (!is_memcg_oom(oc) && !oom_cpuset_eligible(task, oc))
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 (mm_flags_test(MMF_OOM_SKIP, task->signal->oom_mm))
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 = LONG_MAX;
goto select;
}
points = oom_badness(task, oc->totalpages);
if (points == LONG_MIN || points < oc->chosen_points)
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;
}
계속 진행은 0, abort는 1입니다. 새 점수가 기존보다 작으면 넘기지만 같은 점수는 뒤에 방문한 후보로 교체될 수 있습니다. 이 tie 처리를 고정된 사용자 우선순위로 해석하지 않습니다. 후보 교체 때 이전 task reference를 반환하고 새 reference를 얻으며, abort 때도 이미 고른 task reference를 반환한 뒤 sentinel을 기록합니다.
origin 우선 branch는 unkillable·domain gate와 기존 victim 검사 뒤, ordinary badness 앞에 있습니다. 따라서 ordinary badness의 -1000·vfork·SKIP 검사만 보고 이 특수 경로까지 같은 보호를 받는다고 확장할 수 없습니다. ordinary badness의 제외와 shared-mm 후속 kill 범위도 서로 다른 단계입니다.
고전 v6.0의 실제 사용 예를 보면 이 표식의 목적이 더 분명합니다. tracing ring buffer는 user thread의 대량 page allocation loop 동안 origin을 설정하고 성공·실패 양쪽에서 지웁니다. KSM은 unmerge 요청을 수행하는 동안, swapoff는 try_to_unuse() 동안 current를 origin으로 표시합니다. OOM을 마지막으로 호출한 task가 대량 memory 소비 작업의 주체와 다를 수 있기 때문입니다. 각각 tracing ring buffer, KSM unmerge, swapoff의 call site로 확인할 수 있습니다. 이 예를 모든 kernel allocation에 자동 적용되는 분류로 일반화하지 않습니다.
proc 출력은 별도의 정규화입니다
// SPDX-License-Identifier: GPL-2.0
/*
* linux/fs/proc/base.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*
* proc base directory handling functions
*
* 1999, Al Viro. Rewritten. Now it covers the whole per-process part.
* Instead of using magical inumbers to determine the kind of object
* we allocate and fill in-core inodes upon lookup. They don't even
* go into icache. We cache the reference to task_struct upon lookup too.
* Eventually it should become a filesystem in its own. We don't use the
* rest of procfs anymore.
*
*
* Changelog:
* 17-Jan-2005
* Allan Bezerra
* Bruna Moreira <bruna.moreira@indt.org.br>
* Edjard Mota <edjard.mota@indt.org.br>
* Ilias Biris <ilias.biris@indt.org.br>
* Mauricio Lin <mauricio.lin@indt.org.br>
*
* Embedded Linux Lab - 10LE Instituto Nokia de Tecnologia - INdT
*
* A new process specific entry (smaps) included in /proc. It shows the
* size of rss for each memory area. The maps entry lacks information
* about physical memory size (rss) for each mapped file, i.e.,
* rss information for executables and library files.
* This additional information is useful for any tools that need to know
* about physical memory consumption for a process specific library.
*
* Changelog:
* 21-Feb-2005
* Embedded Linux Lab - 10LE Instituto Nokia de Tecnologia - INdT
* Pud inclusion in the page table walking.
*
* ChangeLog:
* 10-Mar-2005
* 10LE Instituto Nokia de Tecnologia - INdT:
* A better way to walks through the page table as suggested by Hugh Dickins.
*
* Simo Piiroinen <simo.piiroinen@nokia.com>:
* Smaps information related to shared, private, clean and dirty pages.
*
* Paul Mundt <paul.mundt@nokia.com>:
* Overall revision about smaps.
*/
/*
* Official Linux v6.18: fs/proc/base.c
* https://github.com/torvalds/linux/blob/v6.18/fs/proc/base.c#L585
* Complete upstream scope, not an elided book-code reconstruction.
* See GPL-2.0.txt. Not a standalone translation unit.
*/
static int proc_oom_score(struct seq_file *m, struct pid_namespace *ns,
struct pid *pid, struct task_struct *task)
{
unsigned long totalpages = totalram_pages() + total_swap_pages;
unsigned long points = 0;
long badness;
badness = oom_badness(task, totalpages);
/*
* Special case OOM_SCORE_ADJ_MIN for all others scale the
* badness value into [0, 2000] range which we have been
* exporting for a long time so userspace might depend on it.
*/
if (badness != LONG_MIN)
points = (1000 + badness * 1000 / (long)totalpages) * 2 / 3;
seq_printf(m, "%lu\n", points);
return 0;
}
이 interface는 전역 RAM+swap을 분모로 1000 + badness * 1000 / totalpages를 계산한 뒤 2/3로 scaling합니다. LONG_MIN이면 0을 출력합니다. 정수 절삭과 제외 조건을 포함한 호환 interface이지 당시 constrained OOM의 내부 points를 저장한 기록은 아닙니다. 앞의 음수 점수 예시도 식의 가상 계산이며 실제 process를 대상으로 측정한 값이 아닙니다.
참고 자료
- Lorenzo Stoakes, The Linux Memory Manager, Early Access 2025, Chapter 13, Score Adjustment / OOM Score / Victim Selection.
- Linux v6.18 mm/oom_kill.c: candidate gate와 badness.
- Linux v6.18 fs/proc/base.c:
proc_oom_score()와 adjustment interface. - proc_pid_oom_score_adj(5).