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

copy_process

task 준비·자원 공유/복제·PID 공개와 별도의 실행 시작을 소스 경계로 나눠 읽는다.

전체 흐름

copy_process()는 새 task를 준비하고 부모·PID 관계에 연결해 아직 실행되지 않은 task_struct *를 반환한다. 일반적인 kernel_clone() 호출자는 그 반환값을 받은 뒤 별도로 wake_up_new_task()를 호출한다. 두 함수의 경계를 합치면 공개 시점과 실행 시작을 혼동한다 (kernel/fork.c:2735, kernel/fork.c:2940).

그림 1. 생성·공개·실행 시작의 함수 경계
copy_process 내부의 할당·자원 복제·PID 공개, 반환 후 kernel_clone의 별도 wakeup과 오류 정리 경로

실선은 정상 경로, 주황색은 공개 전 실패 정리다. 그림 크게 열기.

create_io_thread()처럼 copy_process()를 직접 호출하는 경로도 있다. 이 함수는 비활성 task를 반환하며 호출자가 깨워야 한다 (kernel/fork.c:2838). 따라서 wakeup은 copy_process()의 일반적 내부 후속 단계가 아니다.

소스 코드 위치

아래 행 번호는 이 글의 Linux v6.6 고정 소스에 맞춘 것이다. 다른 버전의 유사 코드와 섞지 않는다.

관찰 지점고정 소스질문
생성·자원·오류kernel/fork.c:2327어느 자원을 확보한 뒤 어떤 레이블로 빠지는가?
부모·PID 공개kernel/fork.c:2626잠금 아래에서 목록과 PID가 언제 연결되는가?
호출자 wakeupkernel/fork.c:2909비활성 task를 받은 뒤 누가 깨우는가?
runqueue 반영kernel/sched/core.c:4848TASK_RUNNING, enqueue, 선점 판단은 어디서 일어나는가?

원본 트리: kernel/fork.c, kernel/sched/core.c.

대표 코드

모든 발췌는 해당 고정 버전의 실제 연속 행이다. 이전의 “wake_up_new_task”라는 제목 아래 create_io_thread()와 kernel_clone() 시작 부분을 묶은 발췌는 실제 호출 지점과 스케줄러 구현으로 교체했다.

1. task와 stack 확보 — clone_flags 인자 없음

kernel/fork.c:1103–1120 · 고정 소스 그대로 발췌

