프로세스 생성 분석 fork (thread_info, task_struct 구조체) - 3
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를 복사한다.