Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


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

_do_fork() 함수 소개

리눅스에서 구동 중인 모든 프로세스는 _do_fork() 함수가 실행할 때 생성됩니다. 프로세스는 누가 생성할까요? 리눅스 시스템에서 프로세스 생성을 전담하는 프로세스가 있습니다. 주인공은 init과 kthreadd 프로세스입니다.

유저 레벨 프로세스는 init 프로세스, 커널 레벨 프로세스(커널 스레드)는 kthreadd 프로세스가 생성합니다. 그런데 프로세스는 생성이 아니라 복제된다고 말할 수 있습니다. 

     "그러면 프로세스를 생성할 때 부모 프로세스를 복제하는 이유는 무엇일까?" 

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

리눅스 커널에서는 '코드의 성능이나 속도를 개선'하기 위한 많은 기법이 적용되고 있습니다.
그 중 대표적인 방법을 한 가지 소개하겠습니다.

     "'이미 만들어놓은 리소스'를 가져다 쓴다."

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

프로세스 생성 과정도 마찬가지입니다. 프로세스를 생성할 때 부모 프로세스가 쓰고 있는 자료구조를 복사합니다. 프로세스를 생성할 때 이미 생성된 프로세스에서 복제하는 것이 속도 면에서 더 효율적이기 때문입니다. 


_do_fork() 함수 선언부와 반환값 확인하기
먼저 _do_fork() 함수 선언부를 보면서 이 함수에 전달되는 인자와 반환값을 확인합시다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/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://github.com/raspberrypi/linux/blob/rpi-4.19.y/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() 커널 함수를 호출하는지 살펴봅니다.

Reference(프로세스 관리)

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

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

#Reference(프로세스 관리)
프로세스 디버깅
   glibc fork 함수 gdb 디버깅


핑백

덧글

댓글 입력 영역