fork - 프로세스 실행 방식(1)

fork는 프로세스를 생성하는 시스템 콜이다.

일반적으로 사용자는 터미널 등에서 실행파일을 실행시킴으로써 프로세스를 생성하지만, fork와 같은 시스템 콜을 프로그램 안에서 호출함으로써 프로세스를 만들 수도 있다.

아래 예제 코드를 보면,

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    printf("(pid: %d) main starts\n", (int)getpid());
    int rc = fork();
    if(rc < 0)
    {
        fprintf(stderr, "fork failed!\n");
        exit(1);
    }
    else if(rc == 0)
    {
        printf("(pid: %d) I am child\n", (int)getpid());
    }
    else
    {
        printf("(pid: %d) I am parent of %d\n", (int)getpid(), rc);
    }
}


fork()를 호출할 경우 cpu를 커널 모드로 변경한 뒤, 현재 프로세스가 할당받은 코드, 데이터, 힙, 스택 영역 등을 그대로 복사하여 메모리에 새로 할당한다.

기존 프로세스가 사용하던 메모리 영역만큼 새로운 프로세스를 위해 공간을 마련한 뒤, 기계적으로 가져가는 것이다.

모든 것을 동일하게 복사하더라도 프로세스 번호 PID는 다르게 가져가기 때문에, fork()의 반환값으로 해당 자식 프로세스의 PID를 반환한다.

fork를 통해 생성된 자식프로세스의 경우에는 실행흐름이 fork 호출 이후 부분부터 시작한다.

즉 현재 코드에서는 if(rc < 0) 부분이 자기가 실행할 위치라고 생각한다는 뜻인데, 그렇기 때문에 해당 위치 앞의 코드 영역들은 실행되지 않는다.

뿐만 아니라 앞에서 선언한 변수들은 부모 프로세스에서 메모리에 올라와 있기 때문에 자식 프로세스의 메모리에도 공간을 차지하긴 하지만, 값을 그대로 가져가지는 않는다.

메모리에는 올라와 있지만, 그냥 실행했었던 흔적 정도라고 볼 수 있는 것이다.

그렇기 때문에 부모 프로세스에서의 rc 값은 자식프로세스의 PID이지만, 자식 프로세스 입장에서 rc 라는 변수에는 0 이 들어가게 되며, 같은 코드임에도 불구하고 조건문으로 분기가 가능한 것이다.

먼저 첫번째 조건인 rc 가 음수일 경우에는 fork 라는 시스템 콜이 실패했을 경우에 반환된 값이기 때문에, 알맞게 처리를 해준다.

두번째 조건인 rc == 0 의 경우에는 자식 프로세스가 실행될 때 곧바로 걸리게 된다.

자식 프로세스가 실행될 경우에는 메인함수 처음부터 시작하는 것이 아닌 fork 이후인 조건문부터 시작하며, 해당 실행흐름 이전까지의 내용들을 위한 값들이 메모리에 흔적처럼만 남아있으며 rc 값은 0으로 설정되기 때문이다.

마지막 else 는, fork 이후 자식의 PID를 rc값에 받아온 부모 프로세스에서 걸린다.


실제로 해당 코드를 실행하면 아래처럼 출력된다.

bconfiden2@h01:~$ ./a.out
(pid: 37670) main starts
(pid: 37670) I am parent of 37671
(pid: 37671) I am child

처음에 메인함수의 첫번째 줄인 main starts 가 자신의 pid와 함께 출력된 뒤, fork를 통해 내부적으로 자식 프로세스를 만들고, 부모 프로세스는 else문을 통해, 자식 프로세스는 else if 문을 통해 각자 자신이 출력해야할 문장을 출력했다.

그러나 여기서 부모와 자식 프로세스 사이에 누가 먼저 실행되는지를 보장받는 것은 아니다.

두개의 프로세스가 생성되고 cpu를 사용하기 위해 경쟁하고 있는 상황이기 때문에, 스케줄러 입장에서는 어떤 프로세스가 먼저 실행되어야 한다는 규칙은 없기 때문이다.

자식 프로세스를 먼저 스케줄링할 수도 있고, 부모를 먼저 스케줄링 할 수도 있다.

부모 프로세스에서 wait 을 통해 자식 프로세스가 끝날때까지 기다린다고 할지라도, 출력문의 결과 순서는 항상 자식 - 부모 순으로 나오긴 하겠지만, 프로세스의 실행 순서 자체를 보장하지는 않는다.