이전 시간까지 유저 프로세스와 커널 프로세스가 어떤 흐름으로 생성되는지 살펴봤습니다.
둘 다 _do_fork() 함수를 호출한다는 사실을 알 수 있습니다.

프로세스 생성 시 공통으로 실행하는 _do_fork() 함수 코드를 분석하면서 커널이 어떻게 프로세스를 생성하는지 살펴봅니다.

4.6.1 _do_fork() 함수 분석
_do_fork() 함수을 분석하기 앞서 이 함수 동작을 분류해봅시다.

1단계: 프로세스 생성
copy_process() 함수를 호출해서 프로세스를 생성합니다. 프로세스를 생성하는 세부 동작을 파악하려면 copy_process() 함수를 분석할 필요가 있습니다.

2단계: 생성한 프로세스 실행 요청
copy_process() 함수를 호출해서 프로세스 생성을 마쳤으면 wake_up_new_task() 함수를 호출해서 프로세스를 바로 깨웁니다. 프로세스를 깨운다는 의미는 스케줄러에게 프로세스 실행 요청을 하는 것입니다.

_do_fork() 함수 전체 흐름에 대해 알아봤으니 이번에는 _do_fork() 함수에 전달되는 인자와 반환값을 살펴봅시다.
long _do_fork(unsigned long clone_flags,
      unsigned long stack_start,
      unsigned long stack_size,
      int __user *parent_tidptr,
      int __user *child_tidptr,
      unsigned long tls)

_do_fork() 함수는 프로세스를 생성한 다음 생성한 프로세스의 PID를 반환합니다.

