Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

19113
1478
166889


[리눅스커널] 프로세스: do_fork() 함수 소개 4. 프로세스(Process) 관리

_do_fork() 함수 소개

리눅스에서 구동 중인 모든 프로세스는 _do_fork() 함수가 실행할 때 생성됩니다. 프로세스는 누가 생성할까요? 리눅스에서 프로세스 생성을 전담하는 프로세스가 있습니다. 주인공은 init과 kthreadd 프로세스입니다. 특히 init 프로세스는 부팅 과정에서 유저 프로세스를 생성하는 역할을 담당합니다.

유저 레벨 프로세스는 init 프로세스, 커널 레벨 프로세스(커널 스레드)는 kthreadd 프로세스가 생성합니다. 그런데 프로세스는 생성이 아니라 복제된다고 말할 수 있습니다. 그럼 프로세스를 생성할 때 부모 프로세스를 복제하는 이유는 무엇일까요? 

프로세스를 생성할 때 프로세스에게 필요한 리소스를 각각 할당받으면 시간이 오래 걸립니다. 그래서 이미 생성된 프로세스에게서 리소스를 물려받는 것입니다. 

리눅스 커널에서는 '코드의 성능이나 속도를 개선'하기 위한 많은 기법이 활용됩니다. 그중 대표적인 방법을 한 가지 소개하면 '이미 만들어놓은 리소스'를 가져다 쓰는 것입니다.

리눅스 커널에서는 속도 개선을 위해 반복 실행되는 코드를 줄이려는 노력을 기울인 흔적을 볼 수 있습니다. 커널 메모리 할당자인 슬럽 메모리 할당자(Slub Memory Allocator)도 이와 유사한 기법을 활용합니다. 드라이버에서 메모리 할당을 요청할 때 쓰는 구조체를 정의해서 해당 구조체에 대한 메모리를 미리 확보해 놓습니다. 메모리 할당을 요청하면 이미 확보한 메모리 주소를 알려줍니다. 이 과정을 통해 메모리를 할당하는 속도를 개선할 수 있습니다. 

프로세스 생성 과정도 마찬가지입니다. 프로세스를 생성할 때 부모 프로세스가 쓰고 있는 자료구조를 복사합니다. 새로운 프로세스의 자료구조를 초기화하는 것보다 이미 생성된 프로세스의 데이터를 복제하는 것이 더 효율적이기 때문입니다.

_do_fork() 함수 선언부와 반환값 확인

먼저 _do_fork() 함수의 선언부를 보면서 이 함수에 전달되는 인자와 반환값을 확인해 보겠습니다.

https://elixir.bootlin.com/linux/v4.19.30/source/kernel/fork.c
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);

먼저 반환값을 확인해 볼까요? 함수 선언부와 같이 반환값 타입은 long인데 프로세스의 PID를 반환합니다. 프로세스 생성 시 에러가 발생하면 PID 대신 PTR_ERR() 매크로로 지정된 에러 값을 반환합니다. 만약 프로세스의 PID가 210이면 210을 _do_fork() 함수가 반환합니다.

이어서 _do_fork() 함수에 전달하는 인자값들을 점검합시다.

unsigned long clone_flags;

clone_flags 필드는 프로세스를 생성할 때 지정하는 옵션 정보를 저장합니다. 옵션 정보는 프로세스를 생성할 때 부모 프로세스로부터 복제될 리소스 정보를 의미합니다.

https://elixir.bootlin.com/linux/v4.19.30/source/include/uapi/linux/sched.h
#define CSIGNAL 0x000000ff 
#define CLONE_VM 0x00000100  
#define CLONE_FS 0x00000200  
#define CLONE_FILES 0x00000400  
#define CLONE_SIGHAND 0x00000800  
#define CLONE_PTRACE 0x00002000 
#define CLONE_VFORK 0x00004000 
#define CLONE_PARENT 0x00008000 
#define CLONE_THREAD 0x00010000  

위에서 보이는 매크로를 OR 비트 연산한 결과를 clone_flags 필드에 저장합니다.

unsigned long stack_start;

보통 유저 영역에서 스레드를 생성할 때 복사하려는 스택의 주소입니다. 이 스택 주소는 유저 공간에서 실행 중인 프로세스 스택의 주소입니다.

unsigned long stack_size;

유저 영역에서 실행 중인 스택 크기입니다. 보통 유저 영역에서 스레드를 생성할 때 복사합니다.

int __user *parent_tidptr;
int __user *child_tidptr;

부모와 자식 스레드 그룹을 관리하는 핸들 정보입니다. 

지금까지 _do_fork() 함수에 전달되는 인자를 살펴봤습니다. 이어서 커널에서 _do_fork() 함수를 언제 호출하는지 알아보겠습니다.

커널에서 _do_fork() 함수를 언제 호출할까?

커널에서 프로세스를 생성할 때 프로세스 유형에 따라 _do_fork() 함수를 호출하는 흐름은 다릅니다.

 1. 유저 모드에서 생성한 프로세스: sys_clone() 시스템 콜 핸들러 함수 
 2. 커널 모드에서 생성한 커널 스레드: kernel_thread() 함수

프로세스는 유저 모드에서 생성된 프로세스와 커널 모드에서 생성된 프로세스로 분류할 수 있습니다. 이를 각각 유저 레벨 프로세스와 커널 레벨 프로세스라고 합니다.

다음 절에서는 유저 레벨 프로세스를 생성할 때 어떤 흐름으로 _do_fork() 커널 함수를 호출하는지 살펴보겠습니다.


#프로세스

프로세스 소개 
프로세스 확인하기  
프로세스는 어떻게 생성할까?  
유저 레벨 프로세스 실행 실습  
커널 스레드  
커널 내부 프로세스의 생성 과정   
프로세스의 종료 과정 분석  
태스크 디스크립터(task_struct 구조체)  
스레드 정보: thread_info 구조체  
프로세스의 태스크 디스크립터에 접근하는 매크로 함수  
프로세스 디버깅  
   * glibc의 fork() 함수를 gdb로 디버깅하기  


"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)



# Reference: For more information on 'Linux Kernel';

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2


Thanks,
Austin Kim







핑백

덧글

댓글 입력 영역