Victim을 골랐는데 왜 다른 thread group에도 SIGKILL을 보낼까요?

OOM victim 하나를 선택해도 같은 mm를 쓰는 다른 thread group이 계속 allocation하면 address-space teardown이 막힐 수 있습니다. Linux의 kill 단계는 task와 thread group, mm의 관계를 다시 확인합니다. 선정 결과의 task pointer만 믿고 signal 하나를 보내는 것으로 끝나지 않습니다.

선택 뒤에도 exit 상태는 바뀔 수 있습니다

oom_kill_process()는 먼저 chosen task가 이미 memory를 반환할 exit 과정인지 다시 검사합니다. 그렇다면 불필요한 후속 kill을 늘리지 않고 victim 표시와 reaper 예약을 수행합니다. selection이 보유한 task reference는 이 판단 동안 object가 사라지지 않게 하며 끝에서 반환해야 합니다.

일반 경로에서는 rate limit을 적용해 OOM header와 victim 정보를 출력합니다. GFP·order·constraint·memory 상태·task usage를 함께 남기므로 단순한 'Killed process' 한 줄보다 원인을 이해하는 데 유용합니다. oom_dump_tasks는 이런 진단 출력의 일부를 제어하지 selection의 badness 계산을 대신하지 않습니다.

Signal 전에 mm를 확보합니다

__oom_kill_process()는 find_lock_task_mm()으로 아직 mm를 가진 thread를 다시 찾습니다. 원래 chosen task가 mm를 놓았다면 같은 group의 다른 task를 대상으로 reference를 교체할 수 있습니다. 아무도 mm를 갖지 않으면 이미 exit가 진행 중이므로 무리하게 kill을 이어가지 않습니다.

task lock을 놓은 뒤에도 mm를 비교할 수 있도록 별도 descriptor reference를 확보합니다. 그리고 SIGKILL을 먼저 전송한 뒤 mark_oom_victim()으로 reserve 접근을 허용합니다. 순서를 반대로 하면 user-controlled 실행이 reserve를 소모할 여지가 커집니다.

Victim 표시는 task와 mm 양쪽의 lifetime에 연결됩니다

v6.18의 mark_oom_victim()은 caller가 oom_lock을 보유하고 task의 mm를 안정적으로 사용할 수 있다는 계약 아래 호출합니다. TIF_MEMDIE를 test-and-set하여 같은 task의 중복 표시를 막습니다. thread group의 signal->oom_mm이 비어 있을 때에만 compare-and-exchange로 mm를 기록하고 별도의 mmgrab()으로 descriptor를 보존합니다. 이 구현이 MMF_OOM_VICTIM을 설정한다고 보아서는 안 됩니다.

이어 frozen victim을 깨우고 oom_victims를 늘립니다. 나중의 exit_oom_victim()은 TIF_MEMDIE와 count를 정리하며, 마지막 victim이 끝나면 대기자를 깨웁니다. victim 표시와 exit accounting도 signal 전송이나 reaper의 unmap과는 다른 단계입니다.

이 reference는 mm_users를 올려 모든 user mapping을 그대로 유지하려는 것이 아닙니다. exit가 mapping을 해체한 뒤에도 asynchronous reaper가 mm descriptor를 안전하게 확인할 수 있도록 하는 lifetime입니다. 나중 signal object 정리에서 이 보유분을 돌려놓는 경로까지 한 쌍으로 읽어야 합니다.

같은 mm를 쓰는 별도 group을 처리합니다

RCU 아래 process를 순회하며 mm를 공유하지 않는 group과 victim 자신의 thread group을 제외합니다. 남은 ordinary user sharer에는 SIGKILL을 보내 같은 address space에서 allocation과 mmap_lock 경합을 계속 만들지 못하게 합니다. 이런 follow-on kill은 새로운 점수 경쟁으로 선정한 victim과 동일한 사건이 아닙니다.

