Process / Syscall · Linux 6.18.37 LTS · source + diagram note

exit

Linux v6.18.37 kernel/exit.c에서 do_exit()의 자원 정리 순서와 exit_notify()의 조건부 zombie·autoreap 경로를 분리해 읽습니다.

종료·통지·수거는 다른 시점

do_exit()는 현재 task의 종료 작업을 진행하고 exit_notify()를 호출한다. exit_notify() 안에서 잠시 EXIT_ZOMBIE를 설정하지만, 조건에 따라 바로 EXIT_DEAD로 바꿔 release_task()에 넘기거나, zombie를 남겨 기다리는 쪽에서 수거하게 한다. 따라서 “종료 뒤 항상 zombie → wait → release” 한 줄은 맞지 않는다.

그림 1. do_exit에서 통지까지, 표시된 호출 순서
① 종료 tracetrace_sched_process_exit() 호출. 아직 뒤의 mm/files/fs 정리 전이다.L946–949
② 자원 정리exit_mm(), exit_files(), exit_fs() 등이 이어진다.L958–973
③ exit_notify부모 관계 재배치와 조건부 통지/자동 수거를 처리한다.L983–984 · L738–781
이 흐름은 do_exit() L946–984의 부분 발췌다. release_task()는 모든 종료 task에 대해 이 화살표 직후 무조건 호출되는 단계가 아니다.

원문 범위와 확인 한계

첫 블록은 do_exit() 전체가 아닌 kernel/exit.c L946–984의 연속 부분 발췌다. 함수 진입과 PF_EXITING, group_dead 설정은 L898–924, exit_notify() 뒤의 마무리와 do_task_dead()는 L985–1016에 있다. 둘째 블록은 exit_notify() 함수 전체 L738–781다.

여기서 EXIT_ZOMBIE는 특정 분기에서 남는 상태이며 자원이 모두 유지된다는 뜻이 아니다. release_task()의 내부와 마지막 task 참조 해제 시점, 특정 부모의 wait 동작은 별도 함수에서 확인해야 한다.

도식 참고와 버전 경계: 문C 블로그 Scheduler -1- (Basic)의 태스크 상태 절과 해당 상태 그림에서 종료 흐름과 부모 wait를 다른 구간으로 읽는 표현을 참고했다. 이 절에는 고정 커널 버전 표기가 없고 글은 2017년에 게시됐으므로 v6.18.37 동작의 근거로 쓰지 않는다. 아래 그림은 원도를 복제하지 않고 do_exit()·exit_notify()와 이후 wait_task_zombie()를 별도 단계로 그렸다. 스케줄러의 task->__state와 exit_state를 하나의 상태축으로 합치지 않는다.

같은 태그의 원본 파일: kernel/exit.c · kernel/signal.c · kernel/fork.c. 이 페이지의 코드 두 블록과 주요 도해 근거는 kernel/exit.c이고, 뒤의 두 파일은 추가 읽기 위치다.

두 연속 코드 발췌

1. do_exit 후반: trace에서 exit_notify까지

kernel/exit.c:946–984 · 함수 일부만 표시하며, 종료의 시작과 최종 task 해제까지 전부 보여 주지는 않는다.

	tsk->exit_code = code;
	taskstats_exit(tsk, group_dead);
	unwind_deferred_task_exit(tsk);
	trace_sched_process_exit(tsk, group_dead);

	/*
	 * Since sampling can touch ->mm, make sure to stop everything before we
	 * tear it down.
	 *
	 * Also flushes inherited counters to the parent - before the parent
	 * gets woken up by child-exit notifications.
	 */
	perf_event_exit_task(tsk);

	exit_mm();

	if (group_dead)
		acct_process();

	exit_sem(tsk);
	exit_shm(tsk);
	exit_files(tsk);
	exit_fs(tsk);
	if (group_dead)
		disassociate_ctty(1);
	exit_task_namespaces(tsk);
	exit_task_work(tsk);
	exit_thread(tsk);

	sched_autogroup_exit_task(tsk);
	cgroup_exit(tsk);

	/*
	 * FIXME: do that only when needed, using sched_exit tracepoint
	 */
	flush_ptrace_hw_breakpoint(tsk);

	exit_tasks_rcu_start();
	exit_notify(tsk, group_dead);

trace_sched_process_exit()는 exit_mm()보다 앞이다. 이 trace를 보았다는 사실만으로 mm/files/fs 정리 완료나 부모 통지가 끝났다고 판단하지 않는다.

2. exit_notify 전체: zombie와 autoreap

kernel/exit.c:738–781 · reparent, 조건별 통지와 release_task() 호출까지다.

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;
		/* untraced sub-thread */
		do_notify_pidfd(tsk);
	}

	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);

	list_for_each_entry_safe(p, n, &dead, ptrace_entry) {
		list_del_init(&p->ptrace_entry);
		release_task(p);
	}
}

