Linux

프로세스 생성 분석 fork (thread_info, task_struct 구조체) - 3

Jminu 2025. 11. 20. 02:28

thread_info 구조체와 task_struct 구조체에 대해서 알아보자.

저번에 ftrace로 확인했듯이, fork를 호출했을때 함수 호출 순서는 이렇다.

__arm64_sys_clone() → __do_sys_clone() → kernel_clone() → copy_process()

 

여기서 kernel_clone함수를 살펴보자.

pid_t kernel_clone(struct kernel_clone_args *args)
{
	u64 clone_flags = args->flags;
	struct completion vfork;
	struct pid *pid;
	struct task_struct *p;
	int trace = 0;
	pid_t nr;

	if ((clone_flags & CLONE_PIDFD) &&
	    (clone_flags & CLONE_PARENT_SETTID) &&
	    (args->pidfd == args->parent_tid))
		return -EINVAL;

	if (!(clone_flags & CLONE_UNTRACED)) {
		if (clone_flags & CLONE_VFORK)
			trace = PTRACE_EVENT_VFORK;
		else if (args->exit_signal != SIGCHLD)
			trace = PTRACE_EVENT_CLONE;
		else
			trace = PTRACE_EVENT_FORK;

		if (likely(!ptrace_event_enabled(current, trace)))
			trace = 0;
	}

	p = copy_process(NULL, trace, NUMA_NO_NODE, args);
    ...
}

kernel_clone에서는 task_struct *p를 선언하여, task_struct의 포인터를 생성하고,

copy_process를 통해 task_struct 포인터를 받는다.

 

그리고 copy_process()는

__latent_entropy struct task_struct *copy_process(
					struct pid *pid,
					int trace,
					int node,
					struct kernel_clone_args *args)
{
	int pidfd = -1, retval;
	struct task_struct *p;
	struct multiprocess_signals delayed;
	struct file *pidfile = NULL;
	const u64 clone_flags = args->flags;
	struct nsproxy *nsp = current->nsproxy;

	...
	p = dup_task_struct(current, node);
	...
    
    return p;
}

dup_task_struct를 호출하고 인자로 current를 넣는다.

current는 매크로 함수로

static __always_inline struct task_struct *get_current(void)
{
	unsigned long sp_el0;

	asm ("mrs %0, sp_el0" : "=r" (sp_el0));

	return (struct task_struct *)sp_el0;
}

#define current get_current()

이렇게 선언되어 있다, 즉, current는 get_current함수로 현재 task_struct구조체를 반환한다.

 

자 그러면 대충 감이 온다.

결국 kernel_clone -> copy_process -> dup_task_struct를 통해서 커널이 하는 행동은

현재 태스크(프로세스)의 task_struct구조체를 복사하는구나...!!

 

그러면 task_struct 구조체의 정의를 살펴보자. (리눅스 6.12버전 기준)

struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
	struct thread_info		thread_info;
#endif
	unsigned int			__state;
	unsigned int			saved_state;
	randomized_struct_fields_start
	void				*stack;
    ...
 	...
}

이런 필드들을 가지는 구조체이다.

여기에는 thread_info, __state같은 필드들이 있다.

말고도 pid같은 필드들도 있으니 살펴보길 바란다.

 

/* -1 unrunnable, 0 runnable, >0 stopped */

 

리눅스 4.1버전에서는 __state필드가 아니라, state필드로 되어있는데

-1이면 실행 불가능, 0이면 실행가능 0보다 크면 스탑상태로 생각한다.

 

task_struct는 현재 실행중인 태스크(프로세스)의 정보를 나타낸다.

그러면 task_struct의 필드인 thread_info를 확인해보자.

struct thread_info {
	unsigned long		flags;		/* low level flags */
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
	u64			ttbr0;		/* saved TTBR0_EL1 */
#endif
	union {
		u64		preempt_count;	/* 0 => preemptible, <0 => bug */
		struct {
#ifdef CONFIG_CPU_BIG_ENDIAN
			u32	need_resched;
			u32	count;
#else
			u32	count;
			u32	need_resched;
#endif
		} preempt;
	};
#ifdef CONFIG_SHADOW_CALL_STACK
	void			*scs_base;
	void			*scs_sp;
#endif
	u32			cpu;
};

flag상태 플래그, preemption count선점 카운트, cpu 현재 스레드가 실행중인 cpu id 등을 가진다.

 

 

이 그림에서는 thread_info안에 task_struct가 있지만,

커널 버전이 업데이트 되면서 task_struct안에 thread_info가 들어가는 구조로 바뀌었다.

thread_info구조체는 커널 스택의 최하단에 위치한다.

 

다시 돌아와서 이번에는 copy_process에서 호출한 dup_task_struct를 봐보자.

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;
	...
}

 

dup_task_struct는 arch_dup_task_struct로 부모 태스크의 내용을 복사,

alloc_thread_stack_node로 커널스택을 할당한다.

 

즉, 전체 내용을 요약하면,

copy_process가 dup_task_struct를 호출하고, 자식 태스크에 커널 스택을 할당하고, 부모 태스크의 task_struct를 복사한다.