이 sharer마다 모두 TIF_MEMDIE를 부여해 reserve를 넓히는 것은 아닙니다. reserve depletion을 피해야 하기 때문입니다. fatal signal이 pending된 allocation 경로의 취급과 공식 OOM victim 표시를 구분해야 합니다.

global init가 mm를 붙잡는 특수 경우에는 init를 죽이지 않고 해당 mm에 SKIP을 설정하여 reaping을 피합니다. PF_KTHREAD sharer도 ordinary user group처럼 kill하지 않습니다. kernel의 mm 사용 규칙을 전제로 한 예외이며 모든 kernel reader에게 모든 user 내용이 계속 안정적이라는 보장은 아닙니다.

memcg group kill은 또 다른 확장입니다

memory.oom.group에 따른 group kill은 같은 mm sharer를 찾는 작업과 다릅니다. 선택된 memcg 또는 관련 group 범위의 task를 함께 종료하는 정책입니다. 이 경로는 -1000과 init에 대한 자체 제외 조건을 가집니다. 두 확장을 하나의 '자식 process까지 죽인다' 규칙으로 설명하면 대상 범위를 잘못 이해하게 됩니다.

마지막에는 필요한 경우 reaper를 예약하고 kill 경로가 임시 확보한 mm·task reference를 돌려놓습니다. SIGKILL이 실제 task의 exit를 진행시키고 reaper는 일부 address space를 미리 비우는 보조 역할입니다. reaper가 signal 대신 process를 죽이거나 모든 자원을 회수하는 것은 아닙니다.

선택된 task를 kill 경로로 넘깁니다

다음은 official Linux v6.18의 kill wrapper와 진단 함수 전체입니다. code는 upstream notice를 유지하고 GPL-2.0을 함께 제공합니다. log field를 설명하는 code이지 이 글을 위해 OOM을 실행한 출력은 아닙니다.

v6_18_oom_kill_process.c

// 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#L1023
 * Complete upstream scope, not an elided book-code reconstruction.
 * See GPL-2.0.txt. Not a standalone translation unit.
 */

static void oom_kill_process(struct oom_control *oc, const char *message)
{
	struct task_struct *victim = oc->chosen;
	struct mem_cgroup *oom_group;
	static DEFINE_RATELIMIT_STATE(oom_rs, DEFAULT_RATELIMIT_INTERVAL,
					      DEFAULT_RATELIMIT_BURST);

	/*
	 * If the task is already exiting, don't alarm the sysadmin or kill
	 * its children or threads, just give it access to memory reserves
	 * so it can die quickly
	 */
	task_lock(victim);
	if (task_will_free_mem(victim)) {
		mark_oom_victim(victim);
		queue_oom_reaper(victim);
		task_unlock(victim);
		put_task_struct(victim);
		return;
	}
	task_unlock(victim);

	if (__ratelimit(&oom_rs)) {
		dump_header(oc);
		dump_oom_victim(oc, victim);
	}

	/*
	 * Do we need to kill the entire memory cgroup?
	 * Or even one of the ancestor memory cgroups?
	 * Check this out before killing the victim task.
	 */
	oom_group = mem_cgroup_get_oom_group(victim, oc->memcg);

	__oom_kill_process(victim, message);

	/*
	 * If necessary, kill all tasks in the selected memory cgroup.
	 */
	if (oom_group) {
		memcg_memory_event(oom_group, MEMCG_OOM_GROUP_KILL);
		mem_cgroup_print_oom_group(oom_group);
		mem_cgroup_scan_tasks(oom_group, oom_kill_memcg_member,
				      (void *)message);
		mem_cgroup_put(oom_group);
	}
}

v6_18_dump_header.c

// 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#L459
 * Complete upstream scope, not an elided book-code reconstruction.
 * See GPL-2.0.txt. Not a standalone translation unit.
 */