static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
{
	struct task_struct *tsk;
	int err;

	if (node == NUMA_NO_NODE)
		node = tsk_fork_get_node(orig);
	tsk = alloc_task_struct_node(node);
	if (!tsk)
		return NULL;

	err = arch_dup_task_struct(tsk, orig);
	if (err)
		goto free_tsk;

	err = alloc_thread_stack_node(tsk, node);
	if (err)
		goto free_tsk;

2. copy_* 호출과 단계별 실패 이동

kernel/fork.c:2489–2512 · 고정 소스 그대로 발췌

	retval = copy_files(clone_flags, p, args->no_files);
	if (retval)
		goto bad_fork_cleanup_semundo;
	retval = copy_fs(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_files;
	retval = copy_sighand(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_fs;
	retval = copy_signal(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_sighand;
	retval = copy_mm(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_signal;
	retval = copy_namespaces(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_mm;
	retval = copy_io(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_namespaces;
	retval = copy_thread(p, args);
	if (retval)
		goto bad_fork_cleanup_io;

3. 부모 목록·PID 공개

kernel/fork.c:2695–2713 · 고정 소스 그대로 발췌

			list_add_tail(&p->sibling, &p->real_parent->children);
			list_add_tail_rcu(&p->tasks, &init_task.tasks);
			attach_pid(p, PIDTYPE_TGID);
			attach_pid(p, PIDTYPE_PGID);
			attach_pid(p, PIDTYPE_SID);
			__this_cpu_inc(process_counts);
		} else {
			current->signal->nr_threads++;
			current->signal->quick_threads++;
			atomic_inc(&current->signal->live);
			refcount_inc(&current->signal->sigcnt);
			task_join_group_stop(p);
			list_add_tail_rcu(&p->thread_group,
					  &p->group_leader->thread_group);
			list_add_tail_rcu(&p->thread_node,
					  &p->signal->thread_head);
		}
		attach_pid(p, PIDTYPE_PID);
		nr_threads++;

4. kernel_clone의 별도 wakeup

kernel/fork.c:2909–2940 · 고정 소스 그대로 발췌

	p = copy_process(NULL, trace, NUMA_NO_NODE, args);
	add_latent_entropy();

	if (IS_ERR(p))
		return PTR_ERR(p);

	/*
	 * Do this prior waking up the new thread - the thread pointer
	 * might get invalid after that point, if the thread exits quickly.
	 */
	trace_sched_process_fork(current, p);

	pid = get_task_pid(p, PIDTYPE_PID);
	nr = pid_vnr(pid);

	if (clone_flags & CLONE_PARENT_SETTID)
		put_user(nr, args->parent_tid);

	if (clone_flags & CLONE_VFORK) {
		p->vfork_done = &vfork;
		init_completion(&vfork);
		get_task_struct(p);
	}

	if (IS_ENABLED(CONFIG_LRU_GEN) && !(clone_flags & CLONE_VM)) {
		/* lock the task to synchronize with memcg migration */
		task_lock(p);
		lru_gen_add_mm(p->mm);
		task_unlock(p);
	}

	wake_up_new_task(p);

5. scheduler의 새 task 활성화

kernel/sched/core.c:4848–4887 · 고정 소스 그대로 발췌

void wake_up_new_task(struct task_struct *p)
{
	struct rq_flags rf;
	struct rq *rq;

	raw_spin_lock_irqsave(&p->pi_lock, rf.flags);
	WRITE_ONCE(p->__state, TASK_RUNNING);
#ifdef CONFIG_SMP
	/*
	 * Fork balancing, do it here and not earlier because:
	 *  - cpus_ptr can change in the fork path
	 *  - any previously selected CPU might disappear through hotplug
	 *
	 * Use __set_task_cpu() to avoid calling sched_class::migrate_task_rq,
	 * as we're not fully set-up yet.
	 */
	p->recent_used_cpu = task_cpu(p);
	rseq_migrate(p);
	__set_task_cpu(p, select_task_rq(p, task_cpu(p), WF_FORK));
#endif
	rq = __task_rq_lock(p, &rf);
	update_rq_clock(rq);
	post_init_entity_util_avg(p);

	activate_task(rq, p, ENQUEUE_NOCLOCK);
	trace_sched_wakeup_new(p);
	check_preempt_curr(rq, p, WF_FORK);
#ifdef CONFIG_SMP
	if (p->sched_class->task_woken) {
		/*
		 * Nothing relies on rq->lock after this, so it's fine to
		 * drop it.
		 */
		rq_unpin_lock(rq, &rf);
		p->sched_class->task_woken(rq, p);
		rq_repin_lock(rq, &rf);
	}
#endif
	task_rq_unlock(rq, p, &rf);
}

함수별 분석

dup_task_struct(orig,node)에는 clone_flags 매개변수가 없다. flags 정책은 copy_process()의 검증과 각 copy_*() helper에서 처리한다 (kernel/fork.c:2327, kernel/fork.c:2489).

그림 2. allocation과 flag별 자원 분기의 실제 위치
copy_process(args)clone_flags = args→flags
→
dup_task_struct(current,node)task·stack 할당; flags는 받지 않음
→
copy_files / copy_mm …각 helper가 flags에 따라 선택
CLONE_FILES / CLONE_VM 설정참조 증가·기존 객체 공유
↔ 조건 분기 ↔
해당 flag 해제dup_fd / dup_mm로 별도 객체 준비
두 갈래는 각 helper가 다시 합쳐 다음 copy_* 호출로 진행한다.

CLONE_VM/CLONE_FILES는 독립적인 예시다. clone 조합 자체의 허용 여부는 앞선 검증 조건을 따른다.

호출조건·동작이후 또는 실패
dup_task_structcurrent와 node를 받아 task·stack 확보 (kernel/fork.c:1103)이 단계는 CLONE_* 공유 정책을 직접 선택하지 않음
copy_filesCLONE_FILES면 count 증가·공유, 아니면 dup_fd (kernel/fork.c:1766)dup_fd(oldf, NR_OPEN_MAX, &error) 실패를 error 값으로 반환한다.
copy_mmCLONE_VM면 mmget·공유, 아니면 dup_mm (kernel/fork.c:1708)current→mm이 없는 kernel thread는 별도 처리
copy_* 연속files → fs → sighand → signal → mm → namespaces → io → thread (kernel/fork.c:2489)각 실패 지점이 이미 얻은 자원에 맞는 label로 이동

객체와 연결 관계

그림 3. task 본체·하위 자원·공개 관계
currentorig task / mm / files / signal
→
p = task_struct새 task 본체 + stack
→
하위 객체flags에 따라 공유 또는 별도 복제
공개 경계: alloc_pid()만으로 사용자에게 실행 가능한 task가 되는 것은 아니다. tasklist_lock 아래 부모 관계·task 목록·PID 연결 뒤 copy_process()가 비활성 task를 반환한다.

복제한 task 본체와 공유 가능한 하위 객체의 소유권을 따로 추적한다.

객체 / 연결이 버전의 관찰 지점
task_struct pdup_task_struct()가 새 task와 stack을 준비 (kernel/fork.c:2327)
mm / filesflags에 따라 공유 참조 또는 복제. dup_fd(oldf, NR_OPEN_MAX, &error) 실패를 error 값으로 반환한다.
struct pidalloc_pid() 후 attach_pid(); 할당과 공개를 구분 (kernel/fork.c:2517, kernel/fork.c:2695)
real_parent / task 목록CLONE_PARENT|CLONE_THREAD면 current→real_parent, 아니면 current (kernel/fork.c:2626). 스레드 분기에는 thread_group과 thread_node를 연결한다.

상태 전이와 오류 되감기

그림 4. 공개와 wakeup 사이의 별도 단계
① 준비task·stack / copy_* / PID 확보
→
② 공개tasklist_lock 아래 관계·PID 연결
→
③ 반환copy_process → 비활성 p
→
④ 실행kernel_clone → wake_up_new_task
준비·공개 직전 실패 ↘ 해당 bad_fork_* label → 확보 자원 해제 → ERR_PTR. 마지막 실패 검사 이후에는 공개 연결을 거쳐 반환한다.

단계 번호는 소스 순서다. ④는 ①–③과 달리 kernel_clone() 호출자 쪽이다.

경계 / 실패소스상 의미검증 포인트
자원 준비copy_* 중 실패하면 해당 정리 label로 이동 (kernel/fork.c:2489)아직 얻지 않은 자원까지 해제하지 않음
cgroup·추가 준비sched_cgroup_fork()은 이 버전에서 void; 실패 분기 없음 (kernel/fork.c:2596). 여기에는 futex hash 추가 할당 실패 분기가 없다.버전별 추가 실패 지점을 구별
마지막 실패 검사“No more failure paths” 주석 이후 목록·PID 연결 (kernel/fork.c:2664)공개 후 bad_fork unwind로 가는 설명을 피함
오류 레이블bad_fork_cancel_cgroup → PID/pidfd 정리 → 확보한 자원 역순 정리. sched_cancel_fork() 레이블은 없다. (kernel/fork.c:2737)진입 레이블에 따라 통과하는 정리 구간이 다름
별도 wakeupselect_task_rq() → activate_task(..., ENQUEUE_NOCLOCK) → check_preempt_curr(..., WF_FORK) (kernel/sched/core.c:4848)copy_process 내부 단계로 그리지 않음

검토할 불변 조건

  • flags 검증: CLONE_THREAD, CLONE_SIGHAND, CLONE_VM의 조합 제약을 자원 helper 진입 전에 확인한다 (kernel/fork.c:2240).
  • 공개 전 준비: cgroup 및 scheduler 준비가 끝난 뒤 tasklist_lock 아래 연결한다 (kernel/fork.c:2596, kernel/fork.c:2626).
  • 오류 정리: 각 goto bad_fork_*는 그 시점까지 성공한 작업에 맞는 레이블로 들어간다. 하나의 공통 “rollback” 함수로 단순화하지 않는다 (kernel/fork.c:2737).
  • 호출자 경계: 성공한 copy_process()는 task를 깨우지 않는다. 일반 kernel_clone()과 create_io_thread()의 호출자 책임을 구별한다 (kernel/fork.c:2909).

계측과 검증

tracepoint는 소스에서 호출된 위치를 기준으로 해석한다. event가 보였다고 앞선 모든 사용자 관찰이나 자식의 실제 실행이 끝났다는 뜻은 아니다.

관찰 이벤트이 버전의 호출 위치해석 한계
task_newtaskcopy_process() 반환 전 (kernel/fork.c:2729)새 task 생성 기록; 자식 실행 시작 아님
sched_process_forkkernel_clone()에서 성공 반환 후, wake 전 (kernel/fork.c:2919)copy_process의 중간 실패 위치를 이 이벤트만으로 구별 못함
sched_wakeup_newwake_up_new_task()의 enqueue 뒤 (kernel/sched/core.c:4873)실제 CPU에서 자식 명령 실행 완료를 보장하지 않음
먼저 사용 가능한 이벤트 확인:
grep -E 'task_newtask|sched_process_fork|sched_wakeup_new' /sys/kernel/tracing/available_events

위 확인은 읽기 전용이다. 이벤트가 없는 커널 구성에서는 그 이벤트로 단계가 관찰된다고 가정하지 않는다.

추가 확인 사항

  • CLONE_FILES나 CLONE_VM의 공유 참조 증가가 실패 unwind의 어느 해제 함수와 짝을 이루는가?
  • CLONE_PARENT|CLONE_THREAD 여부에 따라 real_parent와 thread 목록 연결이 어떻게 달라지는가?
  • 실패가 kernel/fork.c:2596 전후 어느 label에 도달하는가? 성공 시 kernel/fork.c:2940 호출 전에 무엇이 관찰 가능한가?
  • sched_cgroup_fork()은 이 버전에서 void; 실패 분기 없음. 이 버전의 scheduler 상태를 kernel/sched/core.c:4848의 enqueue와 구분할 수 있는가?