Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

125187
803
94454


4.6.1 _do_fork() 함수/ 4.6.2 copy_process() 함수 4장. 프로세스 관리

이전 시간까지 유저 프로세스와 커널 프로세스가 어떤 흐름으로 생성되는지 살펴봤습니다.
둘 다 _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() 함수를 분석하겠습니다.

"이 포스팅이 유익하다고 생각되시면 댓글로 응원해주시면 감사하겠습니다.  
혹시 글을 읽고 궁금점이 있으면 댓글로 질문 남겨주세요. 상세한 답글 올려 드리겠습니다!"



Reference(프로세스 관리)
4.9 프로세스 컨택스트 정보는 어떻게 저장할까?
 4.9.1 컨택스트 소개
 4.9.2 인터럽트 컨택스트 정보 확인하기
 4.9.3 Soft IRQ 컨택스트 정보 확인하기
 4.9.4 선점 스케줄링 여부 정보 저장
4.10 프로세스 디스크립터 접근 매크로 함수
 4.10.1 current_thread_info()
 4.10.2 current 매크로란
4.11 프로세스 디버깅
 4.11.1 glibc fork 함수 gdb 디버깅
 4.11.2 유저 프로그램 실행 추적 


#Reference 시스템 콜

Reference(워크큐)
워크큐(Workqueue) Overview


    핑백

    덧글

    댓글 입력 영역