Process / Syscall · Linux v6.6 · source + diagram note

exit

do_exit()의 자원 정리, exit_notify()의 조건부 통지, 부모 wait의 상태 수거.

전체 흐름

Linux v6.6의 종료는 호출 중인 태스크가 do_exit()에서 자원을 정리하고, exit_notify()가 부모·추적자와의 관계를 정리한 뒤, 별도의 wait 또는 자동 수거 경로가 수거 절차를 진행하는 과정이다. exit() 호출과 release_task()를 같은 시점으로 읽으면 안 된다.

그림 1. 종료 태스크와 부모 wait는 서로 다른 실행 경로
종료 태스크: do_exit()exit_signals → exit_mm → trace_sched_process_exit → exit_files / exit_fs
exit_notify()자식 재부모화, exit_state 설정, 통지와 자동 수거 결정
부모·추적자 wait남은 EXIT_ZOMBIE를 조회하거나 수거
종료 태스크do_task_dead()do_exit() 끝에서 스케줄러 실행을 멈춘다.
수거 경로release_task()자동 수거면 exit_notify() 안에서, 아니면 조건을 만족한 wait 뒤에 호출한다.

점선 화살표는 반드시 wait가 실행된다는 뜻이 아니다. 자동 수거 시 release_task()는 do_task_dead()보다 먼저 실행될 수도 있다.

근거: v6.6 do_exit(), exit_notify(), wait_task_zombie().

소스 코드 위치

먼저 확인할 Linux v6.6 소스 파일

원본 코드:
Linux v6.6: kernel/exit.c
Linux v6.6: kernel/signal.c
Linux v6.6: kernel/fork.c

설명: 종료와 wait의 상태 전이는 kernel/exit.c에 있고, 부모의 자동 수거 판단은 kernel/signal.c의 do_notify_parent()에도 걸린다. kernel/fork.c는 생성 경로와 대조할 때 참고한다.

대표 코드

아래 첫 발췌는 do_exit()의 앞부분만, 둘째 발췌는 exit_notify()의 잠금 해제까지 보여 준다. 뒤따르는 자원 정리와 dead 목록의 release_task()는 v6.6 do_exit 뒷부분과 exit_notify 뒷부분에서 따로 확인해야 한다.

void __noreturn do_exit

원본 코드: kernel/exit.c, 804행 부근
		signal->group_stop_count = 0;
	}
	spin_unlock_irq(&sighand->siglock);
}