_do_fork() 함수 인자와 반환값을 알아봤으니 이제 소스 리뷰를 할 차례입니다.
1  long _do_fork(unsigned long clone_flags,
2       unsigned long stack_start,
3       unsigned long stack_size,
4       int __user *parent_tidptr,
5       int __user *child_tidptr,
6       unsigned long tls)
7 {
8 struct task_struct *p;
9 int trace = 0;
10 long nr;
...
11
12 p = copy_process(clone_flags, stack_start, stack_size,
13  child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
14 add_latent_entropy();
...
15
16 if (!IS_ERR(p)) {
17 struct completion vfork;
18 struct pid *pid;
19
20 trace_sched_process_fork(current, p);
21
22 pid = get_task_pid(p, PIDTYPE_PID);
23 nr = pid_vnr(pid);
24
...
25 wake_up_new_task(p);
...
26 }
27 return nr;
28}

12번째 줄 코드를 봅시다.
12 p = copy_process(clone_flags, stack_start, stack_size,
13  child_tidptr, NULL, trace, tls, NUMA_NO_NODE);

copy_process() 란 함수를 호출해서 부모 프로세스의 메모리 및 시스템 정보를 생성하려는 자식 프로세스에게 복사합니다. _do_fork() 함수에 전달된 clone_flags와 stack_stack 그리고 stack_size 인자를 copy_process() 함수에 전달합니다.

다음 16번째 줄 조건문 코드를 보겠습니다.
16 if (!IS_ERR(p)) {

copy_process() 함수를 실행하면 이 함수는 프로세스를 식별하는 태스크 디스크립터를 p이란 포인터 변수를 반환합니다. 이 태스크 디스크립터에 오류가 있는지 점검하는 코드가 16번째 줄 코드입니다. 17~25번째 줄 코드 구간은 태스크 디스크립터에 오류가 없으면 실행합니다.

다음 20~22번째 줄 코드를 봅시다.
20 trace_sched_process_fork(current, p);
21
22 pid = get_task_pid(p, PIDTYPE_PID);
23 nr = pid_vnr(pid);

20번째 줄 코드는 sched_process_fork 이란 ftrace 이벤트를 켰을 때 실행하며 다음과 같은 ftrace 로그를 확인할 수 있습니다.
kthreadd-2 [003] ....  3495.071290: copy_process0x14/0x17d8 <-_do_fork+0xb0/0x3ec
kthreadd-2 [003] ....  3495.071304: <stack trace>
 => kthreadd+0x1dc/0x268
 => ret_from_fork+0x14/0x28
kthreadd-2     [003] ....  3495.071381: sched_process_fork: comm=kthreadd pid=2 child_comm=kthreadd child_pid=17193
kworker/u8:0-17193 [002] d...  3495.071431: sched_switch: prev_comm=kthreadd prev_pid=17193 prev_prio=120 prev_state=D ==> next_comm=swapper/2 next_pid=0

다음은 22~23번째 줄 코드입니다.
22 pid = get_task_pid(p, PIDTYPE_PID);
23 nr = pid_vnr(pid);

pid를 계산해서 nr이란 지역 변수에 저장합니다.

_do_fork() 함수는 copy_process() 함수를 호출해서 프로세스를 생성하고 프로세스 PID를 커널로부터 할당 받아 반환합니다. 프로세스 생성 핵심 함수는 copy_process() 인데 다음에 분석이 이어집니다.

4.6.2 copy_process() 함수 분석
프로세스를 생성하는 핵심 동작은 copy_process() 함수에서 수행합니다. 대부분 부모 프로세스에 있는 리소스를 복사하는 흐름입니다.

분석할 copy_process() 함수 코드는 다음과 같습니다.
1 static __latent_entropy struct task_struct *copy_process(
2 unsigned long clone_flags,
3 unsigned long stack_start,
4 unsigned long stack_size,
5 int __user *child_tidptr,
6 struct pid *pid,
7 int trace,
8 unsigned long tls,
9 int node)
10 {
11 int retval;
12 struct task_struct *p;
...
13 retval = -ENOMEM;
14 p = dup_task_struct(current, node);
15 if (!p)
16 goto fork_out;
...
17 /* Perform scheduler related setup. Assign this task to a CPU. */
18 retval = sched_fork(clone_flags, p);
19 if (retval)
20 goto bad_fork_cleanup_policy;
21
...
22 retval = copy_files(clone_flags, p);
23 if (retval)
24 goto bad_fork_cleanup_semundo;
25 retval = copy_fs(clone_flags, p);
26 if (retval)
27 goto bad_fork_cleanup_files;
28 retval = copy_sighand(clone_flags, p);
29 if (retval)
20 goto bad_fork_cleanup_fs;

먼저 14번째 줄 코드를 봅시다.
14 p = dup_task_struct(current, node);
15 if (!p)
16 goto fork_out;

생성한 프로세스의 태스크 디스크립터인 struct task_struct 구조체와 커널 프로세스 스택 공간을 할당합니다. 

18번째 줄 코드입니다.
18 retval = sched_fork(clone_flags, p);
19 if (retval)
20 goto bad_fork_cleanup_policy;

태스크 디스크립터 struct task_struct 구조체에서 스케줄링 관련 초기화를 진행합니다.

다음 22~24번째 줄 코드를 분석하겠습니다.
22 retval = copy_files(clone_flags, p);
23 if (retval)
24 goto bad_fork_cleanup_semundo;

프로세스의 파일 디스크립터 관련 내용(파일 디스크립터, 파일 디스크립터 테이블)을 초기화하는 동작입니다. 부모 struct file_struct 구조체 내용을 자식 프로세스에게 복사합니다.

만약 프로세스 생성 플래그 중 CLONE_FILES 로 프로세스를 생성했을 경우 참조 카운트만 증가합니다.

다음 25번째 줄 코드를 확인하겠습니다.
25 retval = copy_fs(clone_flags, p);
26 if (retval)
27 goto bad_fork_cleanup_files;

프로세스의 파일 디스크립터 관련 내용(파일 디스크립터, 파일 디스크립터 테이블)을 초기화하는 동작입니다. 부모 struct file_struct 구조체 내용을 자식 프로세스에게 복사합니다.

다음 분석할 코드는 29번째 줄입니다.
28 retval = copy_sighand(clone_flags, p);
29 if (retval)
20 goto bad_fork_cleanup_fs;

프로세스가 등록한 시그널 핸들러 정보인 struct sighand_struct 구조체를 생성해서 복사합니다.

이번 절 까지 프로세스를 생성하는 함수를 살펴 봤습니다. 다음 절에서는 프로세스를 종료할 때 실행하는 do_exit() 함수를 분석하겠습니다.


+ Recent posts