exec - 프로세스 실행 방식(2)

프로그램이 실행되려면 메모리에 로딩이 되어야 한다.

처음에 커맨드라인 등에서 프로그램을 실행하면 실행파일이 메모리로 탑재 되면서 데이터, 스택, 힙 영역이 생성되며 실행에 필요한 요건들을 갖추게 되는데, 이를 로딩이라고 한다.

이러면 개념적으로 프로세스가 생성되는 것이다.

이렇게 형성된 프로세스 내에서 fork를 할 경우 기존의 프로세스를 싹 복사해가서 새로운 프로세스를 만들지만, exec의 경우에는 현재 프로세스의 영역 위에다가 새로운 프로세스를 덮어쓴다.

코드 영역에는 실행하려는 다른 프로그램의 코드를 써놓은 뒤, 데이터 스택 힙 영역의 모든 값들을 초기화시킴으로써 다른 일을 하기 위한 요건이 갖춰지는 것이다.

즉, 호출하는 프로세스가 새로운 프로세스로 완전히 변경되는 방식이며, 이 때 새로운 프로세스를 위해 메모리를 따로 할당하지 않고 기존 프로세스가 사용하던 영역들을 사용한다.

결과적으로 메모리에는 호출된 프로세스만 남으며, exec()을 호출한 프로세스는 사라진다.


아래의 코드를 보면

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.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());
        char* args[3];
        args[0] = strdup("wc");
        args[1] = strdup("exec.c");
        args[2] = NULL;
        execvp(args[0], args);
        printf("--------not printed--------")
    }
    else
    {
        int wc = wait(NULL);
        printf("(pid: %d) I am parent of %d, wait %d", (int)getpid(), rc, wc);
    }
}

execvp 을 호출할 때 매개변수를 넘겨주고 있는데, 이 부분이 실행할 프로그램과 해당 프로그램을 실행시킬 때 넘겨줄 인자들이 된다.

여기서는 wc 를 넘겨줌으로써 리눅스에서 기본적으로 제공하고 있는 /usr/bin/wc 실행파일을 불러오고, 해당 실행파일이 매개변수로 받는 값들을 같이 넘겨준다.

즉 터미널에서 wc exec.c를 실행하는 것이라고 볼 수 있지만, 이렇게 직접적으로 프로세스를 만드는 것이 아니라 기존에 돌아가던 프로세스의 영역을 사용하는 것이다.

fork로 생성된 자식 프로세스의 코드 영역에는 wc 를 위한 코드가 올라가고, 메모리는 싹 초기화가 된다.


실제로 실행한 결과는 아래와 같다.

bconfiden2@h01:~$ ./a.out
(pid: 39066) main starts
(pid: 39067) I am child
      29      79     665 exec.c
(pid: 39066) I am parent of 39067, wait 39067

터미널에서 해당 코드의 실행파일을 실행시킬 경우 메인함수의 첫줄에서 찍는 printf 문을 자신의 PID와 함께 출력한다.

그 뒤 fork를 통해 자식프로세스를 생성하고, 스케줄러에 의해 만약 부모프로세스가 먼저 실행된다고 하더라도 wait으로 자식프로세스의 종료를 대기한다.

결국 자식프로세스가 실행되면서 출력문을 하나 찍은 뒤, exec으로 자기의 영역에 wc 를 실행시키기 때문에 wc의 결과가 출력되는 것이다.

또한 exec 으로 메모리상에서 기존 c 코드는 싹 지워진 것이기 때문에, 원래의 코드 상에서 exec 이후에 존재해야했던 출력문은 실행되지 않는 것을 확인할 수 있다.