void __noreturn do_exit(long code)
{
	struct task_struct *tsk = current;
	int group_dead;

	WARN_ON(irqs_disabled());

	synchronize_group_exit(tsk, code);

	WARN_ON(tsk->plug);

	kcov_task_exit(tsk);
	kmsan_task_exit(tsk);

	coredump_task_exit(tsk);
	ptrace_event(PTRACE_EVENT_EXIT, code);
	user_events_exit(tsk);

	validate_creds_for_do_exit(tsk);

	io_uring_files_cancel();
	exit_signals(tsk);  /* sets PF_EXITING */

	/* sync mm's RSS info before statistics gathering */
	if (tsk->mm)
		sync_mm_rss(tsk->mm);
	acct_update_integrals(tsk);
	group_dead = atomic_dec_and_test(&tsk->signal->live);
	if (group_dead) {
		/*
		 * If the last thread of global init has exited, panic
		 * immediately to get a useable coredump.
		 */
		if (unlikely(is_global_init(tsk)))
			panic("Attempted to kill init! exitcode=0x%08x\n",

확인 사항: 이 발췌는 exit_signals()의 PF_EXITING 설정과 thread group의 live 감소까지 보인다. exit_mm()·tracepoint·파일/파일시스템 정리·exit_notify()는 발췌 뒤에 있다.

exit_notify

원본 코드: kernel/exit.c, 722행 부근

/*
 * Send signals to all our closest relatives so that they know
 * to properly mourn us..
 */
static void exit_notify(struct task_struct *tsk, int group_dead)
{
	bool autoreap;
	struct task_struct *p, *n;
	LIST_HEAD(dead);

	write_lock_irq(&tasklist_lock);
	forget_original_parent(tsk, &dead);

	if (group_dead)
		kill_orphaned_pgrp(tsk->group_leader, NULL);

	tsk->exit_state = EXIT_ZOMBIE;
	if (unlikely(tsk->ptrace)) {
		int sig = thread_group_leader(tsk) &&
				thread_group_empty(tsk) &&
				!ptrace_reparented(tsk) ?
			tsk->exit_signal : SIGCHLD;
		autoreap = do_notify_parent(tsk, sig);
	} else if (thread_group_leader(tsk)) {
		autoreap = thread_group_empty(tsk) &&
			do_notify_parent(tsk, tsk->exit_signal);
	} else {
		autoreap = true;
	}

	if (autoreap) {
		tsk->exit_state = EXIT_DEAD;
		list_add(&tsk->ptrace_entry, &dead);
	}

	/* mt-exec, de_thread() is waiting for group leader */
	if (unlikely(tsk->signal->notify_count < 0))
		wake_up_process(tsk->signal->group_exec_task);
	write_unlock_irq(&tasklist_lock);

확인 사항: 잠금 안에서 우선 EXIT_ZOMBIE가 되고, ptrace·leader·non-leader 분기에 따라 autoreap가 달라진다. 발췌 뒤의 dead 목록 순회에서만 release_task()가 보인다.

함수별 분석

do_exit()는 오류를 반환하며 복구하는 경로가 아니라 __noreturn 경로다. exit_notify()가 부모에게 종료를 알리는 시점과 release_task()가 참조를 정리하는 시점은 분리된다.

그림 2. v6.6 exit_notify()의 조건부 분기
tasklist_lock 아래 exit_state = EXIT_ZOMBIE먼저 forget_original_parent()로 자식의 부모 관계를 정리한다.
ptrace 중do_notify_parent(tsk, sig)추적자/실제 부모 관계에 맞는 sig를 고른다.
thread group leaderthread_group_empty()?비어 있을 때만 do_notify_parent() 결과로 자동 수거 판단.
non-leaderautoreap = true부모가 개별 스레드를 wait하는 일반적인 child 경로와 구분.
falseEXIT_ZOMBIE 유지대상 부모·추적자가 나중에 wait할 수 있다.
trueEXIT_DEAD → dead 목록 → release_task()tasklist_lock을 푼 뒤 목록을 순회한다.

do_notify_parent()의 true는 ptrace가 아닌 SIGCHLD 통지에서 부모가 SIGCHLD를 명시적으로 무시하거나 SA_NOCLDWAIT를 사용한 경우다. 그룹 리더는 살아 있는 형제 스레드가 있으면 즉시 부모 통지·자동 수거되지 않는다.

함수·행하는 일구분할 시점
do_exit()
exit.c 809–926
exit_mm() 후 tracepoint를 찍고, 파일·fs·thread 등 정리 뒤 exit_notify()와 do_task_dead()로 간다.tracepoint가 최종 release를 뜻하지 않는다.
exit_notify()
exit.c 727–767
재부모화 후 EXIT_ZOMBIE, 분기별 자동 수거 여부를 결정한다.조건부 EXIT_DEAD와 dead 목록.
do_notify_parent()
signal.c 2058–2154
부모/추적자 통지, SIGCHLD 처리 설정에 따른 self-reaping 반환.통지와 장기 zombie 여부는 다르다.
wait_task_zombie()
exit.c 1099–1210
WNOWAIT는 관찰만, 일반 수거는 상태 소유권을 가져와 조건부 release.부모의 별도 실행 문맥.

구조체와 필드

종료가 모두 한 필드에 기록되는 것은 아니다. task_struct.__state는 스케줄러의 실행 가능 상태이고 exit_state는 wait/reap 상태다. exit_code와 signal의 group 종료 정보는 부모가 보고할 상태와 자원 통계에 쓰인다.

그림 3. 태스크 내부 상태와 부모가 읽는 상태의 관계

종료하는 task_struct

__state — 스케줄러 상태; 끝에서 do_task_dead().

exit_state — EXIT_ZOMBIE / EXIT_DEAD / ptrace EXIT_TRACE.

exit_code, signal — wait가 읽을 종료 코드·thread group 통계.

부모·추적자와 남은 자식

parent / real_parent — 알림·wait 주체.

children — forget_original_parent()에서 새 reaper로 이동.

wait_task_zombie() — exit_state와 종료 정보를 확인.

mm / files / fs 포인터 정리와 task_struct 수명은 별개exit_mm(), exit_files(), exit_fs()가 수행되어도 부모가 읽어야 할 종료 상태와 통계 때문에 task_struct는 조건에 따라 더 남는다.
필드·관계v6.6에서 확인할 의미
__state / exit_state각각 runnability와 종료 수거 상태. 동일한 상태 기계가 아니다.
exit_code / signal->group_exit_codewait는 SIGNAL_GROUP_EXIT 조건에 따라 보고할 코드를 고른다.
parent / real_parentptrace 관계와 자연 부모가 다를 수 있어 통지·수거 분기가 달라진다.
children / signal->live자식 재부모화와 마지막 thread 판정은 서로 다른 관계다.

근거: __state와 exit_state 구분, 재부모화·종료 통지.

실행 단계와 상태 변화

좀비는 exit_notify()에서 일단 설정되지만, 항상 오래 유지되는 상태는 아니다. 자동 수거면 같은 함수 안에서 EXIT_DEAD로 바뀐다. 부모가 상태를 수거하는 경로는 종료 태스크의 do_exit() 호출 스택 밖에 있다.

그림 4. 부모 wait의 WNOWAIT·수거 분기
wait_consider_task() → wait_task_zombie()EXIT_ZOMBIE이며 delay_group_leader()와 ptrace 가시성 조건을 통과한 자식을 처리한다.
WNOWAIT상태·rusage 관찰exit_state를 EXIT_DEAD로 전환하지 않고 반환. 다음 wait에서 다시 볼 수 있다.
수거cmpxchg(EXIT_ZOMBIE → EXIT_DEAD / EXIT_TRACE)한 waiter만 상태를 차지한다. ptrace로 재부모화된 leader면 EXIT_TRACE 경유.
release_task()ptrace 정리, __exit_signal, pid·task 참조 정리. 마지막 non-leader가 죽으면 zombie leader의 추가 release도 가능하다.
단계조건남는 것 / 다음 경로
EXIT_ZOMBIEexit_notify가 먼저 설정, autoreap이 false부모·추적자의 wait 대상.
즉시 EXIT_DEADexit_notify의 autoreap이 truedead 목록에서 release_task 호출.
WNOWAITwait_task_zombie에서 WEXITED와 WNOWAIT정보만 반환, 수거하지 않음.
wait 수거cmpxchg로 EXIT_ZOMBIE 점유ptrace 조건에 따라 EXIT_TRACE를 경유하거나 EXIT_DEAD 뒤 release.

불변 조건과 실패 사례

자원과 상태의 수명

exit_mm()와 exit_files() 뒤에도 exit_state·exit_code가 부모 wait를 위해 남을 수 있다. 좀비 존재만으로 mm 또는 열린 파일이 그대로 남는다는 뜻은 아니다.

통지 순서

exit_notify()는 tasklist_lock 아래 재부모화와 상태 전이를 수행하고, 자동 수거 대상의 release_task()는 잠금을 푼 다음 호출한다.

wait의 단일 수거자

wait_task_zombie()의 cmpxchg가 EXIT_ZOMBIE 전환을 한 waiter에게만 허용한다. WNOWAIT는 이 전환을 하지 않는다.

leader 예외

leader가 먼저 끝났어도 다른 스레드가 남으면 부모 통지가 지연될 수 있다. 마지막 non-leader의 release_task()가 zombie leader를 다시 살펴본다.

주의

tracepoint를 관찰하고 곧바로 파일·fs 정리와 task_struct release까지 끝났다고 해석하지 않는다.

주의

모든 EXIT_ZOMBIE가 장기간 남는다고 단정하지 않는다. SIGCHLD 처리 설정, ptrace, thread group 조건을 함께 본다.

계측과 검증

sched_process_exit 이벤트는 v6.6 do_exit()에서 exit_mm() 뒤, exit_files()·exit_fs() 앞에 발생한다. 따라서 이 이벤트만으로 파일 정리·부모 통지·wait 수거 완료를 주장할 수 없다. sched_process_wait는 do_wait() 진입의 관찰점이지 반드시 수거 성공을 뜻하지 않는다.

관찰점말할 수 있는 것말할 수 없는 것
sched_process_exitexit_mm() 후 exit 경로가 이 지점에 도달.파일·fs 정리, exit_notify, release 완료.
sched_process_waitwait 호출 경로에 진입.WNOWAIT 여부나 실제 수거 성공.
/proc/<pid>/status관찰 순간의 State·PPid 등.짧은 EXIT_ZOMBIE→EXIT_DEAD 전이의 완전한 기록.
읽기 전용 확인:
# tracefs가 이미 제공되는 테스트 환경에서 이벤트 형식만 확인
cat /sys/kernel/tracing/events/sched/sched_process_exit/format
cat /sys/kernel/tracing/events/sched/sched_process_wait/format
# 살아 있는 대상으로만; PID는 실험 대상 값으로 교체
cat /proc/<pid>/status

실제 기록이 필요하면 격리된 실험 환경에서 해당 이벤트만 선택하고, 이벤트의 타임스탬프와 별도로 wait 결과를 비교한다. 전역 tracer·filter를 바꾸는 명령은 이 글의 읽기 절차에 포함하지 않는다.

추가 확인 사항

  • 종료 대상이 thread group leader인가, 다른 thread가 남아 있는가, ptrace 관계가 있는가?
  • 부모의 SIGCHLD 설정이 명시적 SIG_IGN 또는 SA_NOCLDWAIT인가? 그렇다면 do_notify_parent()는 언제 true를 반환하는가?
  • 부모가 WNOWAIT로 관찰했는가, 아니면 wait_task_zombie()가 cmpxchg로 수거권을 가져왔는가?
  • tracepoint 시점이 exit_mm() 뒤이면서 파일·fs 정리 앞이라는 점을 실험 결과 해석에 반영했는가?