EXIT_ZOMBIE를 설정한 뒤 ptrace·thread group leader·non-leader 경로에 따라 autoreap이 달라진다. autoreap일 때만 이 함수가 EXIT_DEAD로 바꾸고 dead 목록에서 release_task()를 호출한다.

exit_notify의 세 조건을 펼쳐 보기

함수 시작에서 forget_original_parent()로 자식의 부모 관계를 처리한 뒤(L744–745), EXIT_ZOMBIE를 설정한다. 이후 부모 알림과 자동 수거 판단은 ptrace 및 thread group 상태에 따라 달라진다.

그림 2. exit_notify가 EXIT_ZOMBIE 뒤에 선택하는 경로
ptrace 있음조건에 따라 exit_signal 또는 SIGCHLD로 do_notify_parent()를 호출해 autoreap을 받는다.L752–757
ptrace 없는 리더thread group이 비었을 때 do_notify_parent()를 호출할 수 있다.L758–760
ptrace 없는 비리더autoreap = true; do_notify_pidfd()를 호출한다.L761–765
autoreap 참EXIT_DEAD로 바꾸고 dead 목록을 나중에 순회하며 release_task()를 호출한다.L767–779
autoreap 거짓이 함수에서는 zombie 상태로 남는다. 이후 wait/ptrace 경로가 별도로 처리한다.L750–770 · wait_task_zombie L1170–1197
release_task() 호출은 제거·정리 단계지만 “이 시점에 마지막 task 참조가 즉시 free된다”는 뜻은 아니다. 구현은 L244–313을 따로 본다.
조건통지·판단이 함수의 다음 결과원문
ptrace신호 선택 후 do_notify_parent() 결과를 autoreap으로 사용반환값에 따라 DEAD 또는 zombie 유지L752–770
비추적 리더thread group이 비었을 때 부모 통지 호출조건부 autoreapL758–770
비추적 비리더do_notify_pidfd(); autoreap = trueDEAD로 전이 후 release 목록에 추가L761–770

남은 zombie를 wait가 읽거나 수거할 때

wait_task_zombie()는 WEXITED가 없으면 건너뛰고, WNOWAIT이면 상태를 수거하지 않고 정보만 읽는 경로를 택한다(L1170–1190). 일반 경로는 EXIT_ZOMBIE에서 EXIT_DEAD 또는 ptrace용 EXIT_TRACE로 원자 전이하며(L1191–1201), 뒤쪽에서 state == EXIT_DEAD일 때만 release_task()를 호출한다(L1264–1277).

그림 3. 기다리는 쪽의 WNOWAIT와 수거 경계
WNOWAIT종료 정보를 읽고 상태를 소모하지 않는다. 같은 zombie를 나중에 수거할 여지가 남는다.L1180–1190
일반 waitcmpxchg로 zombie를 DEAD 또는 TRACE로 바꿀 수 있는 쪽이 소유한다.L1191–1201
ptrace 재통지TRACE면 실제 부모에게 다시 알리고 zombie 또는 DEAD로 조정할 수 있다.L1264–1275
DEAD일 때만 releaserelease_task(p) 호출. 이 함수 내부도 별도 단계다.L1276–1277 · L244–313
종료 task가 zombie로 남는 경우와 exit_notify에서 autoreap되는 경우는 서로 다른 분기다. parent/wait 경로를 모든 task의 필수 단일 다음 단계로 그리지 않는다.
조건표시된 상태 처리release_task근거
WNOWAIT상태를 전이하지 않고 정보 반환이 경로에는 없음L1180–1190
일반 waitzombie→DEAD 또는 TRACE 원자 전이 시도DEAD면 뒤에서 호출L1191–1201, L1264–1277
ptrace TRACE재통지 후 zombie 또는 DEAD로 다시 설정DEAD로 끝날 때만 호출L1264–1277

관찰 위치가 뜻하는 것

trace_sched_process_exit()는 L946–960에서 exit_mm() 전에 호출된다. 종료 trace가 발생해도 자원 정리나 exit_notify()가 끝났다는 뜻은 아니다. 사후 /proc/<pid>/status의 상태 표시 역시 특정 내부 호출이 언제 완료됐는지 직접 말해 주지 않는다.

실행 환경에서 종료 원인을 분리하려면 do_exit(), exit_notify(), wait_task_zombie() 중 어느 경계를 보고 있는지 먼저 정한다. 이 글은 검증되지 않은 ftrace event 이름이나 일괄 필터 명령을 제시하지 않는다.

이어서 읽을 위치

release_task() 안에서는 process/thread/PID 목록에서 제거하고 put_task_struct_rcu_user()를 호출한다(L244–313). 실제 마지막 참조와 최종 메모리 해제까지 단정하려면 RCU와 task 참조 수명 경로를 더 읽어야 한다. 개념 설명·함수 원문·그림·GIF은 종료 경로의 넓은 맥락을 보는 별도 자료다.