static void dump_header(struct oom_control *oc)
{
	pr_warn("%s invoked oom-killer: gfp_mask=%#x(%pGg), order=%d, oom_score_adj=%hd\n",
		current->comm, oc->gfp_mask, &oc->gfp_mask, oc->order,
			current->signal->oom_score_adj);
	if (!IS_ENABLED(CONFIG_COMPACTION) && oc->order)
		pr_warn("COMPACTION is disabled!!!\n");

	dump_stack();
	if (is_memcg_oom(oc))
		mem_cgroup_print_oom_meminfo(oc->memcg);
	else {
		__show_mem(SHOW_MEM_FILTER_NODES, oc->nodemask, gfp_zone(oc->gfp_mask));
		if (should_dump_unreclaim_slab())
			dump_unreclaimable_slab();
	}
	if (sysctl_oom_dump_tasks)
		dump_tasks(oc);
}

v6_18_dump_tasks.c

// 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#L425
 * Complete upstream scope, not an elided book-code reconstruction.
 * See GPL-2.0.txt. Not a standalone translation unit.
 */

static void dump_tasks(struct oom_control *oc)
{
	pr_info("Tasks state (memory values in pages):\n");
	pr_info("[  pid  ]   uid  tgid total_vm      rss rss_anon rss_file rss_shmem pgtables_bytes swapents oom_score_adj name\n");

	if (is_memcg_oom(oc))
		mem_cgroup_scan_tasks(oc->memcg, dump_task, oc);
	else {
		struct task_struct *p;
		int i = 0;

		rcu_read_lock();
		for_each_process(p) {
			/* Avoid potential softlockup warning */
			if ((++i & 1023) == 0)
				touch_softlockup_watchdog();
			dump_task(p, oc);
		}
		rcu_read_unlock();
	}
}

v6_18_dump_oom_victim.c

// 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#L447
 * Complete upstream scope, not an elided book-code reconstruction.
 * See GPL-2.0.txt. Not a standalone translation unit.
 */

static void dump_oom_victim(struct oom_control *oc, struct task_struct *victim)
{
	/* one line summary of the oom killer context. */
	pr_info("oom-kill:constraint=%s,nodemask=%*pbl",
			oom_constraint_text[oc->constraint],
			nodemask_pr_args(oc->nodemask));
	cpuset_print_current_mems_allowed();
	mem_cgroup_print_oom_context(oc->memcg, victim);
	pr_cont(",task=%s,pid=%d,uid=%d\n", victim->comm, victim->pid,
		from_kuid(&init_user_ns, task_uid(victim)));
}

header의 current는 OOM을 호출한 task입니다. victim summary의 task와 반드시 같지는 않습니다. global은 node-filtered memory 상태와 필요한 slab 정보를, memcg는 해당 group 정보를 출력합니다. task table의 많은 memory 값은 pages지만 pgtables_bytes는 bytes라는 field 이름을 따릅니다. 행의 크기만 비교하기 전에 단위를 맞춰야 합니다.

wrapper는 group-kill 여부를 victim kill 전에 확보합니다. victim이 이미 빠르게 exit할 것으로 판단되면 marking·queue만 처리하고 selection reference를 반환합니다. normal path는 header rate limit 뒤 __oom_kill_process()에 victim reference를 넘기며, 선택된 memcg group이 있으면 그 범위도 별도로 scan하고 group reference를 반환합니다.

mm sharer를 처리하는 전체 함수

v6_18___oom_kill_process.c

// 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#L927
 * Complete upstream scope, not an elided book-code reconstruction.
 * See GPL-2.0.txt. Not a standalone translation unit.
 */

