2017년 2월 4일 토요일

fork 함수와 process 생성

리눅스는 새로운 프로세스와 스레드 생성을 fork (=_do_fork)함수를 이용하여 수행한다. fork 는 현재 태스크(프로세스)를 복제하여 자식 프로세스를 만든다. fork를 수행하는 프로세스의 task_struct 자료구료를 복사하여 새로운 프로세스(혹은 스레드)를 만드는 것이다. 이런 경우 원래 프로세스(fork 를 수행한 프로세스)는 부모 프로세스(parent process)가 되며, 새로운 프로세스는 자식 프로세스(child process)가 된다.

이렇게 만들어진 프로세스는 새로운 pid를 부여받으며, 이때 부모의 아이디는 ppid에 저장된다. 한 프로세스는 자식 프로세스를 여러 개 생성할 수 있으며, 자식 프로세스들끼리는 부모가 같은 형제프로세스(sibling process)가 된다. 자식 프로세스는 부모 프로세스의 task_struct 를 복사하여 만들어진다. 따라서 상속되지 않는 지연된 시그널과 같은 일부 자원과 통계수치를 제외하고는 그 부모와 동일한 값을 갖는다.

프로세스 생성 fork 커널 thread

user space 레벨에서 프로세스 생성을 위하여 커널 단에서는 세가지 시스템 콜을 제공하며, 이를 통하여 프로세스 생성을 가능케 한다. 3가지 시스템콜은 fork, vfork, clone이 되겠다. kernel space 레벨에서도 특정한 작업을 위하여 커널 스레드(kernel thread)를 만드는 경우가 있는데, 이를 위해 kernel_thread 함수를 제공하고 있다 kernel_thread 함수는 커널 함수인 _do_fork 함수를 직접 호출하여 스레드를 생성 실행한다.

user space 에서 프로세스 생성은 fork, vfork, clone 3가지 시스템콜 중에 하나로 수행되며, kernel space 에서 프로세스 생성은 kernel_thread 함수가 _do_fork 를 직접 호출하여 수행된다. 4가지 방식은 모두 _do_fork 함수를 호출하는 점은 동일하며, 호출할때 arg 설정으로 그 차이점이 발생한다.

pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
    return _do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
        (unsigned long)arg, NULL, NULL, 0);
}

SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
    return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
    /* can not support in nommu mode */
    return -EINVAL;
#endif
}

SYSCALL_DEFINE0(vfork)
{
    return _do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, 0,
            0, NULL, NULL, 0);
}

SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
         int __user *, parent_tidptr,
         int __user *, child_tidptr,
         unsigned long, tls)
{
    return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls);
}

위의 4가지 방식의 _do_fork 함수 호출 소스를 살펴보자. #define 관련한 소스는 상당부분 삭제하였다.

fork 시스템콜을 보면 SIGCHLD flag 설정을 제외하면, 추가적인 설정은 없다. SIGCHLD flag의 의미는 자식 프로세스가 종료 될 때 부모 프로세스에게 SIGCHLD 시그널을 보내라는 설정이다. 즉, 해당 flag 설정이 되어 있으면, 프로세스 exit 수행 시에 SIGCHLD 시그널이 부모 프로세스에게 전달된다.

kernel_thread 함수와 vfork 함수는 CLONE_VM가 설정된다. CLONE_VM는 새로 생성되는 자식 프로세스와 부모 프로세스 간에 메모리 공간을 공유하겠다는 설정이다. kernel thread가 각자의 메모리 공간이 존재하지 않는 점은 해당 flag 설정 때문이다. 또한, clone에는 CLONE_VM 설정이 없지만, clone_flags 를 arg 로 받아오기 때문에, 얼마든지 설정이 가능하고, 많은 곳에서 그렇게 사용하고 있다. 4가지 호출 방식의 비교는 뒤에서 다시 세부적으로 하기로 하겠다.

fork vfork clone kernel_thread _do_fork

_do_fork 함수는 크게 copy_process함수와 wake_up_new_task함수 호출 부분으로 나뉜다. copy_process함수에서는 프로세스 생성을 위한 초기화 작업들이 이뤄진다. 부모 프로세스의 task_struct 와 thread_info 구조체를 복사하고 필요한 부분은 초기화하여, execution 을 위한 준비를 한다. wake_up_new_task 함수는 새로이 생성한 task_struct 를 scheduler 쪽에 알려주는 역할을 한다. 새로운 process 생성을 완료하고 scheduler에 알려서, runqueue에 등록/실행될 수 있도록 하는 것이다.

copy_process 함수가 하는 일을 크게 나누어 보면 아래와같다.
1. Check clone_flags : 인자로 넘어온 clone_flags를 확인한다. 리눅스 구현 상 허용되지 못하는 flag 설정이 시스템콜을 통하여 전달될 수 있다. 이를 걸러내기 위한 확인 작업이 되겠다.
2. dup_task_struct : 프로세스의 기본 구성이 되는 task_struct 와 thread_info 구조체를 생성하고, 부모 프로세스의 구조체로부터 복사한다. 커널은 task_struct 와 thread_info 구조체를 슬랩 할당자(slab allocator)를 통해 메모리를 할당한다. 할당된 메모리에 부모 프로세스의 정보를 복사하고, 공유될 수 없는 정보들은 초기화한다.
3. check max number of processes : rlimit (Resource limit ID)값을 확인하여, 프로세스를 생성하는 user의 최대허용 프로세스 개수를 넘지 않도록 한다.
4. Initializing time stamps and irqflags : 프로세스 관련 정보 중 time stamp 통계 값이나 irqflags 등을 초기화한다. 이런 정보들은 프로세스 간에 공유가 불가능하다.
5. Scheduler related setup & Copy process info : sched_fork함수를 호출하여 scheduler 쪽에서 해당 task_struct 에 대한 초기화 작업을 한다. Scheduler 에서는 해당 프로세스를 TASK_NEW state로 설정하고, priority 값 등을 초기화하는 작업이 이뤄진다. copy_fs 는 file system 정보를 복사하며, copy_files 는 현재 오픈되어 있는 file 정보를 복사한다. 각 함수 안에서는 clone_flags를 확인하여, 각 CLONE_FS, CLONE_FILES 비트가 설정되어 있어야 실제 복사가 이뤄진다. 프로세스 메모리 공간의 복사를 수행하는 copy_mm 함수도 호출되며, signal 복사도 이뤄지는 등 여러 함수가 호출되니, 한번 쭉 보기 바란다.

copy_process 함수는 마무리 작업을 완료한 후 새로이 생성된 task_struct 구조체를 반환한다. 반환된 task_struct 구조체는 위에서 이야기한대로 scheduler의 wake_up_new_task 함수에 인자로 담아서 전달하면, runqueue에 등록/실행될 수 있도록 준비하는 것이다.

Navigation of this blog:이 블로그 한눈에 보기

댓글 없음:

댓글 쓰기