static void __oom_kill_process(struct task_struct *victim, const char *message)
{
	struct task_struct *p;
	struct mm_struct *mm;
	bool can_oom_reap = true;

	p = find_lock_task_mm(victim);
	if (!p) {
		pr_info("%s: OOM victim %d (%s) is already exiting. Skip killing the task\n",
			message, task_pid_nr(victim), victim->comm);
		put_task_struct(victim);
		return;
	} else if (victim != p) {
		get_task_struct(p);
		put_task_struct(victim);
		victim = p;
	}

	/* Get a reference to safely compare mm after task_unlock(victim) */
	mm = victim->mm;
	mmgrab(mm);

	/* Raise event before sending signal: task reaper must see this */
	count_vm_event(OOM_KILL);
	memcg_memory_event_mm(mm, MEMCG_OOM_KILL);

	/*
	 * We should send SIGKILL before granting access to memory reserves
	 * in order to prevent the OOM victim from depleting the memory
	 * reserves from the user space under its control.
	 */
	do_send_sig_info(SIGKILL, SEND_SIG_PRIV, victim, PIDTYPE_TGID);
	mark_oom_victim(victim);
	pr_err("%s: Killed process %d (%s) total-vm:%lukB, anon-rss:%lukB, file-rss:%lukB, shmem-rss:%lukB, UID:%u pgtables:%lukB oom_score_adj:%hd\n",
		message, task_pid_nr(victim), victim->comm, K(mm->total_vm),
		K(get_mm_counter(mm, MM_ANONPAGES)),
		K(get_mm_counter(mm, MM_FILEPAGES)),
		K(get_mm_counter(mm, MM_SHMEMPAGES)),
		from_kuid(&init_user_ns, task_uid(victim)),
		mm_pgtables_bytes(mm) >> 10, victim->signal->oom_score_adj);
	task_unlock(victim);

	/*
	 * Kill all user processes sharing victim->mm in other thread groups, if
	 * any.  They don't get access to memory reserves, though, to avoid
	 * depletion of all memory.  This prevents mm->mmap_lock livelock when an
	 * oom killed thread cannot exit because it requires the semaphore and
	 * its contended by another thread trying to allocate memory itself.
	 * That thread will now get access to memory reserves since it has a
	 * pending fatal signal.
	 */
	rcu_read_lock();
	for_each_process(p) {
		if (!process_shares_mm(p, mm))
			continue;
		if (same_thread_group(p, victim))
			continue;
		if (is_global_init(p)) {
			can_oom_reap = false;
			mm_flags_set(MMF_OOM_SKIP, mm);
			pr_info("oom killer %d (%s) has mm pinned by %d (%s)\n",
					task_pid_nr(victim), victim->comm,
					task_pid_nr(p), p->comm);
			continue;
		}
		/*
		 * No kthread_use_mm() user needs to read from the userspace so
		 * we are ok to reap it.
		 */
		if (unlikely(p->flags & PF_KTHREAD))
			continue;
		do_send_sig_info(SIGKILL, SEND_SIG_PRIV, p, PIDTYPE_TGID);
	}
	rcu_read_unlock();

	if (can_oom_reap)
		queue_oom_reaper(victim);

	mmdrop(mm);
	put_task_struct(victim);
}

v6_18_oom_kill_memcg_member.c

// 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#L1013
 * Complete upstream scope, not an elided book-code reconstruction.
 * See GPL-2.0.txt. Not a standalone translation unit.
 */

static int oom_kill_memcg_member(struct task_struct *task, void *message)
{
	if (task->signal->oom_score_adj != OOM_SCORE_ADJ_MIN &&
	    !is_global_init(task)) {
		get_task_struct(task);
		__oom_kill_process(task, message);
	}
	return 0;
}

아직 mm를 가진 thread로 victim을 교체할 때 새 task reference를 먼저 얻고 옛 selection reference를 반환합니다. mm를 확보한 뒤 OOM event를 기록하고, signal을 먼저 보낸 다음 victim 표시와 진단을 수행합니다. task lock은 그 뒤 풀지만 임시 mm descriptor reference는 공유자 비교가 끝날 때까지 유지합니다.

다른 group의 ordinary mm sharer에게 signal을 보내는 loop에는 새 badness 계산이나 -1000 gate가 없습니다. 같은 mm를 계속 유지하는 group을 정리하는 경로이기 때문입니다. memcg member callback은 이와 달리 자기 -1000·global-init 제외를 검사한 뒤 reference를 얻어 kill 함수로 전달합니다. 'group 단위'라는 말이 같아도 두 loop의 범위와 gate는 같지 않습니다.

global init가 같은 mm를 유지하면 can_oom_reap=false와 SKIP으로 추가 reap을 막습니다. kernel-thread sharer는 signal을 보내지 않고 지나갑니다. normal sharer에게 signal을 보냈다는 이유만으로 모두 mark_oom_victim()을 호출하지는 않습니다. 끝에서 queue를 예약할 수 있는지 확인한 다음 임시 mmdrop()과 task put을 맞춥니다.

고전의 MMF_OOM_VICTIM을 최신 flag로 읽지 않습니다

아래 v6.0 전체 marking 함수는 최초 signal->oom_mm 등록 때 MMF_OOM_VICTIM도 설정합니다. 이것은 v6.18 marking과 실제 차이입니다.

v6_0_mark_oom_victim.c

// 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.0: mm/oom_kill.c
 * https://github.com/torvalds/linux/blob/v6.0/mm/oom_kill.c#L757
 * Complete upstream scope, not an elided book-code reconstruction.
 * See GPL-2.0.txt. Not a standalone translation unit.
 */

static void mark_oom_victim(struct task_struct *tsk)
{
	struct mm_struct *mm = tsk->mm;

	WARN_ON(oom_killer_disabled);
	/* OOM killer might race with memcg OOM */
	if (test_and_set_tsk_thread_flag(tsk, TIF_MEMDIE))
		return;

	/* oom_mm is bound to the signal struct life time. */
	if (!cmpxchg(&tsk->signal->oom_mm, NULL, mm)) {
		mmgrab(tsk->signal->oom_mm);
		set_bit(MMF_OOM_VICTIM, &mm->flags);
	}

	/*
	 * Make sure that the task is woken up from uninterruptible sleep
	 * if it is frozen because OOM killer wouldn't be able to free
	 * any memory and livelock. freezing_slow_path will tell the freezer
	 * that TIF_MEMDIE tasks should be ignored.
	 */
	__thaw_task(tsk);
	atomic_inc(&oom_victims);
	trace_mark_victim(tsk->pid);
}

v6_18_mark_oom_victim.c

// 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#L766
 * Complete upstream scope, not an elided book-code reconstruction.
 * See GPL-2.0.txt. Not a standalone translation unit.
 */

static void mark_oom_victim(struct task_struct *tsk)
{
	const struct cred *cred;
	struct mm_struct *mm = tsk->mm;

	WARN_ON(oom_killer_disabled);
	/* OOM killer might race with memcg OOM */
	if (test_and_set_tsk_thread_flag(tsk, TIF_MEMDIE))
		return;

	/* oom_mm is bound to the signal struct life time. */
	if (!cmpxchg(&tsk->signal->oom_mm, NULL, mm))
		mmgrab(tsk->signal->oom_mm);

	/*
	 * Make sure that the process is woken up from uninterruptible sleep
	 * if it is frozen because OOM killer wouldn't be able to free any
	 * memory and livelock. The freezer will thaw the tasks that are OOM
	 * victims regardless of the PM freezing and cgroup freezing states.
	 */
	thaw_process(tsk);
	atomic_inc(&oom_victims);
	cred = get_task_cred(tsk);
	trace_mark_victim(tsk, cred->uid.val);
	put_cred(cred);
}

v6_18_exit_oom_victim.c

// 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#L796
 * Complete upstream scope, not an elided book-code reconstruction.
 * See GPL-2.0.txt. Not a standalone translation unit.
 */

void exit_oom_victim(void)
{
	clear_thread_flag(TIF_MEMDIE);

	if (!atomic_dec_return(&oom_victims))
		wake_up_all(&oom_victims_wait);
}

v6.18은 TIF_MEMDIE 중복 검사를 유지하고 signal-owned mm reference를 확보하지만 그 old mm flag를 설정하지 않습니다. thaw와 victim count, credential을 보유하는 trace까지 포함한 각 tag의 전체 함수를 기준으로 보아야 합니다. exit_oom_victim()의 thread flag·count 정리와 signal object의 mm reference 정리는 서로 다른 수명에 대응합니다.

참고 자료